Add new contact us flow.

master
Alex Hart 2020-03-05 19:05:06 -04:00 committed by Greyson Parrelli
parent f1f505d41c
commit f9de131017
21 changed files with 764 additions and 6 deletions

View File

@ -29,6 +29,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import org.thoughtcrime.securesms.help.HelpFragment;
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
@ -66,6 +67,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private final DynamicTheme dynamicTheme = new DynamicTheme();
@ -154,6 +156,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
this.findPreference(PREFERENCE_CATEGORY_HELP)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
@ -240,6 +244,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment();
break;
case PREFERENCE_CATEGORY_HELP:
fragment = new HelpFragment();
break;
default:
throw new AssertionError();
}

View File

@ -0,0 +1,279 @@
package org.thoughtcrime.securesms.help;
import android.content.Intent;
import android.content.pm.LabeledIntent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import com.annimon.stream.Stream;
import com.dd.CircularProgressButton;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class HelpFragment extends Fragment {
private EditText problem;
private CheckBox includeDebugLogs;
private View debugLogInfo;
private View faq;
private CircularProgressButton next;
private View toaster;
private List<EmojiImageView> emoji;
private HelpViewModel helpViewModel;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.help_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
initializeViewModels();
initializeViews(view);
initializeListeners();
initializeObservers();
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__help);
cancelSpinning(next);
problem.setEnabled(true);
}
private void initializeViewModels() {
helpViewModel = ViewModelProviders.of(this).get(HelpViewModel.class);
}
private void initializeViews(@NonNull View view) {
problem = view.findViewById(R.id.help_fragment_problem);
includeDebugLogs = view.findViewById(R.id.help_fragment_debug);
debugLogInfo = view.findViewById(R.id.help_fragment_debug_info);
faq = view.findViewById(R.id.help_fragment_faq);
next = view.findViewById(R.id.help_fragment_next);
toaster = view.findViewById(R.id.help_fragment_next_toaster);
emoji = new ArrayList<>(Feeling.values().length);
for (Feeling feeling : Feeling.values()) {
EmojiImageView emojiView = view.findViewById(feeling.getViewId());
emojiView.setImageEmoji(feeling.getEmojiCode());
emoji.add(view.findViewById(feeling.getViewId()));
}
}
private void initializeListeners() {
problem.addTextChangedListener(new AfterTextChanged(e -> helpViewModel.onProblemChanged(e.toString())));
Stream.of(emoji).forEach(view -> view.setOnClickListener(this::handleEmojiClicked));
faq.setOnClickListener(v -> launchFaq());
debugLogInfo.setOnClickListener(v -> launchDebugLogInfo());
next.setOnClickListener(v -> submitForm());
toaster.setOnClickListener(v -> Toast.makeText(requireContext(), R.string.HelpFragment__please_be_as_descriptive_as_possible, Toast.LENGTH_LONG).show());
}
private void initializeObservers() {
//noinspection CodeBlock2Expr
helpViewModel.isFormValid().observe(getViewLifecycleOwner(), isValid -> {
next.setEnabled(isValid);
toaster.setVisibility(isValid ? View.GONE : View.VISIBLE);
});
}
private void handleEmojiClicked(@NonNull View clicked) {
if (clicked.isSelected()) {
clicked.setSelected(false);
} else {
Stream.of(emoji).forEach(view -> view.setSelected(false));
clicked.setSelected(true);
}
}
private void launchFaq() {
Uri data = Uri.parse(getString(R.string.HelpFragment__link__faq));
Intent intent = new Intent(Intent.ACTION_VIEW, data);
startActivity(intent);
}
private void launchDebugLogInfo() {
Uri data = Uri.parse(getString(R.string.HelpFragment__link__debug_info));
Intent intent = new Intent(Intent.ACTION_VIEW, data);
startActivity(intent);
}
private void submitForm() {
setSpinning(next);
problem.setEnabled(false);
helpViewModel.onSubmitClicked(includeDebugLogs.isChecked()).observe(this, result -> {
if (result.getDebugLogUrl().isPresent()) {
submitFormWithDebugLog(result.getDebugLogUrl().get());
} else if (result.isError()) {
submitFormWithDebugLog(getString(R.string.HelpFragment__could_not_upload_logs));
} else {
submitFormWithDebugLog(null);
}
});
}
private void submitFormWithDebugLog(@Nullable String debugLog) {
Feeling feeling = Stream.of(emoji)
.filter(View::isSelected)
.map(view -> Feeling.getByViewId(view.getId()))
.findFirst().orElse(null);
Spanned body = getEmailBody(debugLog, feeling);
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{getString(R.string.RegistrationActivity_support_email)});
intent.putExtra(Intent.EXTRA_SUBJECT, getEmailSubject());
intent.putExtra(Intent.EXTRA_TEXT, body.toString());
startActivity(intent);
}
private String getEmailSubject() {
return getString(R.string.HelpFragment__signal_android_support_request);
}
private Spanned getEmailBody(@Nullable String debugLog, @Nullable Feeling feeling) {
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(problem.getText().toString());
builder.append("\n\n");
builder.append("--- ");
builder.append(getString(R.string.HelpFragment__support_info));
builder.append(" ---\n");
builder.append(getString(R.string.HelpFragment__subject));
builder.append(" ");
builder.append(getString(R.string.HelpFragment__signal_android_support_request));
builder.append("\n");
builder.append(getString(R.string.HelpFragment__device_info));
builder.append(" ");
builder.append(getDeviceInfo());
builder.append("\n");
builder.append(getString(R.string.HelpFragment__android_version));
builder.append(" ");
builder.append(getAndroidVersion());
builder.append("\n");
builder.append(getString(R.string.HelpFragment__signal_version));
builder.append(" ");
builder.append(getSignalVersion());
builder.append("\n");
builder.append(getString(R.string.HelpFragment__locale));
builder.append(" ");
builder.append(Locale.getDefault().toString());
if (debugLog != null) {
builder.append("\n");
builder.append(getString(R.string.HelpFragment__debug_log));
builder.append(" ");
builder.append(debugLog);
}
if (feeling != null) {
builder.append("\n\n");
builder.append(feeling.getEmojiCode());
builder.append("\n");
builder.append(getString(feeling.getStringId()));
}
return builder;
}
private static CharSequence getDeviceInfo() {
return String.format("%s %s (%s)", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT);
}
private static CharSequence getAndroidVersion() {
return String.format("%s (%s, %s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL, Build.DISPLAY);
}
private static CharSequence getSignalVersion() {
return BuildConfig.VERSION_NAME;
}
private static void setSpinning(@Nullable CircularProgressButton button) {
if (button != null) {
button.setClickable(false);
button.setIndeterminateProgressMode(true);
button.setProgress(50);
}
}
private static void cancelSpinning(@Nullable CircularProgressButton button) {
if (button != null) {
button.setProgress(0);
button.setIndeterminateProgressMode(false);
button.setClickable(true);
}
}
private enum Feeling {
ECSTATIC(R.id.help_fragment_emoji_5, R.string.HelpFragment__emoji_5, "\ud83d\ude00"),
HAPPY(R.id.help_fragment_emoji_4, R.string.HelpFragment__emoji_4, "\ud83d\ude42"),
AMBIVALENT(R.id.help_fragment_emoji_3, R.string.HelpFragment__emoji_3, "\ud83d\ude10"),
UNHAPPY(R.id.help_fragment_emoji_2, R.string.HelpFragment__emoji_2, "\ud83d\ude41"),
ANGRY(R.id.help_fragment_emoji_1, R.string.HelpFragment__emoji_1, "\ud83d\ude20");
private final @IdRes int viewId;
private final @StringRes int stringId;
private final CharSequence emojiCode;
Feeling(@IdRes int viewId, @StringRes int stringId, @NonNull CharSequence emojiCode) {
this.viewId = viewId;
this.stringId = stringId;
this.emojiCode = emojiCode;
}
public @IdRes int getViewId() {
return viewId;
}
public @StringRes int getStringId() {
return stringId;
}
public @NonNull CharSequence getEmojiCode() {
return emojiCode;
}
static Feeling getByViewId(@IdRes int viewId) {
for (Feeling feeling : values()) {
if (feeling.viewId == viewId) {
return feeling;
}
}
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.help;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import org.thoughtcrime.securesms.logsubmit.LogLine;
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository;
import org.thoughtcrime.securesms.util.livedata.LiveDataPair;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
public class HelpViewModel extends ViewModel {
private static final int MINIMUM_PROBLEM_CHARS = 10;
private MutableLiveData<Boolean> problemMeetsLengthRequirements = new MutableLiveData<>();
private MutableLiveData<Boolean> hasLines = new MutableLiveData<>(false);
private LiveData<Boolean> isFormValid = Transformations.map(new LiveDataPair<>(problemMeetsLengthRequirements, hasLines), this::transformValidationData);
private final SubmitDebugLogRepository submitDebugLogRepository;
private List<LogLine> logLines;
public HelpViewModel() {
submitDebugLogRepository = new SubmitDebugLogRepository();
submitDebugLogRepository.getLogLines(lines -> {
logLines = lines;
hasLines.postValue(true);
});
}
LiveData<Boolean> isFormValid() {
return isFormValid;
}
void onProblemChanged(@NonNull String problem) {
problemMeetsLengthRequirements.setValue(problem.length() >= MINIMUM_PROBLEM_CHARS);
}
LiveData<SubmitResult> onSubmitClicked(boolean includeDebugLogs) {
MutableLiveData<SubmitResult> resultLiveData = new MutableLiveData<>();
if (includeDebugLogs) {
submitDebugLogRepository.submitLog(logLines, result -> resultLiveData.postValue(new SubmitResult(result, result.isPresent())));
} else {
resultLiveData.postValue(new SubmitResult(Optional.absent(), false));
}
return resultLiveData;
}
private boolean transformValidationData(Pair<Boolean, Boolean> validationData) {
return validationData.first() == Boolean.TRUE && validationData.second() == Boolean.TRUE;
}
static class SubmitResult {
private final Optional<String> debugLogUrl;
private final boolean isError;
private SubmitResult(@NonNull Optional<String> debugLogUrl, boolean isError) {
this.debugLogUrl = debugLogUrl;
this.isError = isError;
}
@NonNull Optional<String> getDebugLogUrl() {
return debugLogUrl;
}
boolean isError() {
return isError;
}
}
}

View File

@ -7,7 +7,7 @@ import com.annimon.stream.Stream;
import java.util.List;
import java.util.regex.Pattern;
interface LogLine {
public interface LogLine {
long getId();
@NonNull String getText();

View File

@ -41,7 +41,7 @@ import okhttp3.ResponseBody;
* - Create a new {@link LogSection}.
* - Add it to {@link #SECTIONS}. The order of the list is the order the sections are displayed.
*/
class SubmitDebugLogRepository {
public class SubmitDebugLogRepository {
private static final String TAG = Log.tag(SubmitDebugLogRepository.class);
@ -67,16 +67,16 @@ class SubmitDebugLogRepository {
private final Context context;
private final ExecutorService executor;
SubmitDebugLogRepository() {
public SubmitDebugLogRepository() {
this.context = ApplicationDependencies.getApplication();
this.executor = SignalExecutors.SERIAL;
}
void getLogLines(@NonNull Callback<List<LogLine>> callback) {
public void getLogLines(@NonNull Callback<List<LogLine>> callback) {
executor.execute(() -> callback.onResult(getLogLinesInternal()));
}
void submitLog(@NonNull List<LogLine> lines, Callback<Optional<String>> callback) {
public void submitLog(@NonNull List<LogLine> lines, Callback<Optional<String>> callback) {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(lines)));
}
@ -209,7 +209,7 @@ class SubmitDebugLogRepository {
return out.toString();
}
interface Callback<E> {
public interface Callback<E> {
void onResult(E result);
}
}

View File

@ -1,10 +1,14 @@
package org.thoughtcrime.securesms.util;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
@ -15,4 +19,21 @@ public class IntentUtils {
return resolveInfoList != null && resolveInfoList.size() > 1;
}
/**
* From: <a href="https://stackoverflow.com/a/12328282">https://stackoverflow.com/a/12328282</a>
*/
public static @Nullable LabeledIntent getLabelintent(@NonNull Context context, @NonNull Intent origIntent, int name, int drawable) {
PackageManager pm = context.getPackageManager();
ComponentName launchName = origIntent.resolveActivity(pm);
if (launchName != null) {
Intent resolved = new Intent();
resolved.setComponent(launchName);
resolved.setData(origIntent.getData());
return new LabeledIntent(resolved, context.getPackageName(), name, drawable);
}
return null;
}
}

View File

@ -1,12 +1,19 @@
package org.thoughtcrime.securesms.util;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
public class SpanUtil {

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/signal_primary" android:state_enabled="true" />
<item android:color="@color/core_grey_75" />
</selector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/signal_primary" android:state_enabled="true" />
<item android:color="@color/core_grey_25" />
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<solid android:color="@color/core_grey_25" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<solid android:color="@color/signal_primary" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@color/core_grey_75" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<solid android:color="@color/signal_primary" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@color/core_grey_05" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
<padding android:bottom="1dp" />
<solid android:color="@color/core_grey_90" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
<solid android:color="@color/core_grey_75" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
<padding android:bottom="1dp" />
<solid android:color="@color/core_grey_25" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
<solid android:color="@color/core_grey_05" />
</shape>
</item>
</layer-list>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?icon_tint" android:pathData="M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1ZM12.88,18.13a1.18,1.18 0,0 1,-0.88 0.37,1.24 1.24,0 0,1 -0.88,-2.13A1.16,1.16 0,0 1,12 16a1.18,1.18 0,0 1,0.88 0.37,1.23 1.23,0 0,1 0,1.76ZM14.3,11.73a2.67,2.67 0,0 0,-1.45 2.65v0.11L11.12,14.49v-0.11c0,-2.24 0.68,-2.93 1.8,-3.62a2.33,2.33 0,0 0,1.28 -2A1.74,1.74 0,0 0,12.34 7a1.83,1.83 0,0 0,-1.91 1.84L8.59,8.84a3.44,3.44 0,0 1,3.75 -3.36c2.22,0 3.7,1.33 3.7,3.29A3.24,3.24 0,0 1,14.3 11.73Z"/>
</vector>

View File

@ -0,0 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/help_fragment_scroller"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fadingEdge="vertical"
android:fillViewport="true"
android:requiresFadingEdge="vertical"
app:layout_constraintBottom_toTopOf="@id/help_fragment_faq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/help_fragment_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/HelpFragment__contact_us"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?title_text_color_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/help_fragment_problem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:background="?attr/help_problem_background"
android:gravity="top"
android:hint="@string/HelpFragment__tell_us_whats_going_on"
android:inputType="textMultiLine"
android:minHeight="144dp"
android:padding="16dp"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/help_fragment_contact" />
<CheckBox
android:id="@+id/help_fragment_debug"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:checked="true"
android:text="@string/HelpFragment__include_debug_log"
android:textColor="?title_text_color_secondary"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_debug_info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/help_fragment_debug_info" />
<Button
android:id="@+id/help_fragment_debug_info"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:background="@android:color/transparent"
android:text="@string/HelpFragment__whats_this"
android:textAllCaps="false"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/signal_primary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/help_fragment_debug"
app:layout_constraintTop_toBottomOf="@id/help_fragment_problem" />
<TextView
android:id="@+id/help_fragment_feelings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/HelpFragment__how_do_you_feel"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?title_text_color_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/help_fragment_debug_info" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/help_fragment_emoji_5"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:background="?attr/help_emoji_radio_background"
android:button="@null"
android:gravity="center"
android:scaleType="centerInside"
app:layout_constraintEnd_toStartOf="@id/help_fragment_emoji_4"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/help_fragment_feelings" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/help_fragment_emoji_4"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:background="?attr/help_emoji_radio_background"
android:button="@null"
android:gravity="center"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_emoji_5"
app:layout_constraintEnd_toEndOf="@id/help_fragment_emoji_3"
app:layout_constraintStart_toEndOf="@id/help_fragment_emoji_5"
app:layout_constraintTop_toTopOf="@id/help_fragment_emoji_5" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/help_fragment_emoji_3"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:background="?attr/help_emoji_radio_background"
android:button="@null"
android:gravity="center"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_emoji_4"
app:layout_constraintEnd_toStartOf="@id/help_fragment_emoji_2"
app:layout_constraintStart_toEndOf="@id/help_fragment_emoji_4"
app:layout_constraintTop_toTopOf="@id/help_fragment_emoji_4" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/help_fragment_emoji_2"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:background="?attr/help_emoji_radio_background"
android:button="@null"
android:gravity="center"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_emoji_3"
app:layout_constraintEnd_toStartOf="@id/help_fragment_emoji_1"
app:layout_constraintStart_toEndOf="@id/help_fragment_emoji_3"
app:layout_constraintTop_toTopOf="@id/help_fragment_emoji_3" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/help_fragment_emoji_1"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:background="?attr/help_emoji_radio_background"
android:button="@null"
android:gravity="center"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_emoji_2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/help_fragment_emoji_2"
app:layout_constraintTop_toTopOf="@id/help_fragment_emoji_2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<Button
android:id="@+id/help_fragment_faq"
style="@style/Button.Borderless"
android:layout_width="0dp"
android:layout_height="60dp"
android:gravity="start|center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/HelpFragment__have_you_read_our_faq_yet"
android:textAllCaps="false"
android:textAppearance="@style/Signal.Text.Body"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/help_fragment_next"
app:layout_constraintStart_toStartOf="parent" />
<com.dd.CircularProgressButton
android:id="@+id/help_fragment_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:enabled="false"
android:textColor="@color/white"
app:cpb_colorIndicator="@color/white"
app:cpb_colorProgress="@color/signal_primary"
app:cpb_cornerRadius="4dp"
app:cpb_selectorIdle="?attr/help_next_background"
app:cpb_textIdle="@string/HelpFragment__next"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_faq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/help_fragment_faq"
app:layout_constraintTop_toTopOf="@id/help_fragment_faq" />
<View
android:id="@+id/help_fragment_next_toaster"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:enabled="true"
app:layout_constraintBottom_toBottomOf="@id/help_fragment_faq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/help_fragment_faq"
app:layout_constraintTop_toTopOf="@id/help_fragment_faq" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -202,6 +202,11 @@
<attr name="linked_devices_icon" format="reference" />
<attr name="advanced_icon" format="reference" />
<attr name="safety_number_icon" format="reference" />
<attr name="help_icon" format="reference" />
<attr name="help_problem_background" format="reference" />
<attr name="help_next_background" format="reference" />
<attr name="help_emoji_radio_background" format="reference" />
<attr name="message_request_dialog_button_background" format="reference" />
<attr name="message_request_text_color_primary" format="color" />

View File

@ -1436,6 +1436,35 @@
<string name="MessageRequestsMegaphone__add_name">Add name</string>
<string name="MessageRequestsMegaphone__you_can_now_choose_whether_to_accept">You can now choose whether to accept a new conversation. Youll see options to \"Accept,\" \"Delete,\" or \"Block.\"</string>
<!-- HelpFragment -->
<string name="HelpFragment__help">Help</string>
<string name="HelpFragment__have_you_read_our_faq_yet">Have you read our FAQ yet?</string>
<string name="HelpFragment__next">Next</string>
<string name="HelpFragment__contact_us">Contact us</string>
<string name="HelpFragment__tell_us_whats_going_on">Tell us what\'s going on</string>
<string name="HelpFragment__include_debug_log">Include debug log.</string>
<string name="HelpFragment__whats_this">What\'s this?</string>
<string name="HelpFragment__how_do_you_feel">How do you feel? (Optional)</string>
<string name="HelpFragment__emoji_5" translatable="false">emoji_5</string>
<string name="HelpFragment__emoji_4" translatable="false">emoji_4</string>
<string name="HelpFragment__emoji_3" translatable="false">emoji_3</string>
<string name="HelpFragment__emoji_2" translatable="false">emoji_2</string>
<string name="HelpFragment__emoji_1" translatable="false">emoji_1</string>
<string name="HelpFragment__link__debug_info" translatable="false">https://support.signal.org/hc/en-us/articles/360007318591-Debug-Logs-and-Crash-Reports</string>
<string name="HelpFragment__link__faq" translatable="false">https://support.signal.org</string>
<string name="HelpFragment__support_info">Support Info</string>
<string name="HelpFragment__subject">Subject:</string>
<string name="HelpFragment__signal_android_support_request">Signal Android Support Request</string>
<string name="HelpFragment__device_info">Device info:</string>
<string name="HelpFragment__android_version">Android version:</string>
<string name="HelpFragment__signal_version">Signal version:</string>
<string name="HelpFragment__locale">Locale:</string>
<string name="HelpFragment__debug_log">Debug Log:</string>
<string name="HelpFragment__na">n/a</string>
<string name="HelpFragment__could_not_upload_logs">Could not upload logs</string>
<string name="HelpFragment__signal_support">Signal Support</string>
<string name="HelpFragment__please_be_as_descriptive_as_possible">Please be as descriptive as possible to help us understand the issue.</string>
<!-- arrays.xml -->
<string name="arrays__import_export">Import</string>
<string name="arrays__use_default">Use default</string>
@ -1522,6 +1551,7 @@
<string name="preferences__fast">Fast</string>
<string name="preferences__normal">Normal</string>
<string name="preferences__slow">Slow</string>
<string name="preferences__help">Help</string>
<string name="preferences__advanced">Advanced</string>
<string name="preferences__privacy">Privacy</string>
<string name="preferences__mms_user_agent">MMS User Agent</string>

View File

@ -221,6 +221,10 @@
<item name="conversation_group_member_name">#99000000</item>
<item name="help_emoji_radio_background">@drawable/help_fragment_emoji_radio_background_light</item>
<item name="help_next_background">@color/help_fragment_next_light</item>
<item name="help_problem_background">@drawable/help_fragment_problem_background_light</item>
<item name="conversation_background">@color/core_white</item>
<item name="conversation_editor_background">#22000000</item>
<item name="conversation_editor_text_color">#ff111111</item>
@ -364,6 +368,7 @@
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
<item name="advanced_icon">@drawable/ic_advanced_24</item>
<item name="safety_number_icon">@drawable/ic_safety_number_outline_24</item>
<item name="help_icon">@drawable/ic_help_outline_24</item>
<item name="message_request_dialog_button_background">@drawable/message_request_button_background_light</item>
<item name="message_request_text_color_primary">@color/core_grey_90</item>
<item name="message_request_text_color_secondary">@color/core_grey_60</item>
@ -531,6 +536,10 @@
<item name="fab_color">@color/textsecure_primary_dark</item>
<item name="lower_right_divet">@drawable/divet_lower_right_light</item>
<item name="help_emoji_radio_background">@drawable/help_fragment_emoji_radio_background_dark</item>
<item name="help_next_background">@color/help_fragment_next_dark</item>
<item name="help_problem_background">@drawable/help_fragment_problem_background_dark</item>
<item name="conversation_background">@color/core_grey_95</item>
<item name="conversation_editor_background">#22ffffff</item>
<item name="conversation_editor_text_color">#ffeeeeee</item>
@ -631,6 +640,7 @@
<item name="linked_devices_icon">@drawable/ic_linked_devices_24</item>
<item name="advanced_icon">@drawable/ic_advanced_24</item>
<item name="safety_number_icon">@drawable/ic_safety_number_solid_24</item>
<item name="help_icon">@drawable/ic_help_solid_24</item>
<item name="message_request_dialog_button_background">@drawable/message_request_button_background_dark</item>
<item name="message_request_text_color_primary">@color/core_grey_05</item>
<item name="message_request_text_color_secondary">@color/core_grey_25</item>

View File

@ -33,6 +33,10 @@
android:title="@string/preferences__linked_devices"
android:icon="?attr/linked_devices_icon"/>
<Preference android:key="preference_category_help"
android:title="@string/preferences__help"
android:icon="?attr/help_icon" />
<Preference android:key="preference_category_advanced"
android:title="@string/preferences__advanced"
android:icon="?attr/advanced_icon"/>