Signal-Android/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java

263 wiersze
9.7 KiB
Java

package org.thoughtcrime.securesms.help;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream;
import org.signal.core.util.ResourceUtil;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
import java.util.ArrayList;
import java.util.List;
public class HelpFragment extends LoggingFragment {
public static final String START_CATEGORY_INDEX = "start_category_index";
public static final int PAYMENT_INDEX = 6;
public static final int DONATION_INDEX = 7;
private EditText problem;
private CheckBox includeDebugLogs;
private View debugLogInfo;
private View faq;
private CircularProgressMaterialButton next;
private View toaster;
private List<EmojiImageView> emoji;
private HelpViewModel helpViewModel;
private Spinner categorySpinner;
private ArrayAdapter<CharSequence> categoryAdapter;
@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();
next.cancelSpinning();
problem.setEnabled(true);
}
private void initializeViewModels() {
helpViewModel = new ViewModelProvider(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);
categorySpinner = view.findViewById(R.id.help_fragment_category);
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()));
}
categoryAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.HelpFragment__categories_4, android.R.layout.simple_spinner_item);
categoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
categorySpinner.setAdapter(categoryAdapter);
Bundle args = getArguments();
if (args != null) {
categorySpinner.setSelection(Util.clamp(args.getInt(START_CATEGORY_INDEX, 0), 0, categorySpinner.getCount() - 1));
}
}
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 -> {
if (helpViewModel.getCategoryIndex() == 0) {
categorySpinner.startAnimation(AnimationUtils.loadAnimation(requireContext(), R.anim.shake_horizontal));
}
Toast.makeText(requireContext(), R.string.HelpFragment__please_be_as_descriptive_as_possible, Toast.LENGTH_LONG).show();
});
categorySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
helpViewModel.onCategorySelected(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
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() {
next.setSpinning();
problem.setEnabled(false);
helpViewModel.onSubmitClicked(includeDebugLogs.isChecked()).observe(getViewLifecycleOwner(), 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);
CommunicationActions.openEmail(requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getEmailSubject(),
getEmailBody(debugLog, feeling));
}
private String getEmailSubject() {
return getString(R.string.HelpFragment__signal_android_support_request);
}
private String getEmailBody(@Nullable String debugLog, @Nullable Feeling feeling) {
StringBuilder suffix = new StringBuilder();
if (debugLog != null) {
suffix.append("\n");
suffix.append(getString(R.string.HelpFragment__debug_log));
suffix.append(" ");
suffix.append(debugLog);
}
if (feeling != null) {
suffix.append("\n\n");
suffix.append(feeling.getEmojiCode());
suffix.append("\n");
suffix.append(getString(feeling.getStringId()));
}
String[] englishCategories = ResourceUtil.getEnglishResources(requireContext()).getStringArray(R.array.HelpFragment__categories_4);
String category = (helpViewModel.getCategoryIndex() >= 0 && helpViewModel.getCategoryIndex() < englishCategories.length) ? englishCategories[helpViewModel.getCategoryIndex()]
: categoryAdapter.getItem(helpViewModel.getCategoryIndex()).toString();
return SupportEmailUtil.generateSupportEmailBody(requireContext(),
R.string.HelpFragment__signal_android_support_request,
" - " + category,
problem.getText().toString() + "\n\n",
suffix.toString());
}
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();
}
}
}