Refactor app settings.

This commit is contained in:
Alex Hart
2021-05-12 13:02:44 -03:00
committed by Greyson Parrelli
parent a94d77d81e
commit f2d5ea0391
179 changed files with 5244 additions and 3894 deletions

View File

@@ -1,404 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013-2017 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversationlist.model.UnreadPayments;
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData;
import org.thoughtcrime.securesms.help.HelpFragment;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
import org.thoughtcrime.securesms.preferences.EditProxyFragment;
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.widgets.PaymentsPreference;
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
/**
* The Activity for application preference display and management.
*
* @author Moxie Marlinspike
*
*/
public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
implements SharedPreferences.OnSharedPreferenceChangeListener
{
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
public static final String LAUNCH_TO_NOTIFICATIONS_FRAGMENT = "launch.to.notifications.fragment";
@SuppressWarnings("unused")
private static final String TAG = Log.tag(ApplicationPreferencesActivity.class);
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
private static final String PREFERENCE_CATEGORY_USERNAME = "preference_category_username";
private static final String PREFERENCE_CATEGORY_SMS_MMS = "preference_category_sms_mms";
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
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 static final String PREFERENCE_CATEGORY_DONATE = "preference_category_donate";
private static final String PREFERENCE_CATEGORY_PAYMENTS = "preference_category_payments";
private static final String WAS_CONFIGURATION_UPDATED = "was_configuration_updated";
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private boolean wasConfigurationUpdated = false;
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle icicle, boolean ready) {
//noinspection ConstantConditions
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (getIntent() != null && getIntent().getCategories() != null && getIntent().getCategories().contains("android.intent.category.NOTIFICATION_PREFERENCES")) {
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_BACKUPS_FRAGMENT, false)) {
initFragment(android.R.id.content, new BackupsPreferenceFragment());
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
Bundle bundle = new Bundle();
bundle.putInt(HelpFragment.START_CATEGORY_INDEX, getIntent().getIntExtra(HelpFragment.START_CATEGORY_INDEX, 0));
initFragment(android.R.id.content, new HelpFragment(), null, bundle);
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
initFragment(android.R.id.content, EditProxyFragment.newInstance());
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_NOTIFICATIONS_FRAGMENT, false)) {
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
} else if (icicle == null) {
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
} else {
wasConfigurationUpdated = icicle.getBoolean(WAS_CONFIGURATION_UPDATED);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(WAS_CONFIGURATION_UPDATED, wasConfigurationUpdated);
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content);
fragment.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onSupportNavigateUp() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else {
if (wasConfigurationUpdated) {
setResult(MainActivity.RESULT_CONFIG_CHANGED);
} else {
setResult(RESULT_OK);
}
finish();
}
return true;
}
@Override
public void onBackPressed() {
onSupportNavigateUp();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(TextSecurePreferences.THEME_PREF)) {
DynamicTheme.setDefaultDayNightMode(this);
recreate();
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
CachedInflater.from(this).clear();
wasConfigurationUpdated = true;
recreate();
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.LOCALE_CHANGE_EVENT);
startService(intent);
}
}
public void pushFragment(@NonNull Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
.replace(android.R.id.content, fragment)
.addToBackStack(null)
.commit();
}
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment {
private final UnreadPaymentsLiveData unreadPaymentsLiveData = new UnreadPaymentsLiveData();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
.setOnPreferenceClickListener(new ProfileClickListener());
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
.setOnPreferenceClickListener(new UsernameClickListener());
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_CHATS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
.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));
this.findPreference(PREFERENCE_CATEGORY_DONATE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DONATE));
Preference paymentsPreference = this.findPreference(PREFERENCE_CATEGORY_PAYMENTS);
if (SignalStore.paymentsValues().getPaymentsAvailability().showPaymentsMenu()) {
paymentsPreference.setVisible(true);
paymentsPreference.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PAYMENTS));
} else {
paymentsPreference.setVisible(false);
}
tintIcons();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (SignalStore.paymentsValues().getPaymentsAvailability().showPaymentsMenu()) {
PaymentsPreference paymentsPreference = (PaymentsPreference) this.findPreference(PREFERENCE_CATEGORY_PAYMENTS);
unreadPaymentsLiveData.observe(getViewLifecycleOwner(), unreadPayments -> paymentsPreference.setUnreadCount(unreadPayments.transform(UnreadPayments::getUnreadCount).or(-1)));
}
}
private void tintIcons() {
if (Build.VERSION.SDK_INT >= 21) return;
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
preference.getIcon().setColorFilter(ContextCompat.getColor(requireContext(), R.color.signal_icon_tint_primary), PorterDuff.Mode.SRC_IN);
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences);
if (FeatureFlags.usernames()) {
UsernamePreference pref = (UsernamePreference) findPreference(PREFERENCE_CATEGORY_USERNAME);
pref.setVisible(shouldDisplayUsernameReminder());
pref.setOnLongClickListener(v -> {
new AlertDialog.Builder(requireContext())
.setMessage(R.string.ApplicationPreferencesActivity_hide_reminder)
.setPositiveButton(R.string.ApplicationPreferencesActivity_hide, (dialog, which) -> {
dialog.dismiss();
SignalStore.misc().hideUsernameReminder();
findPreference(PREFERENCE_CATEGORY_USERNAME).setVisible(false);
})
.setNegativeButton(android.R.string.cancel, ((dialog, which) -> dialog.dismiss()))
.setCancelable(true)
.show();
return true;
});
}
}
@Override
public void onResume() {
super.onResume();
//noinspection ConstantConditions
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.text_secure_normal__menu_settings);
setCategorySummaries();
setCategoryVisibility();
}
private void setCategorySummaries() {
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
if (FeatureFlags.usernames()) {
this.findPreference(PREFERENCE_CATEGORY_USERNAME)
.setVisible(shouldDisplayUsernameReminder());
}
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
.setSummary(SmsMmsPreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
.setSummary(NotificationsPreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
.setSummary(AppProtectionPreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setSummary(AppearancePreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_CHATS)
.setSummary(ChatsPreferenceFragment.getSummary(getActivity()));
}
private void setCategoryVisibility() {
Preference devicePreference = this.findPreference(PREFERENCE_CATEGORY_DEVICES);
if (devicePreference != null && !TextSecurePreferences.isPushRegistered(getActivity())) {
getPreferenceScreen().removePreference(devicePreference);
}
}
private static boolean shouldDisplayUsernameReminder() {
return FeatureFlags.usernames() && !Recipient.self().getUsername().isPresent() && SignalStore.misc().shouldShowUsernameReminder();
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
private String category;
CategoryClickListener(String category) {
this.category = category;
}
@Override
public boolean onPreferenceClick(Preference preference) {
Fragment fragment = null;
switch (category) {
case PREFERENCE_CATEGORY_SMS_MMS:
fragment = new SmsMmsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_NOTIFICATIONS:
fragment = new NotificationsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_APP_PROTECTION:
fragment = new AppProtectionPreferenceFragment();
break;
case PREFERENCE_CATEGORY_APPEARANCE:
fragment = new AppearancePreferenceFragment();
break;
case PREFERENCE_CATEGORY_CHATS:
fragment = new ChatsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_STORAGE:
fragment = new DataAndStoragePreferenceFragment();
break;
case PREFERENCE_CATEGORY_DEVICES:
Intent intent = new Intent(getActivity(), DeviceActivity.class);
startActivity(intent);
break;
case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment();
break;
case PREFERENCE_CATEGORY_HELP:
fragment = new HelpFragment();
break;
case PREFERENCE_CATEGORY_DONATE:
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.donate_url));
break;
case PREFERENCE_CATEGORY_PAYMENTS:
startActivity(new Intent(requireContext(), PaymentsActivity.class));
break;
default:
throw new AssertionError();
}
if (fragment != null) {
Bundle args = new Bundle();
fragment.setArguments(args);
((ApplicationPreferencesActivity) requireActivity()).pushFragment(fragment);
}
return true;
}
}
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
requireActivity().startActivity(ManageProfileActivity.getIntent(requireActivity()));
return true;
}
}
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
requireActivity().startActivity(ManageProfileActivity.getIntentForUsernameEdit(preference.getContext()));
return true;
}
}
}
}

View File

@@ -26,11 +26,12 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
@@ -65,8 +66,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@Override
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
int displayMode = Util.isDefaultSmsProvider(this) ? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
}

View File

@@ -77,16 +77,6 @@ public class DeviceActivity extends PassphraseRequiredActivity
} else {
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.getCurrentLocale());
}
overridePendingTransition(R.anim.slide_from_end, R.anim.slide_to_start);
}
@Override
protected void onPause() {
if (isFinishing()) {
overridePendingTransition(R.anim.slide_from_start, R.anim.slide_to_end);
}
super.onPause();
}
@Override

View File

@@ -9,6 +9,8 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
@@ -69,8 +71,7 @@ public class MainNavigator {
}
public void goToAppSettings() {
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
activity.startActivityForResult(intent, REQUEST_CONFIG_CHANGES);
activity.startActivityForResult(AppSettingsActivity.home(activity), REQUEST_CONFIG_CHANGES);
}
public void goToArchiveList() {

View File

@@ -74,7 +74,7 @@ public class BackupDialog {
BackupPassphrase.set(context, Util.join(password, " "));
TextSecurePreferences.setNextBackupTime(context, 0);
TextSecurePreferences.setBackupEnabled(context, true);
SignalStore.settings().setBackupEnabled(true);
LocalBackupListener.schedule(context);
onBackupsEnabled.run();

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.backup;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -11,8 +10,8 @@ import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
@@ -39,11 +38,7 @@ public enum BackupFileIOError {
}
public void postNotification(@NonNull Context context) {
Intent intent = new Intent(context, ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_BACKUPS_FRAGMENT, true);
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, intent, 0);
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, AppSettingsActivity.backups(context), 0);
Notification backupFailedNotification = new NotificationCompat.Builder(context, NotificationChannels.FAILURES)
.setSmallIcon(R.drawable.ic_signal_backup)
.setContentTitle(context.getString(titleId))

View File

@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.components.mention.MentionDeleter;
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -201,7 +202,7 @@ public class ComposeText extends EmojiEditText {
}
public void setTransport(TransportOption transport) {
final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext());
final boolean useSystemEmoji = SignalStore.settings().isPreferSystemEmoji();
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
int inputType = getInputType();
@@ -225,7 +226,7 @@ public class ComposeText extends EmojiEditText {
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
InputConnection inputConnection = super.onCreateInputConnection(editorInfo);
if(TextSecurePreferences.isEnterSendsEnabled(getContext())) {
if(SignalStore.settings().isEnterKeySends()) {
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}

View File

@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.conversation.ConversationStickerSuggestionAdapter;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.mms.GlideApp;
@@ -124,7 +125,7 @@ public class InputPanel extends LinearLayout
this.recordLockCancel.setOnClickListener(v -> microphoneRecorderView.cancelAction());
if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
if (SignalStore.settings().isPreferSystemEmoji()) {
mediaKeyboard.setVisibility(View.GONE);
emojiVisible = false;
} else {

View File

@@ -13,6 +13,7 @@ import androidx.appcompat.widget.AppCompatEditText;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -34,7 +35,7 @@ public class EmojiEditText extends AppCompatEditText {
boolean forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
a.recycle();
if (forceCustom || !TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
if (forceCustom || !SignalStore.settings().isPreferSystemEmoji()) {
if (!isInEditMode()) {
setFilters(appendEmojiFilter(this.getFilters()));
}

View File

@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -215,7 +216,7 @@ public class EmojiTextView extends AppCompatTextView {
}
private boolean useSystemEmoji() {
return !forceCustom && TextSecurePreferences.isSystemEmojiPreferred(getContext());
return !forceCustom && SignalStore.settings().isPreferSystemEmoji();
}
@Override

View File

@@ -0,0 +1,71 @@
package org.thoughtcrime.securesms.components.settings
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
open class DSLSettingsActivity : PassphraseRequiredActivity() {
private val dynamicTheme = DynamicNoActionBarTheme()
protected lateinit var navController: NavController
private set
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
setContentView(R.layout.dsl_settings_activity)
if (savedInstanceState == null) {
val navGraphId = intent.getIntExtra(ARG_NAV_GRAPH, -1)
if (navGraphId == -1) {
throw IllegalStateException("No navgraph id was passed to activity")
}
val fragment: NavHostFragment = NavHostFragment.create(navGraphId)
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host_fragment, fragment)
.commitNow()
navController = fragment.navController
} else {
val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = fragment.navController
}
dynamicTheme.onCreate(this)
onBackPressedDispatcher.addCallback(this, OnBackPressed())
}
override fun onResume() {
super.onResume()
dynamicTheme.onResume(this)
}
override fun onNavigateUp(): Boolean {
return if (!Navigation.findNavController(this, R.id.nav_host_fragment).popBackStack()) {
onWillFinish()
finish()
true
} else {
false
}
}
protected open fun onWillFinish() {}
companion object {
const val ARG_NAV_GRAPH = "nav_graph"
}
private inner class OnBackPressed : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onNavigateUp()
}
}
}

View File

@@ -0,0 +1,181 @@
package org.thoughtcrime.securesms.components.settings
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.switchmaterial.SwitchMaterial
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingViewHolder
import org.thoughtcrime.securesms.util.ViewUtil
class DSLSettingsAdapter : MappingAdapter() {
init {
registerFactory(ClickPreference::class.java, LayoutFactory(::ClickPreferenceViewHolder, R.layout.dsl_preference_item))
registerFactory(TextPreference::class.java, LayoutFactory(::TextPreferenceViewHolder, R.layout.dsl_preference_item))
registerFactory(RadioListPreference::class.java, LayoutFactory(::RadioListPreferenceViewHolder, R.layout.dsl_preference_item))
registerFactory(MultiSelectListPreference::class.java, LayoutFactory(::MultiSelectListPreferenceViewHolder, R.layout.dsl_preference_item))
registerFactory(ExternalLinkPreference::class.java, LayoutFactory(::ExternalLinkPreferenceViewHolder, R.layout.dsl_preference_item))
registerFactory(DividerPreference::class.java, LayoutFactory(::DividerPreferenceViewHolder, R.layout.dsl_divider_item))
registerFactory(SectionHeaderPreference::class.java, LayoutFactory(::SectionHeaderPreferenceViewHolder, R.layout.dsl_section_header))
registerFactory(SwitchPreference::class.java, LayoutFactory(::SwitchPreferenceViewHolder, R.layout.dsl_switch_preference_item))
}
}
abstract class PreferenceViewHolder<T : PreferenceModel<T>>(itemView: View) : MappingViewHolder<T>(itemView) {
protected val iconView: ImageView = itemView.findViewById(R.id.icon)
protected val titleView: TextView = itemView.findViewById(R.id.title)
protected val summaryView: TextView = itemView.findViewById(R.id.summary)
@CallSuper
override fun bind(model: T) {
listOf(itemView, titleView, summaryView).forEach {
it.isEnabled = model.isEnabled
}
if (model.iconId != -1) {
iconView.setImageResource(model.iconId)
iconView.visibility = View.VISIBLE
} else {
iconView.setImageDrawable(null)
iconView.visibility = View.GONE
}
val title = model.title?.resolve(context)
if (title != null) {
titleView.text = model.title?.resolve(context)
titleView.visibility = View.VISIBLE
} else {
titleView.visibility = View.GONE
}
val summary = model.summary?.resolve(context)
if (summary != null) {
summaryView.text = summary
summaryView.visibility = View.VISIBLE
val spans = (summaryView.text as? Spanned)?.getSpans(0, summaryView.text.length, ClickableSpan::class.java)
if (spans?.isEmpty() == false) {
summaryView.movementMethod = LinkMovementMethod.getInstance()
} else {
summaryView.movementMethod = null
}
} else {
summaryView.visibility = View.GONE
summaryView.movementMethod = null
}
}
}
class TextPreferenceViewHolder(itemView: View) : PreferenceViewHolder<TextPreference>(itemView)
class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ClickPreference>(itemView) {
override fun bind(model: ClickPreference) {
super.bind(model)
itemView.setOnClickListener { model.onClick() }
}
}
class RadioListPreferenceViewHolder(itemView: View) : PreferenceViewHolder<RadioListPreference>(itemView) {
override fun bind(model: RadioListPreference) {
super.bind(model)
summaryView.visibility = View.VISIBLE
summaryView.text = model.listItems[model.selected]
itemView.setOnClickListener {
MaterialAlertDialogBuilder(context)
.setTitle(model.title.resolve(context))
.setSingleChoiceItems(model.listItems, model.selected) { dialog, which ->
model.onSelected(which)
dialog.dismiss()
}
.show()
}
}
}
class MultiSelectListPreferenceViewHolder(itemView: View) : PreferenceViewHolder<MultiSelectListPreference>(itemView) {
override fun bind(model: MultiSelectListPreference) {
super.bind(model)
summaryView.visibility = View.VISIBLE
val summaryText = model.selected
.mapIndexed { index, isChecked -> if (isChecked) model.listItems[index] else null }
.filterNotNull()
.joinToString(", ")
if (summaryText.isEmpty()) {
summaryView.setText(R.string.preferences__none)
} else {
summaryView.text = summaryText
}
val selected = model.selected.copyOf()
itemView.setOnClickListener {
MaterialAlertDialogBuilder(context)
.setTitle(model.title.resolve(context))
.setMultiChoiceItems(model.listItems, selected) { _, _, _ ->
// Intentionally empty
}
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
.setPositiveButton(android.R.string.ok) { d, _ ->
model.onSelected(selected)
d.dismiss()
}
.show()
}
}
}
class SwitchPreferenceViewHolder(itemView: View) : PreferenceViewHolder<SwitchPreference>(itemView) {
private val switchWidget: SwitchMaterial = itemView.findViewById(R.id.switch_widget)
override fun bind(model: SwitchPreference) {
super.bind(model)
switchWidget.isEnabled = model.isEnabled
switchWidget.isChecked = model.isChecked
itemView.setOnClickListener {
model.onClick()
}
}
}
class ExternalLinkPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ExternalLinkPreference>(itemView) {
override fun bind(model: ExternalLinkPreference) {
super.bind(model)
val externalLinkIcon = requireNotNull(ContextCompat.getDrawable(context, R.drawable.ic_open_20))
externalLinkIcon.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
if (itemView.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
titleView.setCompoundDrawables(null, null, externalLinkIcon, null)
} else {
titleView.setCompoundDrawables(externalLinkIcon, null, null, null)
}
itemView.setOnClickListener { CommunicationActions.openBrowserLink(itemView.context, itemView.context.getString(model.linkId)) }
}
}
class DividerPreferenceViewHolder(itemView: View) : MappingViewHolder<DividerPreference>(itemView) {
override fun bind(model: DividerPreference) = Unit
}
class SectionHeaderPreferenceViewHolder(itemView: View) : MappingViewHolder<SectionHeaderPreference>(itemView) {
private val sectionHeader: TextView = itemView.findViewById(R.id.section_header)
override fun bind(model: SectionHeaderPreference) {
sectionHeader.text = model.title.resolve(context)
}
}

View File

@@ -0,0 +1,94 @@
package org.thoughtcrime.securesms.components.settings
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.EdgeEffect
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.R
abstract class DSLSettingsFragment(
@StringRes private val titleId: Int,
@MenuRes private val menuId: Int = -1
) : Fragment(R.layout.dsl_settings_fragment) {
private lateinit var recyclerView: RecyclerView
private lateinit var toolbarShadowHelper: ToolbarShadowHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
val toolbarShadow: View = view.findViewById(R.id.toolbar_shadow)
toolbar.setTitle(titleId)
toolbar.setNavigationOnClickListener {
requireActivity().onBackPressed()
}
if (menuId != -1) {
toolbar.inflateMenu(menuId)
toolbar.setOnMenuItemClickListener { onOptionsItemSelected(it) }
}
recyclerView = view.findViewById(R.id.recycler)
recyclerView.edgeEffectFactory = EdgeEffectFactory()
toolbarShadowHelper = ToolbarShadowHelper(toolbarShadow)
val adapter = DSLSettingsAdapter()
recyclerView.adapter = adapter
recyclerView.addOnScrollListener(toolbarShadowHelper)
bindAdapter(adapter)
}
override fun onResume() {
super.onResume()
toolbarShadowHelper.onScrolled(recyclerView, 0, 0)
}
abstract fun bindAdapter(adapter: DSLSettingsAdapter)
private class EdgeEffectFactory : RecyclerView.EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return super.createEdgeEffect(view, direction).apply {
if (Build.VERSION.SDK_INT > 21) {
color =
requireNotNull(ContextCompat.getColor(view.context, R.color.settings_ripple_color))
}
}
}
}
class ToolbarShadowHelper(private val toolbarShadow: View) : RecyclerView.OnScrollListener() {
private var lastAnimationState = ToolbarAnimationState.NONE
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val newAnimationState =
if (recyclerView.canScrollVertically(-1)) ToolbarAnimationState.SHOW else ToolbarAnimationState.HIDE
if (newAnimationState == lastAnimationState) {
return
}
when (newAnimationState) {
ToolbarAnimationState.NONE -> throw AssertionError()
ToolbarAnimationState.HIDE -> toolbarShadow.animate().alpha(0f)
ToolbarAnimationState.SHOW -> toolbarShadow.animate().alpha(1f)
}
lastAnimationState = newAnimationState
}
}
private enum class ToolbarAnimationState {
NONE,
HIDE,
SHOW
}
}

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms.components.settings
import android.content.Context
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import org.thoughtcrime.securesms.util.SpanUtil
sealed class DSLSettingsText {
private data class FromResource(
@StringRes private val stringId: Int,
@ColorInt private val textColor: Int?
) : DSLSettingsText() {
override fun resolve(context: Context): CharSequence {
val text = context.getString(stringId)
return if (textColor == null) {
text
} else {
SpanUtil.color(textColor, text)
}
}
}
private data class FromCharSequence(private val charSequence: CharSequence) : DSLSettingsText() {
override fun resolve(context: Context): CharSequence = charSequence
}
abstract fun resolve(context: Context): CharSequence
companion object {
fun from(@StringRes stringId: Int, @ColorInt textColor: Int? = null): DSLSettingsText =
FromResource(stringId, textColor)
fun from(charSequence: CharSequence): DSLSettingsText = FromCharSequence(charSequence)
}
}

View File

@@ -0,0 +1,121 @@
package org.thoughtcrime.securesms.components.settings.app
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.navigation.NavDirections
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity
import org.thoughtcrime.securesms.help.HelpFragment
import org.thoughtcrime.securesms.keyvalue.SettingsValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.CachedInflater
import org.thoughtcrime.securesms.util.DynamicTheme
private const val START_LOCATION = "app.settings.start.location"
private const val NOTIFICATION_CATEGORY = "android.intent.category.NOTIFICATION_PREFERENCES"
private const val STATE_WAS_CONFIGURATION_UPDATED = "app.settings.state.configuration.updated"
class AppSettingsActivity : DSLSettingsActivity() {
private var wasConfigurationUpdated = false
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
if (intent?.hasExtra(ARG_NAV_GRAPH) != true) {
intent?.putExtra(ARG_NAV_GRAPH, R.navigation.app_settings)
}
super.onCreate(savedInstanceState, ready)
val startingAction: NavDirections? = if (intent?.categories?.contains(NOTIFICATION_CATEGORY) == true) {
AppSettingsFragmentDirections.actionDirectToNotificationsSettingsFragment()
} else {
when (StartLocation.fromCode(intent?.getIntExtra(START_LOCATION, StartLocation.HOME.code))) {
StartLocation.HOME -> null
StartLocation.BACKUPS -> AppSettingsFragmentDirections.actionDirectToBackupsPreferenceFragment()
StartLocation.HELP -> AppSettingsFragmentDirections.actionDirectToHelpFragment()
.setStartCategoryIndex(intent.getIntExtra(HelpFragment.START_CATEGORY_INDEX, 0))
StartLocation.PROXY -> AppSettingsFragmentDirections.actionDirectToEditProxyFragment()
StartLocation.NOTIFICATIONS -> AppSettingsFragmentDirections.actionDirectToNotificationsSettingsFragment()
}
}
if (startingAction == null && savedInstanceState != null) {
wasConfigurationUpdated = savedInstanceState.getBoolean(STATE_WAS_CONFIGURATION_UPDATED)
}
startingAction?.let {
navController.navigate(it)
}
SignalStore.settings().onConfigurationSettingChanged.observe(this) { key ->
if (key == SettingsValues.THEME) {
DynamicTheme.setDefaultDayNightMode(this)
recreate()
} else if (key == SettingsValues.LANGUAGE) {
CachedInflater.from(this).clear()
wasConfigurationUpdated = true
recreate()
val intent = Intent(this, KeyCachingService::class.java)
intent.action = KeyCachingService.LOCALE_CHANGE_EVENT
startService(intent)
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(STATE_WAS_CONFIGURATION_UPDATED, wasConfigurationUpdated)
}
override fun onWillFinish() {
if (wasConfigurationUpdated) {
setResult(MainActivity.RESULT_CONFIG_CHANGED)
} else {
setResult(RESULT_OK)
}
}
companion object {
@JvmStatic
fun home(context: Context): Intent = getIntentForStartLocation(context, StartLocation.HOME)
@JvmStatic
fun backups(context: Context): Intent = getIntentForStartLocation(context, StartLocation.BACKUPS)
@JvmStatic
fun help(context: Context, startCategoryIndex: Int = 0): Intent {
return getIntentForStartLocation(context, StartLocation.HOME)
.putExtra(HelpFragment.START_CATEGORY_INDEX, startCategoryIndex)
}
@JvmStatic
fun proxy(context: Context): Intent = getIntentForStartLocation(context, StartLocation.HELP)
@JvmStatic
fun notifications(context: Context): Intent = getIntentForStartLocation(context, StartLocation.NOTIFICATIONS)
private fun getIntentForStartLocation(context: Context, startLocation: StartLocation): Intent {
return Intent(context, AppSettingsActivity::class.java)
.putExtra(ARG_NAV_GRAPH, R.navigation.app_settings)
.putExtra(START_LOCATION, startLocation.code)
}
}
private enum class StartLocation(val code: Int) {
HOME(0),
BACKUPS(1),
HELP(2),
PROXY(3),
NOTIFICATIONS(4);
companion object {
fun fromCode(code: Int?): StartLocation {
return values().find { code == it.code } ?: HOME
}
}
}
}

View File

@@ -0,0 +1,198 @@
package org.thoughtcrime.securesms.components.settings.app
import android.view.View
import android.widget.TextView
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.AvatarImageView
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingViewHolder
class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__menu_settings) {
override fun bindAdapter(adapter: DSLSettingsAdapter) {
adapter.registerFactory(BioPreference::class.java, MappingAdapter.LayoutFactory(::BioPreferenceViewHolder, R.layout.bio_preference_item))
adapter.registerFactory(PaymentsPreference::class.java, MappingAdapter.LayoutFactory(::PaymentsPreferenceViewHolder, R.layout.dsl_payments_preference))
val viewModel = ViewModelProviders.of(this)[AppSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) { state ->
adapter.submitList(getConfiguration(state).toMappingModelList())
}
}
private fun getConfiguration(state: AppSettingsState): DSLConfiguration {
return configure {
customPref(
BioPreference(state.self) {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_manageProfileActivity)
}
)
clickPref(
title = DSLSettingsText.from(R.string.AccountSettingsFragment__account),
iconId = R.drawable.ic_profile_circle_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_accountSettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__linked_devices),
iconId = R.drawable.ic_linked_devices_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_deviceActivity)
}
)
if (SignalStore.paymentsValues().paymentsAvailability.showPaymentsMenu()) {
customPref(
PaymentsPreference(
unreadCount = state.unreadPaymentsCount
) {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_paymentsActivity)
}
)
}
dividerPref()
clickPref(
title = DSLSettingsText.from(R.string.preferences__appearance),
iconId = R.drawable.ic_appearance_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_appearanceSettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences_chats__chats),
iconId = R.drawable.ic_message_tinted_bitmap_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_chatsSettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__notifications),
iconId = R.drawable.ic_bell_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_notificationsSettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__privacy),
iconId = R.drawable.ic_lock_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_privacySettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__data_and_storage),
iconId = R.drawable.ic_archive_24dp,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_dataAndStorageSettingsFragment)
}
)
dividerPref()
clickPref(
title = DSLSettingsText.from(R.string.preferences__help),
iconId = R.drawable.ic_help_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_helpSettingsFragment)
}
)
clickPref(
title = DSLSettingsText.from(R.string.AppSettingsFragment__invite_your_friends),
iconId = R.drawable.ic_invite_24,
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_inviteActivity)
}
)
externalLinkPref(
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
iconId = R.drawable.ic_heart_24,
linkId = R.string.donate_url
)
if (FeatureFlags.internalUser()) {
dividerPref()
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_preferences),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_internalSettingsFragment)
}
)
}
}
}
private class BioPreference(val recipient: Recipient, val onClick: () -> Unit) : PreferenceModel<BioPreference>() {
override fun areContentsTheSame(newItem: BioPreference): Boolean {
return super.areContentsTheSame(newItem) && recipient.hasSameContent(newItem.recipient)
}
override fun areItemsTheSame(newItem: BioPreference): Boolean {
return recipient == newItem.recipient
}
}
private class BioPreferenceViewHolder(itemView: View) : PreferenceViewHolder<BioPreference>(itemView) {
private val avatarView: AvatarImageView = itemView.findViewById(R.id.icon)
override fun bind(model: BioPreference) {
super.bind(model)
itemView.setOnClickListener { model.onClick() }
titleView.text = model.recipient.getDisplayName(itemView.context)
summaryView.text = model.recipient.requireE164()
avatarView.setAvatar(GlideApp.with(itemView), model.recipient, false, true)
titleView.visibility = View.VISIBLE
summaryView.visibility = View.VISIBLE
avatarView.visibility = View.VISIBLE
}
}
private class PaymentsPreference(val unreadCount: Int, val onClick: () -> Unit) : PreferenceModel<PaymentsPreference>() {
override fun areContentsTheSame(newItem: PaymentsPreference): Boolean {
return super.areContentsTheSame(newItem) && unreadCount == newItem.unreadCount
}
}
private class PaymentsPreferenceViewHolder(itemView: View) : MappingViewHolder<PaymentsPreference>(itemView) {
private val unreadCountView: TextView = itemView.findViewById(R.id.unread_indicator)
override fun bind(model: PaymentsPreference) {
unreadCountView.text = model.unreadCount.toString()
unreadCountView.visibility = if (model.unreadCount > 0) View.VISIBLE else View.GONE
itemView.setOnClickListener {
model.onClick()
}
}
}
}

View File

@@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.components.settings.app
import org.thoughtcrime.securesms.recipients.Recipient
data class AppSettingsState(val self: Recipient, val unreadPaymentsCount: Int)

View File

@@ -0,0 +1,19 @@
package org.thoughtcrime.securesms.components.settings.app
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
class AppSettingsViewModel : ViewModel() {
val unreadPaymentsLiveData = UnreadPaymentsLiveData()
val selfLiveData: LiveData<Recipient> = Recipient.self().live().liveData
val state: LiveData<AppSettingsState> = LiveDataUtil.combineLatest(unreadPaymentsLiveData, selfLiveData) { payments, self ->
val unreadPaymentsCount = payments.transform { it.unreadCount }.or(0)
AppSettingsState(self, unreadPaymentsCount)
}
}

View File

@@ -0,0 +1,180 @@
package org.thoughtcrime.securesms.components.settings.app.account
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.InputType
import android.util.DisplayMetrics
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.autofill.HintConstants
import androidx.core.app.DialogCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.lock.PinHashing
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity
import org.thoughtcrime.securesms.lock.v2.KbsConstants
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.ThemeUtil
class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFragment__account) {
lateinit var viewModel: AccountSettingsViewModel
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
Snackbar.make(requireView(), R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).setTextColor(Color.WHITE).show()
}
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
viewModel = ViewModelProviders.of(this)[AccountSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) { state ->
adapter.submitList(getConfiguration(state).toMappingModelList())
}
}
private fun getConfiguration(state: AccountSettingsState): DSLConfiguration {
return configure {
sectionHeaderPref(R.string.preferences_app_protection__signal_pin)
clickPref(
title = DSLSettingsText.from(if (state.hasPin) R.string.preferences_app_protection__change_your_pin else R.string.preferences_app_protection__create_a_pin),
onClick = {
if (state.hasPin) {
startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN)
} else {
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN)
}
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__pin_reminders),
summary = DSLSettingsText.from(R.string.AccountSettingsFragment__youll_be_asked_less_frequently),
isChecked = state.pinRemindersEnabled,
onClick = {
setPinRemindersEnabled(!state.pinRemindersEnabled)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__registration_lock),
summary = DSLSettingsText.from(R.string.AccountSettingsFragment__require_your_signal_pin),
isChecked = state.registrationLockEnabled,
onClick = {
setRegistrationLockEnabled(!state.registrationLockEnabled)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__advanced_pin_settings),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_accountSettingsFragment_to_advancedPinSettingsActivity)
}
)
dividerPref()
sectionHeaderPref(R.string.AccountSettingsFragment__account)
clickPref(
title = DSLSettingsText.from(R.string.preferences_chats__transfer_account),
summary = DSLSettingsText.from(R.string.preferences_chats__transfer_account_to_a_new_android_device),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_accountSettingsFragment_to_oldDeviceTransferActivity)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__delete_account, ContextCompat.getColor(requireContext(), R.color.signal_alert_primary)),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_accountSettingsFragment_to_deleteAccountFragment)
}
)
}
}
private fun setRegistrationLockEnabled(enabled: Boolean) {
if (enabled) {
RegistrationLockV2Dialog.showEnableDialog(requireContext()) { viewModel.refreshState() }
} else {
RegistrationLockV2Dialog.showDisableDialog(requireContext()) { viewModel.refreshState() }
}
}
private fun setPinRemindersEnabled(enabled: Boolean) {
if (!enabled) {
val context: Context = requireContext()
val metrics: DisplayMetrics = resources.displayMetrics
val dialog: AlertDialog = MaterialAlertDialogBuilder(context, if (ThemeUtil.isDarkTheme(context)) R.style.Theme_Signal_AlertDialog_Dark_Cornered_ColoredAccent else R.style.Theme_Signal_AlertDialog_Light_Cornered_ColoredAccent)
.setView(R.layout.pin_disable_reminders_dialog)
.create()
dialog.show()
dialog.window!!.setLayout((metrics.widthPixels * .80).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
val pinEditText = DialogCompat.requireViewById(dialog, R.id.reminder_disable_pin) as EditText
val statusText = DialogCompat.requireViewById(dialog, R.id.reminder_disable_status) as TextView
val cancelButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_cancel)
val turnOffButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_turn_off)
pinEditText.post {
if (pinEditText.requestFocus()) {
ServiceUtil.getInputMethodManager(pinEditText.context).showSoftInput(pinEditText, 0)
}
}
ViewCompat.setAutofillHints(pinEditText, HintConstants.AUTOFILL_HINT_PASSWORD)
when (SignalStore.pinValues().keyboardType) {
PinKeyboardType.NUMERIC -> pinEditText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
PinKeyboardType.ALPHA_NUMERIC -> pinEditText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
pinEditText.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(text: String) {
turnOffButton.isEnabled = text.length >= KbsConstants.MINIMUM_PIN_LENGTH
}
})
pinEditText.typeface = Typeface.DEFAULT
turnOffButton.setOnClickListener {
val pin = pinEditText.text.toString()
val correct = PinHashing.verifyLocalPinHash(SignalStore.kbsValues().localPinHash!!, pin)
if (correct) {
SignalStore.pinValues().setPinRemindersEnabled(false)
viewModel.refreshState()
dialog.dismiss()
} else {
statusText.setText(R.string.preferences_app_protection__incorrect_pin_try_again)
}
}
cancelButton.setOnClickListener { dialog.dismiss() }
} else {
SignalStore.pinValues().setPinRemindersEnabled(true)
viewModel.refreshState()
}
}
}

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.components.settings.app.account
data class AccountSettingsState(
val hasPin: Boolean,
val pinRemindersEnabled: Boolean,
val registrationLockEnabled: Boolean
)

View File

@@ -0,0 +1,24 @@
package org.thoughtcrime.securesms.components.settings.app.account
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.livedata.Store
class AccountSettingsViewModel : ViewModel() {
private val store: Store<AccountSettingsState> = Store(getCurrentState())
val state: LiveData<AccountSettingsState> = store.stateLiveData
fun refreshState() {
store.update { getCurrentState() }
}
private fun getCurrentState(): AccountSettingsState {
return AccountSettingsState(
hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(),
pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(),
registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled
)
}
}

View File

@@ -0,0 +1,70 @@
package org.thoughtcrime.securesms.components.settings.app.appearance
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__appearance) {
private lateinit var viewModel: AppearanceSettingsViewModel
private val themeLabels by lazy { resources.getStringArray(R.array.pref_theme_entries) }
private val themeValues by lazy { resources.getStringArray(R.array.pref_theme_values) }
private val messageFontSizeLabels by lazy { resources.getStringArray(R.array.pref_message_font_size_entries) }
private val messageFontSizeValues by lazy { resources.getStringArray(R.array.pref_message_font_size_values) }
private val languageLabels by lazy { resources.getStringArray(R.array.language_entries) }
private val languageValues by lazy { resources.getStringArray(R.array.language_values) }
override fun bindAdapter(adapter: DSLSettingsAdapter) {
viewModel = ViewModelProviders.of(this)[AppearanceSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) { state ->
adapter.submitList(getConfiguration(state).toMappingModelList())
}
}
private fun getConfiguration(state: AppearanceSettingsState): DSLConfiguration {
return configure {
radioListPref(
title = DSLSettingsText.from(R.string.preferences__theme),
listItems = themeLabels,
selected = themeValues.indexOf(state.theme),
onSelected = {
viewModel.setTheme(themeValues[it])
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__chat_wallpaper),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_appearanceSettings_to_wallpaperActivity)
}
)
radioListPref(
title = DSLSettingsText.from(R.string.preferences_chats__message_text_size),
listItems = messageFontSizeLabels,
selected = messageFontSizeValues.indexOf(state.messageFontSize.toString()),
onSelected = {
viewModel.setMessageFontSize(messageFontSizeValues[it].toInt())
}
)
radioListPref(
title = DSLSettingsText.from(R.string.preferences__language),
listItems = languageLabels,
selected = languageValues.indexOf(state.language),
onSelected = {
viewModel.setLanguage(languageValues[it])
}
)
}
}
}

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.components.settings.app.appearance
data class AppearanceSettingsState(
val theme: String,
val messageFontSize: Int,
val language: String
)

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms.components.settings.app.appearance
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.livedata.Store
class AppearanceSettingsViewModel : ViewModel() {
private val store: Store<AppearanceSettingsState>
init {
val initialState = AppearanceSettingsState(
SignalStore.settings().theme,
SignalStore.settings().messageFontSize,
SignalStore.settings().language
)
store = Store(initialState)
}
val state: LiveData<AppearanceSettingsState> = store.stateLiveData
fun setTheme(theme: String) {
store.update { it.copy(theme = theme) }
SignalStore.settings().theme = theme
}
fun setLanguage(language: String) {
store.update { it.copy(language = language) }
SignalStore.settings().language = language
}
fun setMessageFontSize(size: Int) {
store.update { it.copy(messageFontSize = size) }
SignalStore.settings().messageFontSize = size
}
}

View File

@@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.components.settings.app.chats
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__chats) {
private lateinit var viewModel: ChatsSettingsViewModel
override fun bindAdapter(adapter: DSLSettingsAdapter) {
val repository = ChatsSettingsRepository()
val factory = ChatsSettingsViewModel.Factory(repository)
viewModel = ViewModelProviders.of(this, factory)[ChatsSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(state: ChatsSettingsState): DSLConfiguration {
return configure {
clickPref(
title = DSLSettingsText.from(R.string.preferences__sms_mms),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_chatsSettingsFragment_to_smsSettingsFragment)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__generate_link_previews),
summary = DSLSettingsText.from(R.string.preferences__retrieve_link_previews_from_websites_for_messages),
isChecked = state.generateLinkPreviews,
onClick = {
viewModel.setGenerateLinkPreviewsEnabled(!state.generateLinkPreviews)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__pref_use_address_book_photos),
summary = DSLSettingsText.from(R.string.preferences__display_contact_photos_from_your_address_book_if_available),
isChecked = state.useAddressBook,
onClick = {
viewModel.setUseAddressBook(!state.useAddressBook)
}
)
dividerPref()
sectionHeaderPref(R.string.ChatsSettingsFragment__keyboard)
switchPref(
title = DSLSettingsText.from(R.string.preferences_advanced__use_system_emoji),
isChecked = state.useSystemEmoji,
onClick = {
viewModel.setUseSystemEmoji(!state.useSystemEmoji)
}
)
switchPref(
title = DSLSettingsText.from(R.string.ChatsSettingsFragment__enter_key_sends),
isChecked = state.enterKeySends,
onClick = {
viewModel.setEnterKeySends(!state.enterKeySends)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences_chats__backups)
clickPref(
title = DSLSettingsText.from(R.string.preferences_chats__chat_backups),
summary = DSLSettingsText.from(if (state.chatBackupsEnabled) R.string.arrays__enabled else R.string.arrays__disabled),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_chatsSettingsFragment_to_backupsPreferenceFragment)
}
)
}
}
}

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms.components.settings.app.chats
import android.content.Context
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
class ChatsSettingsRepository {
private val context: Context = ApplicationDependencies.getApplication()
fun syncLinkPreviewsState() {
SignalExecutors.BOUNDED.execute {
val isLinkPreviewsEnabled = SignalStore.settings().isLinkPreviewsEnabled
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.getJobManager().add(
MultiDeviceConfigurationUpdateJob(
TextSecurePreferences.isReadReceiptsEnabled(context),
TextSecurePreferences.isTypingIndicatorsEnabled(context),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
isLinkPreviewsEnabled
)
)
if (isLinkPreviewsEnabled) {
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.LINK_PREVIEWS)
}
}
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.components.settings.app.chats
data class ChatsSettingsState(
val generateLinkPreviews: Boolean,
val useAddressBook: Boolean,
val useSystemEmoji: Boolean,
val enterKeySends: Boolean,
val chatBackupsEnabled: Boolean
)

View File

@@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.components.settings.app.chats
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ThrottledDebouncer
import org.thoughtcrime.securesms.util.livedata.Store
class ChatsSettingsViewModel(private val repository: ChatsSettingsRepository) : ViewModel() {
private val refreshDebouncer = ThrottledDebouncer(500L)
private val store: Store<ChatsSettingsState> = Store(
ChatsSettingsState(
generateLinkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
useAddressBook = SignalStore.settings().isPreferSystemContactPhotos,
useSystemEmoji = SignalStore.settings().isPreferSystemEmoji,
enterKeySends = SignalStore.settings().isEnterKeySends,
chatBackupsEnabled = SignalStore.settings().isBackupEnabled
)
)
val state: LiveData<ChatsSettingsState> = store.stateLiveData
fun setGenerateLinkPreviewsEnabled(enabled: Boolean) {
store.update { it.copy(generateLinkPreviews = enabled) }
SignalStore.settings().isLinkPreviewsEnabled = enabled
repository.syncLinkPreviewsState()
}
fun setUseAddressBook(enabled: Boolean) {
store.update { it.copy(useAddressBook = enabled) }
SignalStore.settings().isPreferSystemContactPhotos = enabled
refreshDebouncer.publish { ConversationUtil.refreshRecipientShortcuts() }
StorageSyncHelper.scheduleSyncForDataChange()
}
fun setUseSystemEmoji(enabled: Boolean) {
store.update { it.copy(useSystemEmoji = enabled) }
SignalStore.settings().isPreferSystemEmoji = enabled
}
fun setEnterKeySends(enabled: Boolean) {
store.update { it.copy(enterKeySends = enabled) }
SignalStore.settings().isEnterKeySends = enabled
}
class Factory(private val repository: ChatsSettingsRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(ChatsSettingsViewModel(repository)))
}
}
}

View File

@@ -0,0 +1,90 @@
package org.thoughtcrime.securesms.components.settings.app.chats.sms
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.provider.Settings
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.util.SmsUtil
private const val SMS_REQUEST_CODE: Short = 1234
class SmsSettingsFragment : DSLSettingsFragment(R.string.preferences__sms_mms) {
private lateinit var viewModel: SmsSettingsViewModel
override fun onResume() {
super.onResume()
viewModel.checkSmsEnabled()
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
viewModel = ViewModelProviders.of(this)[SmsSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(state: SmsSettingsState): DSLConfiguration {
return configure {
clickPref(
title = DSLSettingsText.from(R.string.SmsSettingsFragment__use_as_default_sms_app),
summary = DSLSettingsText.from(if (state.useAsDefaultSmsApp) R.string.arrays__enabled else R.string.arrays__disabled),
onClick = {
if (state.useAsDefaultSmsApp) {
startDefaultAppSelectionIntent()
} else {
startActivityForResult(SmsUtil.getSmsRoleIntent(requireContext()), SMS_REQUEST_CODE.toInt())
}
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__sms_delivery_reports),
summary = DSLSettingsText.from(R.string.preferences__request_a_delivery_report_for_each_sms_message_you_send),
isChecked = state.smsDeliveryReportsEnabled,
onClick = {
viewModel.setSmsDeliveryReportsEnabled(!state.smsDeliveryReportsEnabled)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__support_wifi_calling),
summary = DSLSettingsText.from(R.string.preferences__enable_if_your_device_supports_sms_mms_delivery_over_wifi),
isChecked = state.wifiCallingCompatibilityEnabled,
onClick = {
viewModel.setWifiCallingCompatibilityEnabled(!state.wifiCallingCompatibilityEnabled)
}
)
if (Build.VERSION.SDK_INT < 21) {
clickPref(
title = DSLSettingsText.from(R.string.preferences__advanced_mms_access_point_names),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_smsSettingsFragment_to_mmsPreferencesFragment)
}
)
}
}
}
// Linter isn't smart enough to figure out the else only happens if API >= 24
@SuppressLint("InlinedApi")
private fun startDefaultAppSelectionIntent() {
startActivity(
when {
Build.VERSION.SDK_INT < 23 -> Intent(Settings.ACTION_WIRELESS_SETTINGS)
Build.VERSION.SDK_INT < 24 -> Intent(Settings.ACTION_SETTINGS)
else -> Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
}
)
}
}

View File

@@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.components.settings.app.chats.sms
data class SmsSettingsState(
val useAsDefaultSmsApp: Boolean,
val smsDeliveryReportsEnabled: Boolean,
val wifiCallingCompatibilityEnabled: Boolean
)

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.components.settings.app.chats.sms
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.livedata.Store
class SmsSettingsViewModel : ViewModel() {
private val store = Store(
SmsSettingsState(
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()),
smsDeliveryReportsEnabled = SignalStore.settings().isSmsDeliveryReportsEnabled,
wifiCallingCompatibilityEnabled = SignalStore.settings().isWifiCallingCompatibilityModeEnabled
)
)
val state: LiveData<SmsSettingsState> = store.stateLiveData
fun setSmsDeliveryReportsEnabled(enabled: Boolean) {
store.update { it.copy(smsDeliveryReportsEnabled = enabled) }
SignalStore.settings().isSmsDeliveryReportsEnabled = enabled
}
fun setWifiCallingCompatibilityEnabled(enabled: Boolean) {
store.update { it.copy(wifiCallingCompatibilityEnabled = enabled) }
SignalStore.settings().isWifiCallingCompatibilityModeEnabled = enabled
}
fun checkSmsEnabled() {
store.update { it.copy(useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication())) }
}
}

View File

@@ -0,0 +1,115 @@
package org.thoughtcrime.securesms.components.settings.app.data
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import androidx.preference.PreferenceManager
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
import kotlin.math.abs
class DataAndStorageSettingsFragment : DSLSettingsFragment(R.string.preferences__data_and_storage) {
private val autoDownloadValues by lazy { resources.getStringArray(R.array.pref_media_download_entries) }
private val autoDownloadLabels by lazy { resources.getStringArray(R.array.pref_media_download_values) }
private val callBandwidthLabels by lazy { resources.getStringArray(R.array.pref_data_and_storage_call_bandwidth_values) }
private lateinit var viewModel: DataAndStorageSettingsViewModel
override fun onResume() {
super.onResume()
viewModel.refresh()
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val repository = DataAndStorageSettingsRepository()
val factory = DataAndStorageSettingsViewModel.Factory(preferences, repository)
viewModel = ViewModelProviders.of(this, factory)[DataAndStorageSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
fun getConfiguration(state: DataAndStorageSettingsState): DSLConfiguration {
return configure {
clickPref(
title = DSLSettingsText.from(R.string.preferences_data_and_storage__manage_storage),
summary = DSLSettingsText.from(Util.getPrettyFileSize(state.totalStorageUse)),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_dataAndStorageSettingsFragment_to_storagePreferenceFragment)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences_chats__media_auto_download)
multiSelectPref(
title = DSLSettingsText.from(R.string.preferences_chats__when_using_mobile_data),
listItems = autoDownloadLabels,
selected = autoDownloadValues.map { state.mobileAutoDownloadValues.contains(it) }.toBooleanArray(),
onSelected = {
val resultSet = it.mapIndexed { index, selected -> if (selected) autoDownloadValues[index] else null }.filterNotNull().toSet()
viewModel.setMobileAutoDownloadValues(resultSet)
}
)
multiSelectPref(
title = DSLSettingsText.from(R.string.preferences_chats__when_using_wifi),
listItems = autoDownloadLabels,
selected = autoDownloadValues.map { state.wifiAutoDownloadValues.contains(it) }.toBooleanArray(),
onSelected = {
val resultSet = it.mapIndexed { index, selected -> if (selected) autoDownloadValues[index] else null }.filterNotNull().toSet()
viewModel.setWifiAutoDownloadValues(resultSet)
}
)
multiSelectPref(
title = DSLSettingsText.from(R.string.preferences_chats__when_roaming),
listItems = autoDownloadLabels,
selected = autoDownloadValues.map { state.roamingAutoDownloadValues.contains(it) }.toBooleanArray(),
onSelected = {
val resultSet = it.mapIndexed { index, selected -> if (selected) autoDownloadValues[index] else null }.filterNotNull().toSet()
viewModel.setRoamingAutoDownloadValues(resultSet)
}
)
dividerPref()
sectionHeaderPref(R.string.DataAndStorageSettingsFragment__calls)
radioListPref(
title = DSLSettingsText.from(R.string.preferences_data_and_storage__use_less_data_for_calls),
listItems = callBandwidthLabels,
selected = abs(state.callBandwidthMode.code - 2),
onSelected = {
viewModel.setCallBandwidthMode(CallBandwidthMode.fromCode(abs(it - 2)))
}
)
textPref(
summary = DSLSettingsText.from(R.string.preference_data_and_storage__using_less_data_may_improve_calls_on_bad_networks)
)
dividerPref()
sectionHeaderPref(R.string.preferences_proxy)
clickPref(
title = DSLSettingsText.from(R.string.preferences_use_proxy),
summary = DSLSettingsText.from(if (state.isProxyEnabled) R.string.preferences_on else R.string.preferences_off),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_dataAndStorageSettingsFragment_to_editProxyFragment)
}
)
}
}
}

View File

@@ -0,0 +1,19 @@
package org.thoughtcrime.securesms.components.settings.app.data
import android.content.Context
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
class DataAndStorageSettingsRepository {
private val context: Context = ApplicationDependencies.getApplication()
fun getTotalStorageUse(consumer: (Long) -> Unit) {
SignalExecutors.BOUNDED.execute {
val breakdown = DatabaseFactory.getMediaDatabase(context).storageBreakdown
consumer(listOf(breakdown.audioSize, breakdown.documentSize, breakdown.photoSize, breakdown.videoSize).sum())
}
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.data
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
data class DataAndStorageSettingsState(
val totalStorageUse: Long,
val mobileAutoDownloadValues: Set<String>,
val wifiAutoDownloadValues: Set<String>,
val roamingAutoDownloadValues: Set<String>,
val callBandwidthMode: CallBandwidthMode,
val isProxyEnabled: Boolean
)

View File

@@ -0,0 +1,77 @@
package org.thoughtcrime.securesms.components.settings.app.data
import android.content.SharedPreferences
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
class DataAndStorageSettingsViewModel(
private val sharedPreferences: SharedPreferences,
private val repository: DataAndStorageSettingsRepository
) : ViewModel() {
private val store = Store(getState())
val state: LiveData<DataAndStorageSettingsState> = store.stateLiveData
fun refresh() {
repository.getTotalStorageUse { totalStorageUse ->
store.update { getState().copy(totalStorageUse = totalStorageUse) }
}
}
fun setMobileAutoDownloadValues(resultSet: Set<String>) {
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF, resultSet).apply()
getStateAndCopyStorageUsage()
}
fun setWifiAutoDownloadValues(resultSet: Set<String>) {
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF, resultSet).apply()
getStateAndCopyStorageUsage()
}
fun setRoamingAutoDownloadValues(resultSet: Set<String>) {
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF, resultSet).apply()
getStateAndCopyStorageUsage()
}
fun setCallBandwidthMode(callBandwidthMode: CallBandwidthMode) {
SignalStore.settings().callBandwidthMode = callBandwidthMode
ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate()
getStateAndCopyStorageUsage()
}
private fun getStateAndCopyStorageUsage() {
store.update { getState().copy(totalStorageUse = it.totalStorageUse) }
}
private fun getState() = DataAndStorageSettingsState(
totalStorageUse = 0,
mobileAutoDownloadValues = TextSecurePreferences.getMobileMediaDownloadAllowed(
ApplicationDependencies.getApplication()
),
wifiAutoDownloadValues = TextSecurePreferences.getWifiMediaDownloadAllowed(
ApplicationDependencies.getApplication()
),
roamingAutoDownloadValues = TextSecurePreferences.getRoamingMediaDownloadAllowed(
ApplicationDependencies.getApplication()
),
callBandwidthMode = SignalStore.settings().callBandwidthMode,
isProxyEnabled = SignalStore.proxy().isProxyEnabled
)
class Factory(
private val sharedPreferences: SharedPreferences,
private val repository: DataAndStorageSettingsRepository
) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(DataAndStorageSettingsViewModel(sharedPreferences, repository)))
}
}
}

View File

@@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.components.settings.app.help
import android.view.MenuItem
import androidx.navigation.Navigation
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
class HelpSettingsFragment : DSLSettingsFragment(R.string.preferences__help, R.menu.help_settings) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.action_submit_debug_log) {
Navigation.findNavController(requireView()).navigate(R.id.action_helpSettingsFragment_to_submitDebugLogActivity)
true
} else {
false
}
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
adapter.submitList(getConfiguration().toMappingModelList())
}
fun getConfiguration(): DSLConfiguration {
return configure {
externalLinkPref(
title = DSLSettingsText.from(R.string.HelpSettingsFragment__support_center),
linkId = R.string.support_center_url
)
clickPref(
title = DSLSettingsText.from(R.string.HelpSettingsFragment__contact_us),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_helpSettingsFragment_to_helpFragment)
}
)
dividerPref()
textPref(
title = DSLSettingsText.from(R.string.HelpSettingsFragment__version),
summary = DSLSettingsText.from(BuildConfig.VERSION_NAME)
)
externalLinkPref(
title = DSLSettingsText.from(R.string.HelpSettingsFragment__terms_amp_privacy_policy),
linkId = R.string.terms_and_privacy_policy_url
)
textPref(
summary = DSLSettingsText.from(
StringBuilder().apply {
append(getString(R.string.HelpFragment__copyright_signal_messenger))
append("\n")
append(getString(R.string.HelpFragment__licenced_under_the_gplv3))
}
)
)
}
}
}

View File

@@ -0,0 +1,281 @@
package org.thoughtcrime.securesms.components.settings.app.internal
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
import org.thoughtcrime.securesms.payments.DataExportUtil
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.concurrent.SimpleTask
class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__internal_preferences) {
private lateinit var viewModel: InternalSettingsViewModel
override fun bindAdapter(adapter: DSLSettingsAdapter) {
val repository = InternalSettingsRepository(requireContext())
val factory = InternalSettingsViewModel.Factory(repository)
viewModel = ViewModelProviders.of(this, factory)[InternalSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(state: InternalSettingsState): DSLConfiguration {
return configure {
sectionHeaderPref(R.string.preferences__internal_payments)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_payment_copy_data),
summary = DSLSettingsText.from(R.string.preferences__internal_payment_copy_data_description),
onClick = {
copyPaymentsDataToClipboard()
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_account)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_refresh_attributes),
summary = DSLSettingsText.from(R.string.preferences__internal_refresh_attributes_description),
onClick = {
refreshAttributes()
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_rotate_profile_key),
summary = DSLSettingsText.from(R.string.preferences__internal_rotate_profile_key_description),
onClick = {
rotateProfileKey()
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_refresh_remote_values),
summary = DSLSettingsText.from(R.string.preferences__internal_refresh_remote_values_description),
onClick = {
refreshRemoteValues()
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_display)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_user_details),
summary = DSLSettingsText.from(R.string.preferences__internal_user_details_description),
isChecked = state.seeMoreUserDetails,
onClick = {
viewModel.setSeeMoreUserDetails(!state.seeMoreUserDetails)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_storage_service)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_force_storage_service_sync),
summary = DSLSettingsText.from(R.string.preferences__internal_force_storage_service_sync_description),
onClick = {
forceStorageServiceSync()
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v2)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2),
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2_description),
isChecked = state.gv2doNotCreateGv2Groups,
onClick = {
viewModel.setGv2DoNotCreateGv2Groups(!state.gv2doNotCreateGv2Groups)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites),
summary = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites_description),
isChecked = state.gv2forceInvites,
onClick = {
viewModel.setGv2ForceInvites(!state.gv2forceInvites)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes),
summary = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes_description),
isChecked = state.gv2ignoreServerChanges,
onClick = {
viewModel.setGv2IgnoreServerChanges(!state.gv2ignoreServerChanges)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_p2p_changes),
summary = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes_description),
isChecked = state.gv2ignoreP2PChanges,
onClick = {
viewModel.setGv2IgnoreP2PChanges(!state.gv2ignoreP2PChanges)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v1_migration)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate),
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate_description),
isChecked = state.disableAutoMigrationInitiation,
onClick = {
viewModel.setDisableAutoMigrationInitiation(!state.disableAutoMigrationInitiation)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate),
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate_description),
isChecked = state.disableAutoMigrationNotification,
onClick = {
viewModel.setDisableAutoMigrationNotification(!state.disableAutoMigrationNotification)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_network)
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_force_censorship),
summary = DSLSettingsText.from(R.string.preferences__internal_force_censorship_description),
isChecked = state.forceCensorship,
onClick = {
viewModel.setDisableAutoMigrationNotification(!state.forceCensorship)
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_conversations_and_shortcuts)
clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_delete_all_dynamic_shortcuts),
summary = DSLSettingsText.from(R.string.preferences__internal_click_to_delete_all_dynamic_shortcuts),
onClick = {
deleteAllDynamicShortcuts()
}
)
dividerPref()
sectionHeaderPref(R.string.preferences__internal_emoji)
val emojiSummary = if (state.emojiVersion == null) {
getString(R.string.preferences__internal_use_built_in_emoji_set)
} else {
getString(
R.string.preferences__internal_current_version_d_at_density_s,
state.emojiVersion.version,
state.emojiVersion.density
)
}
switchPref(
title = DSLSettingsText.from(R.string.preferences__internal_use_built_in_emoji_set),
summary = DSLSettingsText.from(emojiSummary),
isChecked = state.useBuiltInEmojiSet,
onClick = {
viewModel.setDisableAutoMigrationNotification(!state.useBuiltInEmojiSet)
}
)
}
}
private fun copyPaymentsDataToClipboard() {
MaterialAlertDialogBuilder(requireContext())
.setMessage(
"""
Local payments history will be copied to the clipboard.
It may therefore compromise privacy.
However, no private keys will be copied.
""".trimIndent()
)
.setPositiveButton(
"Copy"
) { _: DialogInterface?, _: Int ->
SimpleTask.run<Any?>(
SignalExecutors.UNBOUNDED,
{
val context: Context = ApplicationDependencies.getApplication()
val clipboard =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val tsv = DataExportUtil.createTsv()
val clip = ClipData.newPlainText(context.getString(R.string.app_name), tsv)
clipboard.setPrimaryClip(clip)
null
},
{
Toast.makeText(
context,
"Payments have been copied",
Toast.LENGTH_SHORT
).show()
}
)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun refreshAttributes() {
ApplicationDependencies.getJobManager()
.startChain(RefreshAttributesJob())
.then(RefreshOwnProfileJob())
.enqueue()
Toast.makeText(context, "Scheduled attribute refresh", Toast.LENGTH_SHORT).show()
}
private fun rotateProfileKey() {
ApplicationDependencies.getJobManager().add(RotateProfileKeyJob())
Toast.makeText(context, "Scheduled profile key rotation", Toast.LENGTH_SHORT).show()
}
private fun refreshRemoteValues() {
ApplicationDependencies.getJobManager().add(RemoteConfigRefreshJob())
Toast.makeText(context, "Scheduled remote config refresh", Toast.LENGTH_SHORT).show()
}
private fun forceStorageServiceSync() {
ApplicationDependencies.getJobManager().add(StorageForcePushJob())
Toast.makeText(context, "Scheduled storage force push", Toast.LENGTH_SHORT).show()
}
private fun deleteAllDynamicShortcuts() {
ConversationUtil.clearAllShortcuts(requireContext())
Toast.makeText(context, "Deleted all dynamic shortcuts.", Toast.LENGTH_SHORT).show()
}
}

View File

@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.components.settings.app.internal
import android.content.Context
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.emoji.EmojiFiles
class InternalSettingsRepository(context: Context) {
private val context = context.applicationContext
fun getEmojiVersionInfo(consumer: (EmojiFiles.Version?) -> Unit) {
SignalExecutors.BOUNDED.execute {
consumer(EmojiFiles.Version.readVersion(context))
}
}
}

View File

@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.components.settings.app.internal
import org.thoughtcrime.securesms.emoji.EmojiFiles
data class InternalSettingsState(
val seeMoreUserDetails: Boolean,
val gv2doNotCreateGv2Groups: Boolean,
val gv2forceInvites: Boolean,
val gv2ignoreServerChanges: Boolean,
val gv2ignoreP2PChanges: Boolean,
val disableAutoMigrationInitiation: Boolean,
val disableAutoMigrationNotification: Boolean,
val forceCensorship: Boolean,
val useBuiltInEmojiSet: Boolean,
val emojiVersion: EmojiFiles.Version?
)

View File

@@ -0,0 +1,90 @@
package org.thoughtcrime.securesms.components.settings.app.internal
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.keyvalue.InternalValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.livedata.Store
class InternalSettingsViewModel(private val repository: InternalSettingsRepository) : ViewModel() {
private val preferenceDataStore = SignalStore.getPreferenceDataStore()
private val store = Store(getState())
init {
repository.getEmojiVersionInfo { version ->
store.update { it.copy(emojiVersion = version) }
}
}
val state: LiveData<InternalSettingsState> = store.stateLiveData
fun setSeeMoreUserDetails(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.RECIPIENT_DETAILS, enabled)
refresh()
}
fun setGv2DoNotCreateGv2Groups(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_DO_NOT_CREATE_GV2, enabled)
refresh()
}
fun setGv2ForceInvites(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_FORCE_INVITES, enabled)
refresh()
}
fun setGv2IgnoreServerChanges(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_IGNORE_SERVER_CHANGES, enabled)
refresh()
}
fun setGv2IgnoreP2PChanges(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_IGNORE_P2P_CHANGES, enabled)
refresh()
}
fun setDisableAutoMigrationInitiation(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_INITIATION, enabled)
refresh()
}
fun setDisableAutoMigrationNotification(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_NOTIFICATION, enabled)
refresh()
}
fun setForceCensorship(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.FORCE_CENSORSHIP, enabled)
refresh()
}
fun setUseBuiltInEmoji(enabled: Boolean) {
preferenceDataStore.putBoolean(InternalValues.FORCE_BUILT_IN_EMOJI, enabled)
refresh()
}
private fun refresh() {
store.update { getState().copy(emojiVersion = it.emojiVersion) }
}
private fun getState() = InternalSettingsState(
seeMoreUserDetails = SignalStore.internalValues().recipientDetails(),
gv2doNotCreateGv2Groups = SignalStore.internalValues().gv2DoNotCreateGv2Groups(),
gv2forceInvites = SignalStore.internalValues().gv2ForceInvites(),
gv2ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges(),
gv2ignoreP2PChanges = SignalStore.internalValues().gv2IgnoreP2PChanges(),
disableAutoMigrationInitiation = SignalStore.internalValues().disableGv1AutoMigrateInitiation(),
disableAutoMigrationNotification = SignalStore.internalValues().disableGv1AutoMigrateNotification(),
forceCensorship = SignalStore.internalValues().forcedCensorship(),
useBuiltInEmojiSet = SignalStore.internalValues().forceBuiltInEmoji(),
emojiVersion = null
)
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository)))
}
}
}

View File

@@ -0,0 +1,337 @@
package org.thoughtcrime.securesms.components.settings.app.notifications
import android.app.Activity
import android.content.Intent
import android.graphics.ColorFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.media.RingtoneManager
import android.net.Uri
import android.provider.Settings
import android.text.TextUtils
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.RadioListPreference
import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.RingtoneUtil
import org.thoughtcrime.securesms.util.ViewUtil
private const val MESSAGE_SOUND_SELECT: Int = 1
private const val CALL_RINGTONE_SELECT: Int = 2
class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__notifications) {
private val repeatAlertsValues by lazy { resources.getStringArray(R.array.pref_repeat_alerts_values) }
private val repeatAlertsLabels by lazy { resources.getStringArray(R.array.pref_repeat_alerts_entries) }
private val notificationPrivacyValues by lazy { resources.getStringArray(R.array.pref_notification_privacy_values) }
private val notificationPrivacyLabels by lazy { resources.getStringArray(R.array.pref_notification_privacy_entries) }
private val notificationPriorityValues by lazy { resources.getStringArray(R.array.pref_notification_priority_values) }
private val notificationPriorityLabels by lazy { resources.getStringArray(R.array.pref_notification_priority_entries) }
private val ledColorValues by lazy { resources.getStringArray(R.array.pref_led_color_values) }
private val ledColorLabels by lazy { resources.getStringArray(R.array.pref_led_color_entries) }
private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) }
private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) }
private lateinit var viewModel: NotificationsSettingsViewModel
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) {
val uri = data.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
viewModel.setMessageNotificationsSound(uri)
} else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) {
val uri = data.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
viewModel.setCallRingtone(uri)
}
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
adapter.registerFactory(
LedColorPreference::class.java,
MappingAdapter.LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val factory = NotificationsSettingsViewModel.Factory(sharedPreferences)
viewModel = ViewModelProviders.of(this, factory)[NotificationsSettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(state: NotificationsSettingsState): DSLConfiguration {
return configure {
sectionHeaderPref(R.string.NotificationsSettingsFragment__messages)
switchPref(
title = DSLSettingsText.from(R.string.preferences__notifications),
isChecked = state.messageNotificationsState.notificationsEnabled,
onClick = {
viewModel.setMessageNotificationsEnabled(!state.messageNotificationsState.notificationsEnabled)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__sound),
summary = DSLSettingsText.from(getRingtoneSummary(state.messageNotificationsState.sound)),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onClick = {
launchMessageSoundSelectionIntent()
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__vibrate),
isChecked = state.messageNotificationsState.vibrateEnabled,
isEnabled = state.messageNotificationsState.notificationsEnabled,
onClick = {
viewModel.setMessageNotificationVibration(!state.messageNotificationsState.vibrateEnabled)
}
)
customPref(
LedColorPreference(
colorValues = ledColorValues,
radioListPreference = RadioListPreference(
title = DSLSettingsText.from(R.string.preferences__led_color),
listItems = ledColorLabels,
selected = ledColorValues.indexOf(state.messageNotificationsState.ledColor),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onSelected = {
viewModel.setMessageNotificationLedColor(ledColorValues[it])
}
)
)
)
if (!NotificationChannels.supported()) {
radioListPref(
title = DSLSettingsText.from(R.string.preferences__pref_led_blink_title),
listItems = ledBlinkLabels,
selected = ledBlinkValues.indexOf(state.messageNotificationsState.ledBlink),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onSelected = {
viewModel.setMessageNotificationLedBlink(ledBlinkValues[it])
}
)
}
switchPref(
title = DSLSettingsText.from(R.string.preferences_notifications__in_chat_sounds),
isChecked = state.messageNotificationsState.inChatSoundsEnabled,
isEnabled = state.messageNotificationsState.notificationsEnabled,
onClick = {
viewModel.setMessageNotificationInChatSoundsEnabled(!state.messageNotificationsState.inChatSoundsEnabled)
}
)
radioListPref(
title = DSLSettingsText.from(R.string.preferences__repeat_alerts),
listItems = repeatAlertsLabels,
selected = repeatAlertsValues.indexOf(state.messageNotificationsState.repeatAlerts.toString()),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onSelected = {
viewModel.setMessageRepeatAlerts(repeatAlertsValues[it].toInt())
}
)
radioListPref(
title = DSLSettingsText.from(R.string.preferences_notifications__show),
listItems = notificationPrivacyLabels,
selected = notificationPrivacyValues.indexOf(state.messageNotificationsState.messagePrivacy),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onSelected = {
viewModel.setMessageNotificationPrivacy(notificationPrivacyValues[it])
}
)
if (NotificationChannels.supported()) {
clickPref(
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onClick = {
launchNotificationPriorityIntent()
}
)
} else {
radioListPref(
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
listItems = notificationPriorityLabels,
selected = notificationPriorityValues.indexOf(state.messageNotificationsState.priority.toString()),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onSelected = {
viewModel.setMessageNotificationPriority(notificationPriorityValues[it].toInt())
}
)
}
dividerPref()
sectionHeaderPref(R.string.NotificationsSettingsFragment__calls)
switchPref(
title = DSLSettingsText.from(R.string.preferences__notifications),
isChecked = state.callNotificationsState.notificationsEnabled,
onClick = {
viewModel.setCallNotificationsEnabled(!state.callNotificationsState.notificationsEnabled)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences_notifications__ringtone),
summary = DSLSettingsText.from(getRingtoneSummary(state.callNotificationsState.ringtone)),
isEnabled = state.callNotificationsState.notificationsEnabled,
onClick = {
launchCallRingtoneSelectionIntent()
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__vibrate),
isChecked = state.callNotificationsState.vibrateEnabled,
isEnabled = state.callNotificationsState.notificationsEnabled,
onClick = {
viewModel.setCallVibrateEnabled(!state.callNotificationsState.vibrateEnabled)
}
)
dividerPref()
sectionHeaderPref(R.string.NotificationsSettingsFragment__notify_when)
switchPref(
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__contact_joins_signal),
isChecked = state.notifyWhenContactJoinsSignal,
onClick = {
viewModel.setNotifyWhenContactJoinsSignal(!state.notifyWhenContactJoinsSignal)
}
)
}
}
private fun getRingtoneSummary(uri: Uri): String {
return if (TextUtils.isEmpty(uri.toString())) {
getString(R.string.preferences__silent)
} else {
val tone = RingtoneUtil.getRingtone(requireContext(), uri)
if (tone != null) {
tone.getTitle(requireContext())
} else {
getString(R.string.preferences__default)
}
}
}
private fun launchMessageSoundSelectionIntent() {
val current = SignalStore.settings().messageNotificationSound
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
intent.putExtra(
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
Settings.System.DEFAULT_NOTIFICATION_URI
)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
startActivityForResult(intent, MESSAGE_SOUND_SELECT)
}
@RequiresApi(26)
private fun launchNotificationPriorityIntent() {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID,
NotificationChannels.getMessagesChannel(requireContext())
)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
startActivity(intent)
}
private fun launchCallRingtoneSelectionIntent() {
val current = SignalStore.settings().callRingtone
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
intent.putExtra(
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
Settings.System.DEFAULT_RINGTONE_URI
)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
startActivityForResult(intent, CALL_RINGTONE_SELECT)
}
private class LedColorPreference(
val colorValues: Array<String>,
val radioListPreference: RadioListPreference
) : PreferenceModel<LedColorPreference>(
title = radioListPreference.title,
iconId = radioListPreference.iconId,
summary = radioListPreference.summary
) {
override fun areContentsTheSame(newItem: LedColorPreference): Boolean {
return super.areContentsTheSame(newItem) && radioListPreference.areContentsTheSame(newItem.radioListPreference)
}
}
private class LedColorPreferenceViewHolder(itemView: View) :
PreferenceViewHolder<LedColorPreference>(itemView) {
val radioListPreferenceViewHolder = RadioListPreferenceViewHolder(itemView)
override fun bind(model: LedColorPreference) {
super.bind(model)
radioListPreferenceViewHolder.bind(model.radioListPreference)
summaryView.visibility = View.GONE
val circleDrawable = requireNotNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable))
circleDrawable.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
circleDrawable.colorFilter = model.colorValues[model.radioListPreference.selected].toColorFilter()
if (titleView.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
titleView.setCompoundDrawables(null, null, circleDrawable, null)
} else {
titleView.setCompoundDrawables(circleDrawable, null, null, null)
}
}
private fun String.toColorFilter(): ColorFilter {
val color = when (this) {
"green" -> ContextCompat.getColor(context, R.color.green_500)
"red" -> ContextCompat.getColor(context, R.color.red_500)
"blue" -> ContextCompat.getColor(context, R.color.blue_500)
"yellow" -> ContextCompat.getColor(context, R.color.yellow_500)
"cyan" -> ContextCompat.getColor(context, R.color.cyan_500)
"magenta" -> ContextCompat.getColor(context, R.color.pink_500)
"white" -> ContextCompat.getColor(context, R.color.white)
else -> ContextCompat.getColor(context, R.color.transparent)
}
return PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
}
}
}

View File

@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.components.settings.app.notifications
import android.net.Uri
data class NotificationsSettingsState(
val messageNotificationsState: MessageNotificationsState,
val callNotificationsState: CallNotificationsState,
val notifyWhenContactJoinsSignal: Boolean
)
data class MessageNotificationsState(
val notificationsEnabled: Boolean,
val sound: Uri,
val vibrateEnabled: Boolean,
val ledColor: String,
val ledBlink: String,
val inChatSoundsEnabled: Boolean,
val repeatAlerts: Int,
val messagePrivacy: String,
val priority: Int
)
data class CallNotificationsState(
val notificationsEnabled: Boolean,
val ringtone: Uri,
val vibrateEnabled: Boolean
)

View File

@@ -0,0 +1,121 @@
package org.thoughtcrime.securesms.components.settings.app.notifications
import android.content.SharedPreferences
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
class NotificationsSettingsViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {
init {
if (NotificationChannels.supported()) {
SignalStore.settings().messageNotificationSound = NotificationChannels.getMessageRingtone(ApplicationDependencies.getApplication())
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getMessageVibrate(ApplicationDependencies.getApplication())
}
}
private val store = Store(getState())
val state: LiveData<NotificationsSettingsState> = store.stateLiveData
fun setMessageNotificationsEnabled(enabled: Boolean) {
SignalStore.settings().isMessageNotificationsEnabled = enabled
store.update { getState() }
}
fun setMessageNotificationsSound(sound: Uri?) {
SignalStore.settings().messageNotificationSound = sound ?: Uri.EMPTY
NotificationChannels.updateMessageRingtone(ApplicationDependencies.getApplication(), sound)
store.update { getState() }
}
fun setMessageNotificationVibration(enabled: Boolean) {
SignalStore.settings().isMessageVibrateEnabled = enabled
NotificationChannels.updateMessageVibrate(ApplicationDependencies.getApplication(), enabled)
store.update { getState() }
}
fun setMessageNotificationLedColor(color: String) {
SignalStore.settings().messageLedColor = color
NotificationChannels.updateMessagesLedColor(ApplicationDependencies.getApplication(), color)
store.update { getState() }
}
fun setMessageNotificationLedBlink(blink: String) {
SignalStore.settings().messageLedBlinkPattern = blink
store.update { getState() }
}
fun setMessageNotificationInChatSoundsEnabled(enabled: Boolean) {
SignalStore.settings().isMessageNotificationsInChatSoundsEnabled = enabled
store.update { getState() }
}
fun setMessageRepeatAlerts(repeats: Int) {
SignalStore.settings().messageNotificationsRepeatAlerts = repeats
store.update { getState() }
}
fun setMessageNotificationPrivacy(preference: String) {
SignalStore.settings().messageNotificationsPrivacy = NotificationPrivacyPreference(preference)
store.update { getState() }
}
fun setMessageNotificationPriority(priority: Int) {
sharedPreferences.edit().putInt(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF, priority).apply()
store.update { getState() }
}
fun setCallNotificationsEnabled(enabled: Boolean) {
SignalStore.settings().isCallNotificationsEnabled = enabled
store.update { getState() }
}
fun setCallRingtone(ringtone: Uri?) {
SignalStore.settings().callRingtone = ringtone ?: Uri.EMPTY
store.update { getState() }
}
fun setCallVibrateEnabled(enabled: Boolean) {
SignalStore.settings().isCallVibrateEnabled = enabled
store.update { getState() }
}
fun setNotifyWhenContactJoinsSignal(enabled: Boolean) {
SignalStore.settings().isNotifyWhenContactJoinsSignal = enabled
store.update { getState() }
}
private fun getState(): NotificationsSettingsState = NotificationsSettingsState(
messageNotificationsState = MessageNotificationsState(
notificationsEnabled = SignalStore.settings().isMessageNotificationsEnabled,
sound = SignalStore.settings().messageNotificationSound,
vibrateEnabled = SignalStore.settings().isMessageVibrateEnabled,
ledColor = SignalStore.settings().messageLedColor,
ledBlink = SignalStore.settings().messageLedBlinkPattern,
inChatSoundsEnabled = SignalStore.settings().isMessageNotificationsInChatSoundsEnabled,
repeatAlerts = SignalStore.settings().messageNotificationsRepeatAlerts,
messagePrivacy = SignalStore.settings().messageNotificationsPrivacy.toString(),
priority = TextSecurePreferences.getNotificationPriority(ApplicationDependencies.getApplication())
),
callNotificationsState = CallNotificationsState(
notificationsEnabled = SignalStore.settings().isCallNotificationsEnabled,
ringtone = SignalStore.settings().callRingtone,
vibrateEnabled = SignalStore.settings().isCallVibrateEnabled
),
notifyWhenContactJoinsSignal = SignalStore.settings().isNotifyWhenContactJoinsSignal
)
class Factory(private val sharedPreferences: SharedPreferences) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(NotificationsSettingsViewModel(sharedPreferences)))
}
}
}

View File

@@ -0,0 +1,391 @@
package org.thoughtcrime.securesms.components.settings.app.privacy
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.TextAppearanceSpan
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import mobi.upod.timedurationpicker.TimeDurationPicker
import mobi.upod.timedurationpicker.TimeDurationPickerDialog
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.PassphraseChangeActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.lang.Integer.max
import java.util.ArrayList
import java.util.LinkedHashMap
import java.util.Locale
import java.util.concurrent.TimeUnit
private val TAG = Log.tag(PrivacySettingsFragment::class.java)
class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privacy) {
private lateinit var viewModel: PrivacySettingsViewModel
private val incognitoSummary: CharSequence by lazy {
SpannableStringBuilder(getString(R.string.preferences__this_setting_is_not_a_guarantee))
.append(" ")
.append(
SpanUtil.learnMore(requireContext(), ContextCompat.getColor(requireContext(), R.color.signal_text_primary)) {
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.preferences__incognito_keyboard_learn_more))
}
)
}
override fun onResume() {
super.onResume()
viewModel.refreshBlockedCount()
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val repository = PrivacySettingsRepository()
val factory = PrivacySettingsViewModel.Factory(sharedPreferences, repository)
viewModel = ViewModelProviders.of(this, factory)[PrivacySettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) { state ->
adapter.submitList(getConfiguration(state).toMappingModelList())
}
}
private fun getConfiguration(state: PrivacySettingsState): DSLConfiguration {
return configure {
clickPref(
title = DSLSettingsText.from(R.string.PrivacySettingsFragment__blocked),
summary = DSLSettingsText.from(getString(R.string.PrivacySettingsFragment__d_contacts, state.blockedCount)),
onClick = {
Navigation.findNavController(requireView())
.navigate(R.id.action_privacySettingsFragment_to_blockedUsersActivity)
}
)
dividerPref()
if (FeatureFlags.phoneNumberPrivacy()) {
sectionHeaderPref(R.string.preferences_app_protection__who_can)
clickPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__see_my_phone_number),
summary = DSLSettingsText.from(getWhoCanSeeMyPhoneNumberSummary(state.seeMyPhoneNumber)),
onClick = {
onSeeMyPhoneNumberClicked(state.seeMyPhoneNumber)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__find_me_by_phone_number),
summary = DSLSettingsText.from(getWhoCanFindMeByPhoneNumberSummary(state.findMeByPhoneNumber)),
onClick = {
onFindMyPhoneNumberClicked(state.findMeByPhoneNumber)
}
)
dividerPref()
}
sectionHeaderPref(R.string.PrivacySettingsFragment__messaging)
switchPref(
title = DSLSettingsText.from(R.string.preferences__read_receipts),
summary = DSLSettingsText.from(R.string.preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts),
isChecked = state.readReceipts,
onClick = {
viewModel.setReadReceiptsEnabled(!state.readReceipts)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__typing_indicators),
summary = DSLSettingsText.from(R.string.preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators),
isChecked = state.typingIndicators,
onClick = {
viewModel.setTypingIndicatorsEnabled(!state.typingIndicators)
}
)
dividerPref()
sectionHeaderPref(R.string.PrivacySettingsFragment__app_security)
if (state.isObsoletePasswordEnabled) {
switchPref(
title = DSLSettingsText.from(R.string.preferences__enable_passphrase),
summary = DSLSettingsText.from(R.string.preferences__lock_signal_and_message_notifications_with_a_passphrase),
isChecked = true,
onClick = {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase)
setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications)
setIcon(R.drawable.ic_warning)
setPositiveButton(R.string.ApplicationPreferencesActivity_disable) { dialog, which ->
MasterSecretUtil.changeMasterSecretPassphrase(
activity,
KeyCachingService.getMasterSecret(context),
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
)
TextSecurePreferences.setPasswordDisabled(activity, true)
val intent = Intent(activity, KeyCachingService::class.java)
intent.action = KeyCachingService.DISABLE_ACTION
requireActivity().startService(intent)
viewModel.refresh()
}
setNegativeButton(android.R.string.cancel, null)
show()
}
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__change_passphrase),
summary = DSLSettingsText.from(R.string.preferences__change_your_passphrase),
onClick = {
if (MasterSecretUtil.isPassphraseInitialized(activity)) {
startActivity(Intent(activity, PassphraseChangeActivity::class.java))
} else {
Toast.makeText(
activity,
R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet,
Toast.LENGTH_LONG
).show()
}
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__inactivity_timeout_passphrase),
summary = DSLSettingsText.from(R.string.preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity),
isChecked = state.isObsoletePasswordTimeoutEnabled,
onClick = {
viewModel.setObsoletePasswordTimeoutEnabled(!state.isObsoletePasswordEnabled)
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences__inactivity_timeout_interval),
onClick = {
TimeDurationPickerDialog(
context,
{ _: TimeDurationPicker?, duration: Long ->
val timeoutMinutes = max(TimeUnit.MILLISECONDS.toMinutes(duration).toInt(), 1)
viewModel.setObsoletePasswordTimeout(timeoutMinutes)
},
0, TimeDurationPicker.HH_MM
).show()
}
)
} else {
val isKeyguardSecure = ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure
switchPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
summary = DSLSettingsText.from(R.string.preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint),
isChecked = state.screenLock && isKeyguardSecure,
isEnabled = isKeyguardSecure,
onClick = {
viewModel.setScreenLockEnabled(!state.screenLock)
val intent = Intent(requireContext(), KeyCachingService::class.java)
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
requireContext().startService(intent)
ConversationUtil.refreshRecipientShortcuts()
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock_inactivity_timeout),
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(state.screenLockActivityTimeout)),
isEnabled = isKeyguardSecure,
onClick = {
TimeDurationPickerDialog(
context,
{ _: TimeDurationPicker?, duration: Long ->
val timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration)
viewModel.setScreenLockTimeout(timeoutSeconds)
},
0, TimeDurationPicker.HH_MM
).show()
}
)
}
switchPref(
title = DSLSettingsText.from(R.string.preferences__screen_security),
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__block_screenshots_in_the_recents_list_and_inside_the_app),
isChecked = state.screenSecurity,
onClick = {
viewModel.setScreenSecurityEnabled(!state.screenSecurity)
}
)
switchPref(
title = DSLSettingsText.from(R.string.preferences__incognito_keyboard),
summary = DSLSettingsText.from(R.string.preferences__request_keyboard_to_disable),
isChecked = state.incognitoKeyboard,
onClick = {
viewModel.setIncognitoKeyboard(!state.incognitoKeyboard)
}
)
textPref(
summary = DSLSettingsText.from(incognitoSummary),
)
dividerPref()
clickPref(
title = DSLSettingsText.from(R.string.preferences__advanced),
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__signal_message_and_calls),
onClick = {
Navigation.findNavController(requireView()).navigate(R.id.action_privacySettingsFragment_to_advancedPrivacySettingsFragment)
}
)
}
}
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
val hours = TimeUnit.SECONDS.toHours(timeoutSeconds)
val minutes =
TimeUnit.SECONDS.toMinutes(timeoutSeconds) - TimeUnit.SECONDS.toHours(timeoutSeconds) * 60
return if (timeoutSeconds <= 0) {
getString(R.string.AppProtectionPreferenceFragment_none)
} else {
String.format(Locale.getDefault(), "%02d:%02d:00", hours, minutes)
}
}
@StringRes
private fun getWhoCanSeeMyPhoneNumberSummary(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode): Int {
return when (phoneNumberSharingMode) {
PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacy_everyone
PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS -> R.string.PhoneNumberPrivacy_my_contacts
PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacy_nobody
}
}
@StringRes
private fun getWhoCanFindMeByPhoneNumberSummary(phoneNumberListingMode: PhoneNumberListingMode): Int {
return when (phoneNumberListingMode) {
PhoneNumberListingMode.LISTED -> R.string.PhoneNumberPrivacy_everyone
PhoneNumberListingMode.UNLISTED -> R.string.PhoneNumberPrivacy_nobody
}
}
private fun onSeeMyPhoneNumberClicked(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
val value = arrayOf(phoneNumberSharingMode)
val items = items(requireContext())
val modes: List<PhoneNumberPrivacyValues.PhoneNumberSharingMode> = ArrayList(items.keys)
val modeStrings = items.values.toTypedArray()
val selectedMode = modes.indexOf(value[0])
MaterialAlertDialogBuilder(requireActivity()).apply {
setTitle(R.string.preferences_app_protection__see_my_phone_number)
setCancelable(true)
setSingleChoiceItems(
modeStrings,
selectedMode
) { _: DialogInterface?, which: Int -> value[0] = modes[which] }
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
val newSharingMode = value[0]
Log.i(
TAG,
String.format(
"PhoneNumberSharingMode changed to %s. Scheduling storage value sync",
newSharingMode
)
)
viewModel.setPhoneNumberSharingMode(value[0])
}
setNegativeButton(android.R.string.cancel, null)
show()
}
}
private fun items(context: Context): Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> {
val map: MutableMap<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> = LinkedHashMap()
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE] = titleAndDescription(
context,
context.getString(R.string.PhoneNumberPrivacy_everyone),
context.getString(R.string.PhoneNumberPrivacy_everyone_see_description)
)
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY] =
context.getString(R.string.PhoneNumberPrivacy_nobody)
return map
}
private fun titleAndDescription(
context: Context,
header: String,
description: String
): CharSequence {
return SpannableStringBuilder().apply {
append("\n")
append(header)
append("\n")
setSpan(
TextAppearanceSpan(context, android.R.style.TextAppearance_Small),
length,
length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
append(description)
append("\n")
}
}
fun onFindMyPhoneNumberClicked(phoneNumberListingMode: PhoneNumberListingMode) {
val context = requireContext()
val value = arrayOf(phoneNumberListingMode)
MaterialAlertDialogBuilder(requireActivity()).apply {
setTitle(R.string.preferences_app_protection__find_me_by_phone_number)
setCancelable(true)
setSingleChoiceItems(
arrayOf(
titleAndDescription(
context,
context.getString(R.string.PhoneNumberPrivacy_everyone),
context.getString(R.string.PhoneNumberPrivacy_everyone_find_description)
),
context.getString(R.string.PhoneNumberPrivacy_nobody)
),
value[0].ordinal
) { _: DialogInterface?, which: Int -> value[0] = PhoneNumberListingMode.values()[which] }
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
Log.i(
TAG,
String.format(
"PhoneNumberListingMode changed to %s. Scheduling storage value sync",
value[0]
)
)
viewModel.setPhoneNumberListingMode(value[0])
}
setNegativeButton(android.R.string.cancel, null)
show()
}
}
}

View File

@@ -0,0 +1,58 @@
package org.thoughtcrime.securesms.components.settings.app.privacy
import android.content.Context
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
class PrivacySettingsRepository {
private val context: Context = ApplicationDependencies.getApplication()
fun getBlockedCount(consumer: (Int) -> Unit) {
SignalExecutors.BOUNDED.execute {
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
consumer(recipientDatabase.blocked.count)
}
}
fun syncReadReceiptState() {
SignalExecutors.BOUNDED.execute {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.getJobManager().add(
MultiDeviceConfigurationUpdateJob(
TextSecurePreferences.isReadReceiptsEnabled(context),
TextSecurePreferences.isTypingIndicatorsEnabled(context),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
SignalStore.settings().isLinkPreviewsEnabled
)
)
}
}
fun syncTypingIndicatorsState() {
val enabled = TextSecurePreferences.isTypingIndicatorsEnabled(context)
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.getJobManager().add(
MultiDeviceConfigurationUpdateJob(
TextSecurePreferences.isReadReceiptsEnabled(context),
enabled,
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
SignalStore.settings().isLinkPreviewsEnabled
)
)
if (!enabled) {
ApplicationDependencies.getTypingStatusRepository().clear()
}
}
}

View File

@@ -0,0 +1,18 @@
package org.thoughtcrime.securesms.components.settings.app.privacy
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
data class PrivacySettingsState(
val blockedCount: Int,
val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode,
val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode,
val readReceipts: Boolean,
val typingIndicators: Boolean,
val screenLock: Boolean,
val screenLockActivityTimeout: Long,
val screenSecurity: Boolean,
val incognitoKeyboard: Boolean,
val isObsoletePasswordEnabled: Boolean,
val isObsoletePasswordTimeoutEnabled: Boolean,
val obsoletePasswordTimeout: Int
)

View File

@@ -0,0 +1,118 @@
package org.thoughtcrime.securesms.components.settings.app.privacy
import android.content.SharedPreferences
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
class PrivacySettingsViewModel(
private val sharedPreferences: SharedPreferences,
private val repository: PrivacySettingsRepository
) : ViewModel() {
private val store = Store(getState())
val state: LiveData<PrivacySettingsState> = store.stateLiveData
fun refreshBlockedCount() {
repository.getBlockedCount { count ->
store.update { it.copy(blockedCount = count) }
}
}
fun setReadReceiptsEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.READ_RECEIPTS_PREF, enabled).apply()
repository.syncReadReceiptState()
refresh()
}
fun setTypingIndicatorsEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.TYPING_INDICATORS, enabled).apply()
repository.syncTypingIndicatorsState()
refresh()
}
fun setScreenLockEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, enabled).apply()
refresh()
}
fun setScreenLockTimeout(seconds: Long) {
TextSecurePreferences.setScreenLockTimeout(ApplicationDependencies.getApplication(), seconds)
refresh()
}
fun setScreenSecurityEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, enabled).apply()
refresh()
}
fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode
StorageSyncHelper.scheduleSyncForDataChange()
refresh()
}
fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberPrivacyValues.PhoneNumberListingMode) {
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
refresh()
}
fun setIncognitoKeyboard(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply()
refresh()
}
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
refresh()
}
fun setObsoletePasswordTimeout(minutes: Int) {
TextSecurePreferences.setPassphraseTimeoutInterval(ApplicationDependencies.getApplication(), minutes)
refresh()
}
fun refresh() {
store.update(this::updateState)
}
private fun getState(): PrivacySettingsState {
return PrivacySettingsState(
blockedCount = 0,
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(ApplicationDependencies.getApplication()),
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(ApplicationDependencies.getApplication()),
screenLock = TextSecurePreferences.isScreenLockEnabled(ApplicationDependencies.getApplication()),
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(ApplicationDependencies.getApplication()),
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode,
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication())
)
}
private fun updateState(state: PrivacySettingsState): PrivacySettingsState {
return getState().copy(blockedCount = state.blockedCount)
}
class Factory(
private val sharedPreferences: SharedPreferences,
private val repository: PrivacySettingsRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(PrivacySettingsViewModel(sharedPreferences, repository)))
}
}
}

View File

@@ -0,0 +1,161 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
import android.app.ProgressDialog
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.text.SpannableStringBuilder
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.ViewUtil
class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__advanced) {
lateinit var viewModel: AdvancedPrivacySettingsViewModel
private val sealedSenderSummary: CharSequence by lazy {
SpanUtil.learnMore(
requireContext(),
ContextCompat.getColor(requireContext(), R.color.signal_text_primary)
) {
CommunicationActions.openBrowserLink(
requireContext(),
getString(R.string.AdvancedPrivacySettingsFragment__sealed_sender_link)
)
}
}
var progressDialog: ProgressDialog? = null
val statusIcon: CharSequence by lazy {
val unidentifiedDeliveryIcon = requireNotNull(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_unidentified_delivery
)
)
unidentifiedDeliveryIcon.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
val iconTint = ContextCompat.getColor(requireContext(), R.color.signal_text_primary_dialog)
unidentifiedDeliveryIcon.colorFilter = PorterDuffColorFilter(iconTint, PorterDuff.Mode.SRC_IN)
SpanUtil.buildImageSpan(unidentifiedDeliveryIcon)
}
override fun onResume() {
super.onResume()
viewModel.refresh()
}
override fun bindAdapter(adapter: DSLSettingsAdapter) {
val repository = AdvancedPrivacySettingsRepository(requireContext())
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val factory = AdvancedPrivacySettingsViewModel.Factory(preferences, repository)
viewModel = ViewModelProviders.of(this, factory)[AdvancedPrivacySettingsViewModel::class.java]
viewModel.state.observe(viewLifecycleOwner) {
if (it.showProgressSpinner) {
if (progressDialog?.isShowing == false) {
progressDialog = ProgressDialog.show(requireContext(), null, null, true)
}
} else {
progressDialog?.hide()
}
adapter.submitList(getConfiguration(it).toMappingModelList())
}
viewModel.events.observe(viewLifecycleOwner) {
if (it == AdvancedPrivacySettingsViewModel.Event.DISABLE_PUSH_FAILED) {
Toast.makeText(
requireContext(),
R.string.ApplicationPreferencesActivity_error_connecting_to_server,
Toast.LENGTH_LONG
).show()
}
}
}
private fun getConfiguration(state: AdvancedPrivacySettingsState): DSLConfiguration {
return configure {
switchPref(
title = DSLSettingsText.from(R.string.preferences__signal_messages_and_calls),
summary = DSLSettingsText.from(getPushToggleSummary(state.isPushEnabled)),
isChecked = state.isPushEnabled
) {
if (state.isPushEnabled) {
MaterialAlertDialogBuilder(requireContext()).apply {
setIcon(R.drawable.ic_info_outline)
setTitle(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls)
setMessage(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls_by_unregistering)
setNegativeButton(android.R.string.cancel, null)
setPositiveButton(
android.R.string.ok
) { _, _ -> viewModel.disablePushMessages() }
show()
}
} else {
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()))
}
}
switchPref(
title = DSLSettingsText.from(R.string.preferences_advanced__always_relay_calls),
summary = DSLSettingsText.from(R.string.preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address),
isChecked = state.alwaysRelayCalls
) {
viewModel.setAlwaysRelayCalls(!state.alwaysRelayCalls)
}
dividerPref()
sectionHeaderPref(R.string.preferences_communication__category_sealed_sender)
switchPref(
title = DSLSettingsText.from(
SpannableStringBuilder(getString(R.string.AdvancedPrivacySettingsFragment__show_status_icon))
.append(" ")
.append(statusIcon)
),
summary = DSLSettingsText.from(R.string.AdvancedPrivacySettingsFragment__show_an_icon),
isChecked = state.showSealedSenderStatusIcon
) {
viewModel.setShowStatusIconForSealedSender(!state.showSealedSenderStatusIcon)
}
switchPref(
title = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone),
summary = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone_description),
isChecked = state.allowSealedSenderFromAnyone
) {
viewModel.setAllowSealedSenderFromAnyone(!state.allowSealedSenderFromAnyone)
}
textPref(
summary = DSLSettingsText.from(sealedSenderSummary)
)
}
}
private fun getPushToggleSummary(isPushEnabled: Boolean): String {
return if (isPushEnabled) {
PhoneNumberFormatter.prettyPrint(TextSecurePreferences.getLocalNumber(requireContext()))
} else {
getString(R.string.preferences__free_private_messages_and_calls)
}
}
}

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
import android.content.Context
import com.google.firebase.iid.FirebaseInstanceId
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException
import java.io.IOException
private val TAG = Log.tag(AdvancedPrivacySettingsRepository::class.java)
class AdvancedPrivacySettingsRepository(private val context: Context) {
fun disablePushMessages(consumer: (DisablePushMessagesResult) -> Unit) {
SignalExecutors.BOUNDED.execute {
val result = try {
val accountManager = ApplicationDependencies.getSignalServiceAccountManager()
try {
accountManager.setGcmId(Optional.absent())
} catch (e: AuthorizationFailedException) {
Log.w(TAG, e)
}
if (!TextSecurePreferences.isFcmDisabled(context)) {
FirebaseInstanceId.getInstance().deleteInstanceId()
}
DisablePushMessagesResult.SUCCESS
} catch (ioe: IOException) {
Log.w(TAG, ioe)
DisablePushMessagesResult.NETWORK_ERROR
}
consumer(result)
}
}
fun syncShowSealedSenderIconState() {
SignalExecutors.BOUNDED.execute {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.getJobManager().add(
MultiDeviceConfigurationUpdateJob(
TextSecurePreferences.isReadReceiptsEnabled(context),
TextSecurePreferences.isTypingIndicatorsEnabled(context),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
SignalStore.settings().isLinkPreviewsEnabled
)
)
}
}
enum class DisablePushMessagesResult {
SUCCESS,
NETWORK_ERROR
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
data class AdvancedPrivacySettingsState(
val isPushEnabled: Boolean,
val alwaysRelayCalls: Boolean,
val showSealedSenderStatusIcon: Boolean,
val allowSealedSenderFromAnyone: Boolean,
val showProgressSpinner: Boolean
)

View File

@@ -0,0 +1,95 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
import android.content.SharedPreferences
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.SingleLiveEvent
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
class AdvancedPrivacySettingsViewModel(
private val sharedPreferences: SharedPreferences,
private val repository: AdvancedPrivacySettingsRepository
) : ViewModel() {
private val store = Store(getState())
private val singleEvents = SingleLiveEvent<Event>()
val state: LiveData<AdvancedPrivacySettingsState> = store.stateLiveData
val events: LiveData<Event> = singleEvents
fun disablePushMessages() {
store.update { getState().copy(showProgressSpinner = true) }
repository.disablePushMessages {
when (it) {
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> {
TextSecurePreferences.setPushRegistered(ApplicationDependencies.getApplication(), false)
SignalStore.registrationValues().clearRegistrationComplete()
}
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> {
singleEvents.postValue(Event.DISABLE_PUSH_FAILED)
}
}
store.update { getState().copy(showProgressSpinner = false) }
}
}
fun setAlwaysRelayCalls(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.ALWAYS_RELAY_CALLS_PREF, enabled).apply()
refresh()
}
fun setShowStatusIconForSealedSender(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled).apply()
repository.syncShowSealedSenderIconState()
refresh()
}
fun setAllowSealedSenderFromAnyone(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS, enabled).apply()
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
refresh()
}
fun refresh() {
store.update { getState().copy(showProgressSpinner = it.showProgressSpinner) }
}
private fun getState() = AdvancedPrivacySettingsState(
isPushEnabled = TextSecurePreferences.isPushRegistered(ApplicationDependencies.getApplication()),
alwaysRelayCalls = TextSecurePreferences.isTurnOnly(ApplicationDependencies.getApplication()),
showSealedSenderStatusIcon = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(
ApplicationDependencies.getApplication()
),
allowSealedSenderFromAnyone = TextSecurePreferences.isUniversalUnidentifiedAccess(
ApplicationDependencies.getApplication()
),
false
)
enum class Event {
DISABLE_PUSH_FAILED
}
class Factory(
private val sharedPreferences: SharedPreferences,
private val repository: AdvancedPrivacySettingsRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(
modelClass.cast(
AdvancedPrivacySettingsViewModel(
sharedPreferences,
repository
)
)
)
}
}
}

View File

@@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
/**
* Wraps a fragment to give it a Settings style toolbar. This class should be used sparingly, and
* is really only here as stop-gap as we migrate more settings screens to the new UI
*/
abstract class SettingsWrapperFragment : Fragment(R.layout.settings_wrapper_fragment) {
protected lateinit var toolbar: Toolbar
private set
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
toolbar = view.findViewById(R.id.toolbar)
toolbar.setNavigationOnClickListener {
onBackPressed()
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, OnBackPressed())
childFragmentManager
.beginTransaction()
.replace(R.id.wrapped_fragment, getFragment())
.commit()
}
abstract fun getFragment(): Fragment
fun setTitle(@StringRes titleId: Int) {
toolbar.setTitle(titleId)
}
private fun onBackPressed() {
if (!childFragmentManager.popBackStackImmediate()) {
requireActivity().onNavigateUp()
}
}
private inner class OnBackPressed : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.preferences.AdvancedPinPreferenceFragment
class WrappedAdvancedPinPreferenceFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.preferences__advanced_pin_settings)
return AdvancedPinPreferenceFragment()
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment
class WrappedBackupsPreferenceFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.BackupsPreferenceFragment__chat_backups)
return BackupsPreferenceFragment()
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.delete.DeleteAccountFragment
class WrappedDeleteAccountFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.preferences__delete_account)
return DeleteAccountFragment()
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.preferences.EditProxyFragment
class WrappedEditProxyFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.preferences_use_proxy)
return EditProxyFragment()
}
}

View File

@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.help.HelpFragment
class WrappedHelpFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.title = getString(R.string.preferences__help)
val fragment = HelpFragment()
fragment.arguments = arguments
return fragment
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.preferences.MmsPreferencesFragment
class WrappedMmsPreferencesFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.preferences__advanced_mms_access_point_names)
return MmsPreferencesFragment()
}
}

View File

@@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment
class WrappedStoragePreferenceFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
return StoragePreferenceFragment()
}
}

View File

@@ -0,0 +1,192 @@
package org.thoughtcrime.securesms.components.settings
import androidx.annotation.CallSuper
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.MappingModelList
private const val UNSET = -1
fun configure(init: DSLConfiguration.() -> Unit): DSLConfiguration {
val configuration = DSLConfiguration()
configuration.init()
return configuration
}
class DSLConfiguration {
private val children = arrayListOf<PreferenceModel<*>>()
fun customPref(customPreference: PreferenceModel<*>) {
children.add(customPreference)
}
fun radioListPref(
title: DSLSettingsText,
@DrawableRes iconId: Int = UNSET,
isEnabled: Boolean = true,
listItems: Array<String>,
selected: Int,
onSelected: (Int) -> Unit
) {
val preference = RadioListPreference(title, iconId, isEnabled, listItems, selected, onSelected)
children.add(preference)
}
fun multiSelectPref(
title: DSLSettingsText,
isEnabled: Boolean = true,
listItems: Array<String>,
selected: BooleanArray,
onSelected: (BooleanArray) -> Unit
) {
val preference = MultiSelectListPreference(title, isEnabled, listItems, selected, onSelected)
children.add(preference)
}
fun switchPref(
title: DSLSettingsText,
summary: DSLSettingsText? = null,
@DrawableRes iconId: Int = UNSET,
isEnabled: Boolean = true,
isChecked: Boolean,
onClick: () -> Unit
) {
val preference = SwitchPreference(title, summary, iconId, isEnabled, isChecked, onClick)
children.add(preference)
}
fun clickPref(
title: DSLSettingsText,
summary: DSLSettingsText? = null,
@DrawableRes iconId: Int = UNSET,
isEnabled: Boolean = true,
onClick: () -> Unit
) {
val preference = ClickPreference(title, summary, iconId, isEnabled, onClick)
children.add(preference)
}
fun externalLinkPref(
title: DSLSettingsText,
@DrawableRes iconId: Int = UNSET,
@StringRes linkId: Int
) {
val preference = ExternalLinkPreference(title, iconId, linkId)
children.add(preference)
}
fun dividerPref() {
val preference = DividerPreference()
children.add(preference)
}
fun sectionHeaderPref(title: DSLSettingsText) {
val preference = SectionHeaderPreference(title)
children.add(preference)
}
fun sectionHeaderPref(title: Int) {
val preference = SectionHeaderPreference(DSLSettingsText.from(title))
children.add(preference)
}
fun textPref(
title: DSLSettingsText? = null,
summary: DSLSettingsText? = null
) {
val preference = TextPreference(title, summary)
children.add(preference)
}
fun toMappingModelList(): MappingModelList = MappingModelList().apply { addAll(children) }
}
abstract class PreferenceModel<T : PreferenceModel<T>>(
open val title: DSLSettingsText? = null,
open val summary: DSLSettingsText? = null,
@DrawableRes open val iconId: Int = UNSET,
open val isEnabled: Boolean = true
) : MappingModel<T> {
override fun areItemsTheSame(newItem: T): Boolean {
return when {
title != null -> title == newItem.title
summary != null -> summary == newItem.summary
else -> throw AssertionError("Could not determine equality.")
}
}
@CallSuper
override fun areContentsTheSame(newItem: T): Boolean {
return areItemsTheSame(newItem) &&
newItem.summary == summary &&
newItem.iconId == iconId &&
newItem.isEnabled == isEnabled
}
}
class TextPreference(
title: DSLSettingsText?,
summary: DSLSettingsText?
) : PreferenceModel<TextPreference>(title = title, summary = summary)
class DividerPreference : PreferenceModel<DividerPreference>() {
override fun areItemsTheSame(newItem: DividerPreference) = false
}
class RadioListPreference(
override val title: DSLSettingsText,
@DrawableRes override val iconId: Int = UNSET,
override val isEnabled: Boolean,
val listItems: Array<String>,
val selected: Int,
val onSelected: (Int) -> Unit
) : PreferenceModel<RadioListPreference>(title = title, iconId = iconId, isEnabled = isEnabled) {
override fun areContentsTheSame(newItem: RadioListPreference): Boolean {
return super.areContentsTheSame(newItem) && listItems.contentEquals(newItem.listItems) && selected == newItem.selected
}
}
class MultiSelectListPreference(
override val title: DSLSettingsText,
override val isEnabled: Boolean,
val listItems: Array<String>,
val selected: BooleanArray,
val onSelected: (BooleanArray) -> Unit
) : PreferenceModel<MultiSelectListPreference>(title = title, isEnabled = isEnabled) {
override fun areContentsTheSame(newItem: MultiSelectListPreference): Boolean {
return super.areContentsTheSame(newItem) &&
listItems.contentEquals(newItem.listItems) &&
selected.contentEquals(newItem.selected)
}
}
class SwitchPreference(
override val title: DSLSettingsText,
override val summary: DSLSettingsText? = null,
@DrawableRes override val iconId: Int = UNSET,
isEnabled: Boolean,
val isChecked: Boolean,
val onClick: () -> Unit
) : PreferenceModel<SwitchPreference>(title = title, summary = summary, iconId = iconId, isEnabled = isEnabled) {
override fun areContentsTheSame(newItem: SwitchPreference): Boolean {
return super.areContentsTheSame(newItem) && isChecked == newItem.isChecked
}
}
class ClickPreference(
override val title: DSLSettingsText,
override val summary: DSLSettingsText?,
@DrawableRes override val iconId: Int,
isEnabled: Boolean,
val onClick: () -> Unit
) : PreferenceModel<ClickPreference>(title = title, summary = summary, iconId = iconId, isEnabled = isEnabled)
class ExternalLinkPreference(
override val title: DSLSettingsText,
@DrawableRes override val iconId: Int,
@StringRes val linkId: Int
) : PreferenceModel<ExternalLinkPreference>(title = title, iconId = iconId)
class SectionHeaderPreference(override val title: DSLSettingsText) : PreferenceModel<SectionHeaderPreference>(title = title)

View File

@@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
@@ -67,7 +68,7 @@ class VoiceNoteMediaDescriptionCompatFactory {
extras.putString(EXTRA_COLOR, threadRecipient.getColor().serialize());
extras.putLong(EXTRA_MESSAGE_ID, messageRecord.getId());
NotificationPrivacyPreference preference = TextSecurePreferences.getNotificationPrivacy(context);
NotificationPrivacyPreference preference = SignalStore.settings().getMessageNotificationsPrivacy();
String title;
if (preference.isDisplayContact() && threadRecipient.isGroup()) {

View File

@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -130,7 +131,7 @@ class VoiceNoteNotificationManager {
@Override
public @Nullable Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
if (!hasMetadata() || !TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact()) {
if (!hasMetadata() || !SignalStore.settings().getMessageNotificationsPrivacy().isDisplayContact()) {
cachedBitmap = null;
cachedRecipientId = null;
return null;

View File

@@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
@@ -57,7 +58,7 @@ public class TurnOffContactJoinedNotificationsActivity extends AppCompatActivity
List<MessageDatabase.MarkedMessageInfo> marked = threadDatabase.setRead(getIntent().getLongExtra(EXTRA_THREAD_ID, -1), false);
MarkReadReceiver.process(this, marked);
TextSecurePreferences.setNewContactsNotificationEnabled(this, false);
SignalStore.settings().setNotifyWhenContactJoinsSignal(false);
ApplicationDependencies.getMessageNotifier().updateNotification(this);
return null;

View File

@@ -463,7 +463,7 @@ public class DirectoryHelper {
private static void notifyNewUsers(@NonNull Context context,
@NonNull Collection<RecipientId> newUsers)
{
if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return;
if (!SignalStore.settings().isNotifyWhenContactJoinsSignal()) return;
for (RecipientId newUser: newUsers) {
Recipient recipient = Recipient.resolved(newUser);

View File

@@ -2184,7 +2184,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
stickerViewModel.getStickersAvailability().observe(this, stickersAvailable -> {
if (stickersAvailable == null) return;
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
boolean isSystemEmojiPreferred = SignalStore.settings().isPreferSystemEmoji();
MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this);
boolean stickerIntro = !TextSecurePreferences.hasSeenStickerIntroTooltip(this);
@@ -2631,7 +2631,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
boolean isSystemEmojiPreferred = SignalStore.settings().isPreferSystemEmoji();
if (stickersAvailable) {
if (isSystemEmojiPreferred) {
@@ -3277,7 +3277,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (TextSecurePreferences.isEnterSendsEnabled(ConversationActivity.this)) {
if (SignalStore.settings().isEnterKeySends()) {
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
return true;

View File

@@ -69,7 +69,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
@@ -81,6 +80,7 @@ import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact;
@@ -1568,10 +1568,7 @@ public class ConversationFragment extends LoggingFragment {
d.dismiss();
})
.setNeutralButton(R.string.ConversationFragment_contact_us, (d, w) -> {
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_HELP_FRAGMENT, true);
startActivity(intent);
startActivity(AppSettingsActivity.help(requireContext(), 0));
d.dismiss();
})
.show();

View File

@@ -651,7 +651,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
{
bodyText.setClickable(false);
bodyText.setFocusable(false);
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, SignalStore.settings().getMessageFontSize());
bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext()));
if (messageRecord.isRemoteDelete()) {

View File

@@ -26,6 +26,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.ConversationFragment;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -53,7 +54,7 @@ public final class EnableCallNotificationSettingsDialog extends DialogFragment {
public static void fixAutomatically(@NonNull Context context) {
if (areCallNotificationsDisabled(context)) {
TextSecurePreferences.setCallNotificationsEnabled(context, true);
SignalStore.settings().setCallNotificationsEnabled(true);
Toast.makeText(context, R.string.EnableCallNotificationSettingsDialog__call_notifications_enabled, Toast.LENGTH_SHORT).show();
}
}
@@ -198,7 +199,7 @@ public final class EnableCallNotificationSettingsDialog extends DialogFragment {
}
private static boolean areCallNotificationsDisabled(Context context) {
return !TextSecurePreferences.isCallNotificationsEnabled(context);
return !SignalStore.settings().isCallNotificationsEnabled();
}
private static boolean isCallChannelInvalid(Context context) {

View File

@@ -71,7 +71,6 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.MainFragment;
import org.thoughtcrime.securesms.MainNavigator;
import org.thoughtcrime.securesms.NewConversationActivity;
@@ -89,6 +88,7 @@ import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationFragment;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
@@ -132,6 +132,7 @@ import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
@@ -269,7 +270,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
updateReminders();
EventBus.getDefault().register(this);
if (TextSecurePreferences.isSmsEnabled(requireContext())) {
if (Util.isDefaultSmsProvider(requireContext())) {
InsightsLauncher.showInsightsModal(requireContext(), requireFragmentManager());
}
@@ -322,7 +323,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
@Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_insights).setVisible(TextSecurePreferences.isSmsEnabled(requireContext()));
menu.findItem(R.id.menu_insights).setVisible(Util.isDefaultSmsProvider(requireContext()));
menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(requireContext()));
}
@@ -947,10 +948,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
private void onProxyStatusClicked() {
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_PROXY_FRAGMENT, true);
startActivity(intent);
startActivity(AppSettingsActivity.proxy(requireContext()));
}
protected void onPostSubmitList(int conversationCount) {

View File

@@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
@@ -402,7 +403,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
Uri messageSoundUri = messageSound != null ? Uri.parse(messageSound) : null;
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate"));
String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, null, address);
boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1;
boolean vibrateEnabled = vibrateState == 0 ? SignalStore.settings().isMessageVibrateEnabled() :vibrateState == 1;
if (GroupId.isEncodedGroup(address)) {
try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address })) {

View File

@@ -31,7 +31,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.util.SpanUtil;
@@ -42,13 +41,13 @@ import org.whispersystems.libsignal.util.guava.Optional;
public class DeleteAccountFragment extends Fragment {
private ArrayAdapter<String> countrySpinnerAdapter;
private TextView bullets;
private LabeledEditText countryCode;
private LabeledEditText number;
private AsYouTypeFormatter countryFormatter;
private DeleteAccountViewModel viewModel;
private DialogInterface deletionProgressDialog;
private ArrayAdapter<String> countrySpinnerAdapter;
private TextView bullets;
private LabeledEditText countryCode;
private LabeledEditText number;
private AsYouTypeFormatter countryFormatter;
private DeleteAccountViewModel viewModel;
private DialogInterface deletionProgressDialog;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -57,8 +56,8 @@ public class DeleteAccountFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Spinner countrySpinner = view.findViewById(R.id.delete_account_fragment_country_spinner);
View confirm = view.findViewById(R.id.delete_account_fragment_delete);
Spinner countrySpinner = view.findViewById(R.id.delete_account_fragment_country_spinner);
View confirm = view.findViewById(R.id.delete_account_fragment_delete);
bullets = view.findViewById(R.id.delete_account_fragment_bullets);
countryCode = view.findViewById(R.id.delete_account_fragment_country_code);
@@ -80,12 +79,6 @@ public class DeleteAccountFragment extends Fragment {
initializeSpinner(countrySpinner);
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__delete_account);
}
private void updateBullets(@NonNull Optional<String> formattedBalance) {
bullets.setText(buildBulletsText(formattedBalance));
}

View File

@@ -11,8 +11,8 @@ import androidx.annotation.StringRes;
import androidx.navigation.fragment.NavHostFragment;
import org.signal.devicetransfer.DeviceToDeviceTransferService;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferSetupFragment;
import org.thoughtcrime.securesms.devicetransfer.SetupStep;
@@ -46,9 +46,7 @@ public final class OldDeviceTransferSetupFragment extends DeviceTransferSetupFra
@Override
protected void navigateWhenWifiDirectUnavailable() {
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_BACKUPS_FRAGMENT, true);
startActivity(intent);
startActivity(AppSettingsActivity.backups(requireContext()));
requireActivity().finish();
}

View File

@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker;
import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -29,7 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
@@ -56,8 +57,8 @@ public class CreateGroupActivity extends ContactSelectionActivity {
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.create_group_activity);
int displayMode = TextSecurePreferences.isSmsEnabled(context) ? ContactsCursorLoader.DisplayMode.FLAG_SMS | ContactsCursorLoader.DisplayMode.FLAG_PUSH
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
int displayMode = Util.isDefaultSmsProvider(context) ? ContactsCursorLoader.DisplayMode.FLAG_SMS | ContactsCursorLoader.DisplayMode.FLAG_PUSH
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, FeatureFlags.groupLimits().excludingSelf());

View File

@@ -24,7 +24,6 @@ import com.annimon.stream.Stream;
import com.dd.CircularProgressButton;
import org.signal.core.util.ResourceUtil;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
@@ -38,19 +37,19 @@ import java.util.List;
public class HelpFragment extends LoggingFragment {
public static final String START_CATEGORY_INDEX = "start.category.index";
public static final String START_CATEGORY_INDEX = "start_category_index";
public static final int PAYMENT_INDEX = 5;
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;
private Spinner categorySpinner;
private ArrayAdapter<CharSequence> categoryAdapter;
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;
private Spinner categorySpinner;
private ArrayAdapter<CharSequence> categoryAdapter;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -68,7 +67,6 @@ public class HelpFragment extends LoggingFragment {
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) requireActivity()).requireSupportActionBar().setTitle(R.string.preferences__help);
cancelSpinning(next);
problem.setEnabled(true);

View File

@@ -1,16 +1,24 @@
package org.thoughtcrime.securesms.keyvalue;
import android.net.Uri;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("deprecation")
public final class SettingsValues extends SignalStoreValues {
public static final String LINK_PREVIEWS = "settings.link_previews";
@@ -23,8 +31,31 @@ public final class SettingsValues extends SignalStoreValues {
private static final String CALL_BANDWIDTH_MODE = "settings.signal.call.bandwidth.mode";
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
public static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
public static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
public static final String THEME = "settings.theme";
public static final String MESSAGE_FONT_SIZE = "settings.message.font.size";
public static final String LANGUAGE = "settings.language";
public static final String PREFER_SYSTEM_EMOJI = "settings.use.system.emoji";
public static final String ENTER_KEY_SENDS = "settings.enter.key.sends";
public static final String BACKUPS_ENABLED = "settings.backups.enabled";
public static final String SMS_DELIVERY_REPORTS_ENABLED = "settings.sms.delivery.reports.enabled";
public static final String WIFI_CALLING_COMPATIBILITY_MODE_ENABLED = "settings.wifi.calling.compatibility.mode.enabled";
public static final String MESSAGE_NOTIFICATIONS_ENABLED = "settings.message.notifications.enabled";
public static final String MESSAGE_NOTIFICATION_SOUND = "settings.message.notifications.sound";
public static final String MESSAGE_VIBRATE_ENABLED = "settings.message.vibrate.enabled";
public static final String MESSAGE_LED_COLOR = "settings.message.led.color";
public static final String MESSAGE_LED_BLINK_PATTERN = "settings.message.led.blink";
public static final String MESSAGE_IN_CHAT_SOUNDS_ENABLED = "settings.message.in.chats.sounds.enabled";
public static final String MESSAGE_REPEAT_ALERTS = "settings.message.repeat.alerts";
public static final String MESSAGE_NOTIFICATION_PRIVACY = "settings.message.notification.privacy";
public static final String CALL_NOTIFICATIONS_ENABLED = "settings.call.notifications.enabled";
public static final String CALL_RINGTONE = "settings.call.ringtone";
public static final String CALL_VIBRATE_ENABLED = "settings.call.vibrate.enabled";
public static final String NOTIFY_WHEN_CONTACT_JOINS_SIGNAL = "settings.notify.when.contact.joins.signal";
private final SingleLiveEvent<String> onConfigurationSettingChanged = new SingleLiveEvent<>();
SettingsValues(@NonNull KeyValueStore store) {
super(store);
@@ -46,7 +77,29 @@ public final class SettingsValues extends SignalStoreValues {
PREFER_SYSTEM_CONTACT_PHOTOS,
CALL_BANDWIDTH_MODE,
THREAD_TRIM_LENGTH,
THREAD_TRIM_ENABLED);
THREAD_TRIM_ENABLED,
LANGUAGE,
THEME,
MESSAGE_FONT_SIZE,
PREFER_SYSTEM_EMOJI,
ENTER_KEY_SENDS,
BACKUPS_ENABLED,
MESSAGE_NOTIFICATIONS_ENABLED,
MESSAGE_NOTIFICATION_SOUND,
MESSAGE_VIBRATE_ENABLED,
MESSAGE_LED_COLOR,
MESSAGE_LED_BLINK_PATTERN,
MESSAGE_IN_CHAT_SOUNDS_ENABLED,
MESSAGE_REPEAT_ALERTS,
MESSAGE_NOTIFICATION_PRIVACY,
CALL_NOTIFICATIONS_ENABLED,
CALL_RINGTONE,
CALL_VIBRATE_ENABLED,
NOTIFY_WHEN_CONTACT_JOINS_SIGNAL);
}
public @NonNull LiveData<String> getOnConfigurationSettingChanged() {
return onConfigurationSettingChanged;
}
public boolean isLinkPreviewsEnabled() {
@@ -114,6 +167,181 @@ public final class SettingsValues extends SignalStoreValues {
return CallBandwidthMode.fromCode(getInteger(CALL_BANDWIDTH_MODE, CallBandwidthMode.HIGH_ALWAYS.getCode()));
}
public @NonNull String getTheme() {
return getString(THEME, TextSecurePreferences.getTheme(ApplicationDependencies.getApplication()));
}
public void setTheme(@NonNull String theme) {
putString(THEME, theme);
onConfigurationSettingChanged.postValue(THEME);
}
public int getMessageFontSize() {
return getInteger(MESSAGE_FONT_SIZE, TextSecurePreferences.getMessageBodyTextSize(ApplicationDependencies.getApplication()));
}
public void setMessageFontSize(int messageFontSize) {
putInteger(MESSAGE_FONT_SIZE, messageFontSize);
}
public @NonNull String getLanguage() {
return TextSecurePreferences.getLanguage(ApplicationDependencies.getApplication());
}
public void setLanguage(@NonNull String language) {
TextSecurePreferences.setLanguage(ApplicationDependencies.getApplication(), language);
onConfigurationSettingChanged.postValue(LANGUAGE);
}
public boolean isPreferSystemEmoji() {
return getBoolean(PREFER_SYSTEM_EMOJI, TextSecurePreferences.isSystemEmojiPreferred(ApplicationDependencies.getApplication()));
}
public void setPreferSystemEmoji(boolean useSystemEmoji) {
putBoolean(PREFER_SYSTEM_EMOJI, useSystemEmoji);
}
public boolean isEnterKeySends() {
return getBoolean(ENTER_KEY_SENDS, TextSecurePreferences.isEnterSendsEnabled(ApplicationDependencies.getApplication()));
}
public void setEnterKeySends(boolean enterKeySends) {
putBoolean(ENTER_KEY_SENDS, enterKeySends);
}
public boolean isBackupEnabled() {
return getBoolean(BACKUPS_ENABLED, TextSecurePreferences.isBackupEnabled(ApplicationDependencies.getApplication()));
}
public void setBackupEnabled(boolean backupEnabled) {
putBoolean(BACKUPS_ENABLED, backupEnabled);
}
public boolean isSmsDeliveryReportsEnabled() {
return getBoolean(SMS_DELIVERY_REPORTS_ENABLED, TextSecurePreferences.isSmsDeliveryReportsEnabled(ApplicationDependencies.getApplication()));
}
public void setSmsDeliveryReportsEnabled(boolean smsDeliveryReportsEnabled) {
putBoolean(SMS_DELIVERY_REPORTS_ENABLED, smsDeliveryReportsEnabled);
}
public boolean isWifiCallingCompatibilityModeEnabled() {
return getBoolean(WIFI_CALLING_COMPATIBILITY_MODE_ENABLED, TextSecurePreferences.isWifiSmsEnabled(ApplicationDependencies.getApplication()));
}
public void setWifiCallingCompatibilityModeEnabled(boolean wifiCallingCompatibilityModeEnabled) {
putBoolean(WIFI_CALLING_COMPATIBILITY_MODE_ENABLED, wifiCallingCompatibilityModeEnabled);
}
public void setMessageNotificationsEnabled(boolean messageNotificationsEnabled) {
putBoolean(MESSAGE_NOTIFICATIONS_ENABLED, messageNotificationsEnabled);
}
public boolean isMessageNotificationsEnabled() {
return getBoolean(MESSAGE_NOTIFICATIONS_ENABLED, TextSecurePreferences.isNotificationsEnabled(ApplicationDependencies.getApplication()));
}
public void setMessageNotificationSound(@NonNull Uri sound) {
putString(MESSAGE_NOTIFICATION_SOUND, sound.toString());
}
public @NonNull Uri getMessageNotificationSound() {
String result = getString(MESSAGE_NOTIFICATION_SOUND, TextSecurePreferences.getNotificationRingtone(ApplicationDependencies.getApplication()).toString());
if (result.startsWith("file:")) {
result = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
}
return Uri.parse(result);
}
public boolean isMessageVibrateEnabled() {
return getBoolean(MESSAGE_VIBRATE_ENABLED, TextSecurePreferences.isNotificationVibrateEnabled(ApplicationDependencies.getApplication()));
}
public void setMessageVibrateEnabled(boolean messageVibrateEnabled) {
putBoolean(MESSAGE_VIBRATE_ENABLED, messageVibrateEnabled);
}
public @NonNull String getMessageLedColor() {
return getString(MESSAGE_LED_COLOR, TextSecurePreferences.getNotificationLedColor(ApplicationDependencies.getApplication()));
}
public void setMessageLedColor(@NonNull String ledColor) {
putString(MESSAGE_LED_COLOR, ledColor);
}
public @NonNull String getMessageLedBlinkPattern() {
return getString(MESSAGE_LED_BLINK_PATTERN, TextSecurePreferences.getNotificationLedPattern(ApplicationDependencies.getApplication()));
}
public void setMessageLedBlinkPattern(@NonNull String blinkPattern) {
putString(MESSAGE_LED_BLINK_PATTERN, blinkPattern);
}
public boolean isMessageNotificationsInChatSoundsEnabled() {
return getBoolean(MESSAGE_IN_CHAT_SOUNDS_ENABLED, TextSecurePreferences.isInThreadNotifications(ApplicationDependencies.getApplication()));
}
public void setMessageNotificationsInChatSoundsEnabled(boolean inChatSoundsEnabled) {
putBoolean(MESSAGE_IN_CHAT_SOUNDS_ENABLED, inChatSoundsEnabled);
}
public int getMessageNotificationsRepeatAlerts() {
return getInteger(MESSAGE_REPEAT_ALERTS, TextSecurePreferences.getRepeatAlertsCount(ApplicationDependencies.getApplication()));
}
public void setMessageNotificationsRepeatAlerts(int count) {
putInteger(MESSAGE_REPEAT_ALERTS, count);
}
public @NonNull NotificationPrivacyPreference getMessageNotificationsPrivacy() {
return new NotificationPrivacyPreference(getString(MESSAGE_NOTIFICATION_PRIVACY, TextSecurePreferences.getNotificationPrivacy(ApplicationDependencies.getApplication()).toString()));
}
public void setMessageNotificationsPrivacy(@NonNull NotificationPrivacyPreference messageNotificationsPrivacy) {
putString(MESSAGE_NOTIFICATION_PRIVACY, messageNotificationsPrivacy.toString());
}
public boolean isCallNotificationsEnabled() {
return getBoolean(CALL_NOTIFICATIONS_ENABLED, TextSecurePreferences.isCallNotificationsEnabled(ApplicationDependencies.getApplication()));
}
public void setCallNotificationsEnabled(boolean callNotificationsEnabled) {
putBoolean(CALL_NOTIFICATIONS_ENABLED, callNotificationsEnabled);
}
public @NonNull Uri getCallRingtone() {
String result = getString(CALL_RINGTONE, TextSecurePreferences.getCallNotificationRingtone(ApplicationDependencies.getApplication()).toString());
if (result != null && result.startsWith("file:")) {
result = Settings.System.DEFAULT_RINGTONE_URI.toString();
}
return Uri.parse(result);
}
public void setCallRingtone(@NonNull Uri ringtone) {
putString(CALL_RINGTONE, ringtone.toString());
}
public boolean isCallVibrateEnabled() {
return getBoolean(CALL_VIBRATE_ENABLED, TextSecurePreferences.isCallNotificationVibrateEnabled(ApplicationDependencies.getApplication()));
}
public void setCallVibrateEnabled(boolean callVibrateEnabled) {
putBoolean(CALL_VIBRATE_ENABLED, callVibrateEnabled);
}
public boolean isNotifyWhenContactJoinsSignal() {
return getBoolean(NOTIFY_WHEN_CONTACT_JOINS_SIGNAL, TextSecurePreferences.isNewContactsNotificationEnabled(ApplicationDependencies.getApplication()));
}
public void setNotifyWhenContactJoinsSignal(boolean notifyWhenContactJoinsSignal) {
putBoolean(NOTIFY_WHEN_CONTACT_JOINS_SIGNAL, notifyWhenContactJoinsSignal);
}
private @Nullable Uri getUri(@NonNull String key) {
String uri = getString(key, "");

View File

@@ -7,8 +7,8 @@ import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationManagerCompat;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -23,12 +23,12 @@ final class LogSectionNotifications implements LogSection {
public @NonNull CharSequence getContent(@NonNull Context context) {
StringBuilder output = new StringBuilder();
output.append("Message notifications: ").append(TextSecurePreferences.isNotificationsEnabled(context)).append("\n")
.append("Call notifications : ").append(TextSecurePreferences.isCallNotificationsEnabled(context)).append("\n")
.append("New contact alerts : ").append(TextSecurePreferences.isNewContactsNotificationEnabled(context)).append("\n")
.append("In-chat sounds : ").append(TextSecurePreferences.isInThreadNotifications(context)).append("\n")
.append("Repeat alerts : ").append(TextSecurePreferences.getRepeatAlertsCount(context)).append("\n")
.append("Notification display : ").append(TextSecurePreferences.getNotificationPrivacy(context)).append("\n\n");
output.append("Message notifications: ").append(SignalStore.settings().isMessageNotificationsEnabled()).append("\n")
.append("Call notifications : ").append(SignalStore.settings().isCallNotificationsEnabled()).append("\n")
.append("New contact alerts : ").append(SignalStore.settings().isNotifyWhenContactJoinsSignal()).append("\n")
.append("In-chat sounds : ").append(SignalStore.settings().isMessageNotificationsInChatSoundsEnabled()).append("\n")
.append("Repeat alerts : ").append(SignalStore.settings().getMessageNotificationsRepeatAlerts()).append("\n")
.append("Notification display : ").append(SignalStore.settings().getMessageNotificationsPrivacy()).append("\n\n");
if (Build.VERSION.SDK_INT >= 26) {
NotificationManager manager = ServiceUtil.getNotificationManager(context);

View File

@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.components.ConversationItemFooter;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -144,7 +145,7 @@ public class LongMessageActivity extends PassphraseRequiredActivity {
bubble.setVisibility(View.VISIBLE);
text.setText(styledBody);
text.setMovementMethod(LinkMovementMethod.getInstance());
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(this));
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, SignalStore.settings().getMessageFontSize());
if (message.get().getMessageRecord().isOutgoing()) {
text.setMentionBackgroundTint(ContextCompat.getColor(this, isDarkTheme(this) ? R.color.core_grey_60 : R.color.core_grey_20));
} else {

View File

@@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.HudState;
import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.ViewOnceState;
@@ -348,7 +349,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
return isSend;
});
if (TextSecurePreferences.isSystemEmojiPreferred(this)) {
if (SignalStore.settings().isPreferSystemEmoji()) {
emojiToggle.setVisibility(View.GONE);
} else {
emojiToggle.setOnClickListener(this::onEmojiToggleClicked);
@@ -1038,7 +1039,7 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (TextSecurePreferences.isEnterSendsEnabled(getApplicationContext())) {
if (SignalStore.settings().isEnterKeySends()) {
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
return true;

View File

@@ -12,8 +12,8 @@ import com.annimon.stream.Stream;
import org.signal.core.util.TranslationDetection;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -293,9 +293,7 @@ public final class Megaphones {
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
controller.onMegaphoneNavigationRequested(intent);
} else {
Intent intent = new Intent(context, ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_NOTIFICATIONS_FRAGMENT, true);
controller.onMegaphoneNavigationRequested(intent);
controller.onMegaphoneNavigationRequested(AppSettingsActivity.notifications(context));
}
})
.setSecondaryButton(R.string.NotificationsMegaphone_not_now, (megaphone, controller) -> controller.onMegaphoneSnooze(Event.NOTIFICATIONS))
@@ -328,8 +326,8 @@ public final class Megaphones {
}
private static boolean shouldShowNotificationsMegaphone(@NonNull Context context) {
boolean shouldShow = !TextSecurePreferences.isNotificationsEnabled(context) ||
!NotificationChannels.isMessageChannelEnabled(context) ||
boolean shouldShow = !SignalStore.settings().isMessageNotificationsEnabled() ||
!NotificationChannels.isMessageChannelEnabled(context) ||
!NotificationChannels.isMessagesChannelGroupEnabled(context) ||
!NotificationChannels.areNotificationsEnabled(context);
if (shouldShow) {

View File

@@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.backup.BackupFileIOError;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -43,13 +44,13 @@ public final class BackupNotificationMigrationJob extends MigrationJob {
@Override
public void performMigration() {
if (Build.VERSION.SDK_INT >= 29 && !TextSecurePreferences.isBackupEnabled(context) && BackupUtil.hasBackupFiles(context)) {
if (Build.VERSION.SDK_INT >= 29 && !SignalStore.settings().isBackupEnabled() && BackupUtil.hasBackupFiles(context)) {
Log.w(TAG, "Stranded backup! Notifying.");
BackupFileIOError.UNKNOWN.postNotification(context);
} else {
Log.w(TAG, String.format(Locale.US, "Does not meet criteria. API: %d, BackupsEnabled: %s, HasFiles: %s",
Build.VERSION.SDK_INT,
TextSecurePreferences.isBackupEnabled(context),
SignalStore.settings().isBackupEnabled(),
BackupUtil.hasBackupFiles(context)));
}
}

View File

@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.NotificationIds;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -63,7 +64,7 @@ public class UserNotificationMigrationJob extends MigrationJob {
return;
}
if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) {
if (!SignalStore.settings().isNotifyWhenContactJoinsSignal()) {
Log.w(TAG, "New contact notifications disabled! Skipping.");
return;
}

View File

@@ -14,6 +14,7 @@ import androidx.core.app.NotificationCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -49,8 +50,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
}
public void setAlarms(@Nullable Uri ringtone, RecipientDatabase.VibrateState vibrate) {
Uri defaultRingtone = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : TextSecurePreferences.getNotificationRingtone(context);
boolean defaultVibrate = NotificationChannels.supported() ? NotificationChannels.getMessageVibrate(context) : TextSecurePreferences.isNotificationVibrateEnabled(context);
Uri defaultRingtone = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : SignalStore.settings().getMessageNotificationSound();
boolean defaultVibrate = NotificationChannels.supported() ? NotificationChannels.getMessageVibrate(context) : SignalStore.settings().isMessageVibrateEnabled();
if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) setSound(defaultRingtone);
else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone);
@@ -63,8 +64,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
}
private void setLed() {
String ledColor = TextSecurePreferences.getNotificationLedColor(context);
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
String ledColor = SignalStore.settings().getMessageLedColor();
String ledBlinkPattern = SignalStore.settings().getMessageLedBlinkPattern();
String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
if (!ledColor.equals("none")) {

View File

@@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@@ -139,7 +140,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
Intent intent = ConversationIntents.createBuilder(context, recipient.getId(), threadId)
.withDataUri(Uri.parse("custom://" + System.currentTimeMillis()))
.build();
FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
FailedNotificationBuilder builder = new FailedNotificationBuilder(context, SignalStore.settings().getMessageNotificationsPrivacy(), intent);
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
.notify((int)threadId, builder.build());
@@ -220,7 +221,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
@Override
public void updateNotification(@NonNull Context context) {
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
if (!SignalStore.settings().isMessageNotificationsEnabled()) {
return;
}
@@ -261,7 +262,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
}
private boolean shouldNotify(@NonNull Context context, @Nullable Recipient recipient, long threadId) {
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
if (!SignalStore.settings().isMessageNotificationsEnabled()) {
return false;
}
@@ -281,7 +282,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
int reminderCount,
@NonNull BubbleUtil.BubbleState defaultBubbleState)
{
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
if (!SignalStore.settings().isMessageNotificationsEnabled()) {
return;
}
@@ -371,7 +372,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
return false;
}
NotificationPrivacyPreference notificationPrivacy = TextSecurePreferences.getNotificationPrivacy(context);
NotificationPrivacyPreference notificationPrivacy = SignalStore.settings().getMessageNotificationsPrivacy();
SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, notificationPrivacy);
List<NotificationItem> notifications = notificationState.getNotifications();
Recipient recipient = notifications.get(0).getRecipient();
@@ -442,7 +443,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
NotificationManagerCompat.from(context).notify(notificationId, notification);
Log.i(TAG, "Posted notification.");
} catch (SecurityException e) {
Uri defaultValue = TextSecurePreferences.getNotificationRingtone(context);
Uri defaultValue = SignalStore.settings().getMessageNotificationSound();
if (!defaultValue.equals(notificationState.getRingtone(context))) {
Log.e(TAG, "Security exception when posting notification with custom ringtone", e);
clearNotificationRingtone(context, notifications.get(0).getRecipient());
@@ -465,7 +466,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
return;
}
NotificationPrivacyPreference notificationPrivacy = TextSecurePreferences.getNotificationPrivacy(context);
NotificationPrivacyPreference notificationPrivacy = SignalStore.settings().getMessageNotificationsPrivacy();
MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, notificationPrivacy);
List<NotificationItem> notifications = notificationState.getNotifications();
boolean shouldAlert = signal && Stream.of(notifications).anyMatch(item -> item.getNotifiedTimestamp() == 0);
@@ -506,7 +507,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
NotificationManagerCompat.from(context).notify(NotificationIds.MESSAGE_SUMMARY, builder.build());
Log.i(TAG, "Posted notification. " + notification.toString());
} catch (SecurityException securityException) {
Uri defaultValue = TextSecurePreferences.getNotificationRingtone(context);
Uri defaultValue = SignalStore.settings().getMessageNotificationSound();
if (!defaultValue.equals(notificationState.getRingtone(context))) {
Log.e(TAG, "Security exception when posting notification with custom ringtone", securityException);
clearNotificationRingtone(context, notifications.get(0).getRecipient());
@@ -524,7 +525,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
}
private static void sendInThreadNotification(Context context, Recipient recipient) {
if (!TextSecurePreferences.isInThreadNotifications(context) ||
if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled() ||
ServiceUtil.getAudioManager(context).getRingerMode() != AudioManager.RINGER_MODE_NORMAL)
{
return;
@@ -536,7 +537,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
}
if (uri == null) {
uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : TextSecurePreferences.getNotificationRingtone(context);
uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : SignalStore.settings().getMessageNotificationSound();
}
if (uri.toString().isEmpty()) {
@@ -729,7 +730,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
}
private static void scheduleReminder(Context context, int count) {
if (count >= TextSecurePreferences.getRepeatAlertsCount(context)) {
if (count >= SignalStore.settings().getMessageNotificationsRepeatAlerts()) {
return;
}

View File

@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -158,7 +159,7 @@ public class NotificationChannels {
if (recipient.getId().isUnknown()) return null;
VibrateState vibrateState = recipient.getMessageVibrate();
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == VibrateState.ENABLED;
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? SignalStore.settings().isMessageVibrateEnabled() : vibrateState == VibrateState.ENABLED;
Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context);
String displayName = recipient.getDisplayName(context);
@@ -180,7 +181,7 @@ public class NotificationChannels {
NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH);
setLedPreference(channel, TextSecurePreferences.getNotificationLedColor(context));
setLedPreference(channel, SignalStore.settings().getMessageLedColor());
channel.setGroup(CATEGORY_MESSAGES);
channel.enableVibration(vibrationEnabled);
@@ -523,9 +524,9 @@ public class NotificationChannels {
NotificationChannel joinEvents = new NotificationChannel(JOIN_EVENTS, context.getString(R.string.NotificationChannel_contact_joined_signal), NotificationManager.IMPORTANCE_DEFAULT);
messages.setGroup(CATEGORY_MESSAGES);
messages.enableVibration(TextSecurePreferences.isNotificationVibrateEnabled(context));
messages.setSound(TextSecurePreferences.getNotificationRingtone(context), getRingtoneAudioAttributes());
setLedPreference(messages, TextSecurePreferences.getNotificationLedColor(context));
messages.enableVibration(SignalStore.settings().isMessageVibrateEnabled());
messages.setSound(SignalStore.settings().getMessageNotificationSound(), getRingtoneAudioAttributes());
setLedPreference(messages, SignalStore.settings().getMessageLedColor());
calls.setShowBadge(false);
backups.setShowBadge(false);

View File

@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.MessageDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messages.IncomingMessageObserver
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.MessageNotifier
@@ -27,7 +28,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.BubbleUtil.BubbleState
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder
import org.whispersystems.signalservice.internal.util.Util
import java.util.concurrent.ConcurrentHashMap
@@ -47,7 +47,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
@Volatile private var lastAudibleNotification: Long = -1
@Volatile private var lastScheduledReminder: Long = 0
@Volatile private var previousLockedStatus: Boolean = KeyCachingService.isLocked(context)
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
@Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY
private val threadReminders: MutableMap<Long, Reminder> = ConcurrentHashMap()
@@ -116,12 +116,12 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
reminderCount: Int,
defaultBubbleState: BubbleState
) {
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
if (!SignalStore.settings().isMessageNotificationsEnabled) {
return
}
val currentLockStatus: Boolean = KeyCachingService.isLocked(context)
val currentPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
val currentPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
val notificationConfigurationChanged: Boolean = currentLockStatus != previousLockedStatus || currentPrivacyPreference != previousPrivacyPreference
previousLockedStatus = currentLockStatus
previousPrivacyPreference = currentPrivacyPreference
@@ -211,7 +211,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<Long>, threadsThatAlerted: Set<Long>) {
if (TextSecurePreferences.getRepeatAlertsCount(context) == 0) {
if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) {
return
}
@@ -221,7 +221,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
val (id: Long, reminder: Reminder) = entry
if (alertOverrides.contains(id)) {
val notifyCount: Int = reminder.count + 1
if (notifyCount >= TextSecurePreferences.getRepeatAlertsCount(context)) {
if (notifyCount >= SignalStore.settings().messageNotificationsRepeatAlerts) {
iterator.remove()
} else {
entry.setValue(Reminder(lastAudibleNotification, notifyCount))

View File

@@ -21,6 +21,7 @@ import androidx.core.graphics.drawable.IconCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.ReplyMethod
@@ -45,7 +46,7 @@ private const val BIG_PICTURE_DIMEN = 500
*/
sealed class NotificationBuilder(protected val context: Context) {
private val privacy: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
private val privacy: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
private val isNotLocked: Boolean = !KeyCachingService.isLocked(context)
abstract fun setSmallIcon(@DrawableRes drawable: Int)
@@ -145,10 +146,10 @@ sealed class NotificationBuilder(protected val context: Context) {
}
fun setLights() {
val ledColor: String = TextSecurePreferences.getNotificationLedColor(context)
val ledColor: String = SignalStore.settings().messageLedColor
if (ledColor != "none") {
var blinkPattern = TextSecurePreferences.getNotificationLedPattern(context)
var blinkPattern = SignalStore.settings().messageLedBlinkPattern
if (blinkPattern == "custom") {
blinkPattern = TextSecurePreferences.getNotificationLedPatternCustom(context)
}
@@ -297,8 +298,8 @@ sealed class NotificationBuilder(protected val context: Context) {
val ringtone: Uri? = recipient?.messageRingtone
val vibrate = recipient?.messageVibrate
val defaultRingtone: Uri = TextSecurePreferences.getNotificationRingtone(context)
val defaultVibrate: Boolean = TextSecurePreferences.isNotificationVibrateEnabled(context)
val defaultRingtone: Uri = SignalStore.settings().messageNotificationSound
val defaultVibrate: Boolean = SignalStore.settings().isMessageVibrateEnabled
if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) {
builder.setSound(defaultRingtone)

View File

@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.contacts.TurnOffContactJoinedNotificationsActi
import org.thoughtcrime.securesms.contacts.avatars.ContactColors
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver
import org.thoughtcrime.securesms.notifications.MarkReadReceiver
import org.thoughtcrime.securesms.notifications.NotificationChannels
@@ -21,7 +22,6 @@ import org.thoughtcrime.securesms.notifications.ReplyMethod
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
/**
@@ -41,7 +41,7 @@ data class NotificationConversation(
val isOnlyContactJoinedEvent: Boolean = messageCount == 1 && mostRecentNotification.isJoined
fun getContentTitle(context: Context): CharSequence {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.getDisplayName(context)
} else {
context.getString(R.string.SingleRecipientNotificationBuilder_signal)
@@ -49,7 +49,7 @@ data class NotificationConversation(
}
fun getContactLargeIcon(context: Context): Drawable? {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.getContactDrawable(context)
} else {
GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context))
@@ -57,7 +57,7 @@ data class NotificationConversation(
}
fun getContactUri(context: Context): String? {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.contactUri?.toString()
} else {
null
@@ -65,7 +65,7 @@ data class NotificationConversation(
}
fun getSlideBigPictureUri(context: Context): Uri? {
return if (notificationItems.size == 1 && TextSecurePreferences.getNotificationPrivacy(context).isDisplayMessage && !KeyCachingService.isLocked(context)) {
return if (notificationItems.size == 1 && SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage && !KeyCachingService.isLocked(context)) {
mostRecentNotification.getBigPictureUri()
} else {
null
@@ -73,7 +73,7 @@ data class NotificationConversation(
}
fun getContentText(context: Context): CharSequence? {
val privacy: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
val privacy: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
val stringBuilder = SpannableStringBuilder()
if (privacy.isDisplayContact && recipient.isGroup) {
@@ -88,7 +88,7 @@ data class NotificationConversation(
}
fun getConversationTitle(context: Context): CharSequence? {
if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
return if (isGroup) recipient.getDisplayName(context) else null
}
return context.getString(R.string.SingleRecipientNotificationBuilder_signal)

View File

@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
@@ -250,7 +251,7 @@ object NotificationFactory {
}
private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) {
if (!TextSecurePreferences.isInThreadNotifications(context) ||
if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled ||
ServiceUtil.getAudioManager(context).ringerMode != AudioManager.RINGER_MODE_NORMAL ||
(System.currentTimeMillis() - lastAudibleNotification) < DefaultMessageNotifier.MIN_AUDIBLE_PERIOD_MILLIS
) {
@@ -260,7 +261,7 @@ object NotificationFactory {
val uri: Uri = if (NotificationChannels.supported()) {
NotificationChannels.getMessageRingtone(context, recipient) ?: NotificationChannels.getMessageRingtone(context)
} else {
recipient.messageRingtone ?: TextSecurePreferences.getNotificationRingtone(context)
recipient.messageRingtone ?: SignalStore.settings().messageNotificationSound
}
if (uri.toString().isEmpty()) {

View File

@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.database.ThreadBodyUtil
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.notifications.AbstractNotificationBuilder
@@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageRecordUtil
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
private val TAG: String = Log.tag(NotificationItemV2::class.java)
@@ -72,7 +72,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
}
fun getStyledPrimaryText(context: Context, trimmed: Boolean = false): CharSequence {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayNothing) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayNothing) {
context.getString(R.string.SingleRecipientNotificationBuilder_new_message)
} else {
SpannableStringBuilder().apply {
@@ -87,7 +87,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
}
fun getPersonName(context: Context): CharSequence {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
individualRecipient.getDisplayName(context)
} else {
context.getString(R.string.SingleRecipientNotificationBuilder_signal)
@@ -99,7 +99,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
}
fun getPersonUri(context: Context): String? {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact && individualRecipient.isSystemContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact && individualRecipient.isSystemContact) {
individualRecipient.contactUri.toString()
} else {
null
@@ -107,7 +107,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
}
fun getPersonIcon(context: Context): Bitmap? {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
individualRecipient.getContactDrawable(context).toLargeBitmap(context)
} else {
null
@@ -115,7 +115,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
}
fun getPrimaryText(context: Context): CharSequence {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayMessage) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage) {
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
getPrimaryTextActual(context)
} else {
@@ -128,7 +128,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
fun getInboxLine(context: Context): CharSequence? {
return when {
TextSecurePreferences.getNotificationPrivacy(context).isDisplayNothing -> null
SignalStore.settings().messageNotificationsPrivacy.isDisplayNothing -> null
else -> getStyledPrimaryText(context, true)
}
}
@@ -205,7 +205,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
}
override fun getThumbnailInfo(context: Context): ThumbnailInfo {
return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayMessage && !KeyCachingService.isLocked(context)) {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage && !KeyCachingService.isLocked(context)) {
val thumbnailSlide: Slide? = slideDeck?.thumbnailSlide
ThumbnailInfo(thumbnailSlide?.publicUri, thumbnailSlide?.contentType)
} else {

View File

@@ -25,10 +25,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.PaymentPreferencesDirections;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.help.HelpFragment;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
@@ -224,11 +224,7 @@ public class PaymentsHomeFragment extends LoggingFragment {
NavHostFragment.findNavController(this).navigate(R.id.action_paymentsHome_to_paymentsBackup);
return true;
} else if (item.getItemId() == R.id.payments_home_fragment_menu_help) {
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_HELP_FRAGMENT, true);
intent.putExtra(HelpFragment.START_CATEGORY_INDEX, HelpFragment.PAYMENT_INDEX);
startActivity(intent);
startActivity(AppSettingsActivity.help(requireContext(), HelpFragment.PAYMENT_INDEX));
return true;
}

View File

@@ -10,7 +10,6 @@ import androidx.preference.Preference;
import com.google.android.material.snackbar.Snackbar;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
@@ -68,8 +67,6 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
return true;
});
}
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__advanced_pin_settings);
}
private void onPreferenceChanged(boolean enabled) {

View File

@@ -1,306 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import com.google.firebase.iid.FirebaseInstanceId;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.delete.DeleteAccountFragment;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
import org.thoughtcrime.securesms.payments.preferences.transfer.PaymentsTransferFragmentArgs;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.payments.FormatterOptions;
import org.whispersystems.signalservice.api.payments.Money;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import java.io.IOException;
public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
private static final String TAG = Log.tag(AdvancedPreferenceFragment.class);
private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging";
private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs";
private static final String INTERNAL_PREF = "pref_internal";
private static final String ADVANCED_PIN_PREF = "pref_advanced_pin_settings";
private static final String DELETE_ACCOUNT = "pref_delete_account";
private static final int PICK_IDENTITY_CONTACT = 1;
private static final int TRANSFER_CURRENCY = 2;
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
initializeIdentitySelection();
Preference submitDebugLog = this.findPreference(SUBMIT_DEBUG_LOG_PREF);
submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener());
submitDebugLog.setSummary(getVersion(getActivity()));
Preference pinSettings = this.findPreference(ADVANCED_PIN_PREF);
pinSettings.setOnPreferenceClickListener(preference -> {
getApplicationPreferencesActivity().pushFragment(new AdvancedPinPreferenceFragment());
return false;
});
Preference internalPreference = this.findPreference(INTERNAL_PREF);
internalPreference.setVisible(FeatureFlags.internalUser());
internalPreference.setOnPreferenceClickListener(preference -> {
if (FeatureFlags.internalUser()) {
getApplicationPreferencesActivity().pushFragment(new InternalOptionsPreferenceFragment());
return true;
} else {
return false;
}
});
Preference deleteAccount = this.findPreference(DELETE_ACCOUNT);
deleteAccount.setOnPreferenceClickListener(preference -> {
Money.MobileCoin latestBalance = SignalStore.paymentsValues().mobileCoinLatestBalance().getFullAmount().requireMobileCoin();
if (!latestBalance.equals(Money.MobileCoin.ZERO)) {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.AdvancedPreferenceFragment__transfer_mob_balance)
.setMessage(getString(R.string.AdvancedPreferenceFragment__you_have_a_balance_of_s, latestBalance.toString(FormatterOptions.defaults())))
.setPositiveButton(R.string.AdvancedPreferenceFragment__transfer, (dialog, which) -> {
Intent intent = new Intent(requireContext(), PaymentsActivity.class);
intent.putExtra(PaymentsActivity.EXTRA_PAYMENTS_STARTING_ACTION, R.id.action_directly_to_paymentsTransfer);
intent.putExtra(PaymentsActivity.EXTRA_STARTING_ARGUMENTS, new PaymentsTransferFragmentArgs.Builder().setFinishOnConfirm(true).build().toBundle());
startActivityForResult(intent, TRANSFER_CURRENCY);
dialog.dismiss();
})
.setNegativeButton(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.signal_alert_primary), getString(R.string.AdvancedPreferenceFragment__dont_transfer)), (dialog, which) -> {
getApplicationPreferencesActivity().pushFragment(new DeleteAccountFragment());
dialog.dismiss();
})
.show();
} else {
getApplicationPreferencesActivity().pushFragment(new DeleteAccountFragment());
}
return false;
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_tertiary));
View list = view.findViewById(R.id.recycler_view);
ViewGroup.LayoutParams params = list.getLayoutParams();
params.height = ActionBar.LayoutParams.WRAP_CONTENT;
list.setLayoutParams(params);
list.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_primary));
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_advanced);
}
@Override
public void onResume() {
super.onResume();
getApplicationPreferencesActivity().getSupportActionBar().setTitle(R.string.preferences__advanced);
initializePushMessagingToggle();
}
@Override
public void onActivityResult(int reqCode, int resultCode, Intent data) {
super.onActivityResult(reqCode, resultCode, data);
Log.i(TAG, "Got result: " + resultCode + " for req: " + reqCode);
if (resultCode == Activity.RESULT_OK && reqCode == PICK_IDENTITY_CONTACT) {
handleIdentitySelection(data);
} else if (resultCode == Activity.RESULT_OK && reqCode == TRANSFER_CURRENCY) {
getApplicationPreferencesActivity().pushFragment(new DeleteAccountFragment());
}
}
private @NonNull ApplicationPreferencesActivity getApplicationPreferencesActivity() {
return (ApplicationPreferencesActivity) requireActivity();
}
private void initializePushMessagingToggle() {
CheckBoxPreference preference = (CheckBoxPreference)this.findPreference(PUSH_MESSAGING_PREF);
if (TextSecurePreferences.isPushRegistered(getActivity())) {
preference.setChecked(true);
preference.setSummary(PhoneNumberFormatter.prettyPrint(TextSecurePreferences.getLocalNumber(getActivity())));
} else {
preference.setChecked(false);
preference.setSummary(R.string.preferences__free_private_messages_and_calls);
}
preference.setOnPreferenceChangeListener(new PushMessagingClickListener());
}
private void initializeIdentitySelection() {
ContactIdentityManager identity = ContactIdentityManager.getInstance(getActivity());
Preference preference = this.findPreference(TextSecurePreferences.IDENTITY_PREF);
if (identity.isSelfIdentityAutoDetected()) {
this.getPreferenceScreen().removePreference(preference);
} else {
Uri contactUri = identity.getSelfIdentityUri();
if (contactUri != null) {
String contactName = ContactAccessor.getInstance().getNameFromContact(getActivity(), contactUri);
preference.setSummary(String.format(getString(R.string.ApplicationPreferencesActivity_currently_s),
contactName));
}
preference.setOnPreferenceClickListener(new IdentityPreferenceClickListener());
}
}
private @NonNull String getVersion(@Nullable Context context) {
if (context == null) return "";
String app = context.getString(R.string.app_name);
String version = BuildConfig.VERSION_NAME;
return String.format("%s %s", app, version);
}
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(intent, PICK_IDENTITY_CONTACT);
return true;
}
}
private void handleIdentitySelection(Intent data) {
Uri contactUri = data.getData();
if (contactUri != null) {
TextSecurePreferences.setIdentityContactUri(getActivity(), contactUri.toString());
initializeIdentitySelection();
}
}
private class SubmitDebugLogListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
final Intent intent = new Intent(getActivity(), SubmitDebugLogActivity.class);
startActivity(intent);
return true;
}
}
private class PushMessagingClickListener implements Preference.OnPreferenceChangeListener {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private class DisablePushMessagesTask extends ProgressDialogAsyncTask<Void, Void, Integer> {
private final CheckBoxPreference checkBoxPreference;
public DisablePushMessagesTask(final CheckBoxPreference checkBoxPreference) {
super(getActivity(), R.string.ApplicationPreferencesActivity_unregistering, R.string.ApplicationPreferencesActivity_unregistering_from_signal_messages_and_calls);
this.checkBoxPreference = checkBoxPreference;
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
switch (result) {
case NETWORK_ERROR:
Toast.makeText(getActivity(),
R.string.ApplicationPreferencesActivity_error_connecting_to_server,
Toast.LENGTH_LONG).show();
break;
case SUCCESS:
TextSecurePreferences.setPushRegistered(getActivity(), false);
SignalStore.registrationValues().clearRegistrationComplete();
initializePushMessagingToggle();
break;
}
}
@Override
protected Integer doInBackground(Void... params) {
try {
Context context = getActivity();
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
try {
accountManager.setGcmId(Optional.<String>absent());
} catch (AuthorizationFailedException e) {
Log.w(TAG, e);
}
if (!TextSecurePreferences.isFcmDisabled(context)) {
FirebaseInstanceId.getInstance().deleteInstanceId();
}
return SUCCESS;
} catch (IOException ioe) {
Log.w(TAG, ioe);
return NETWORK_ERROR;
}
}
}
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIcon(R.drawable.ic_info_outline);
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls);
builder.setMessage(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls_by_unregistering);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new DisablePushMessagesTask((CheckBoxPreference)preference).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
builder.show();
} else {
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()));
}
return false;
}
}
}

View File

@@ -1,655 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.TextAppearanceSpan;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.autofill.HintConstants;
import androidx.core.app.DialogCompat;
import androidx.core.view.ViewCompat;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.PassphraseChangeActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.blocked.BlockedUsersActivity;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ConversationShortcutUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.keyvalue.KbsValues;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.PinValues;
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
import org.thoughtcrime.securesms.megaphone.Megaphones;
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
import mobi.upod.timedurationpicker.TimeDurationPicker;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment {
private static final String TAG = Log.tag(AppProtectionPreferenceFragment.class);
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
private static final String PREFERENCE_INCOGNITO_LEARN_MORE = "pref_incognito_learn_more";
private static final String PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER = "pref_who_can_see_phone_number";
private static final String PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER = "pref_who_can_find_by_phone_number";
private CheckBoxPreference disablePassphrase;
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
this.findPreference(KbsValues.V2_LOCK_ENABLED).setPreferenceDataStore(SignalStore.getPreferenceDataStore());
((SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED)).setChecked(SignalStore.kbsValues().isV2RegistrationLockEnabled());
this.findPreference(KbsValues.V2_LOCK_ENABLED).setOnPreferenceChangeListener(new RegistrationLockV2ChangedListener());
this.findPreference(PinValues.PIN_REMINDERS_ENABLED).setPreferenceDataStore(SignalStore.getPreferenceDataStore());
((SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED)).setChecked(SignalStore.pinValues().arePinRemindersEnabled());
this.findPreference(PinValues.PIN_REMINDERS_ENABLED).setOnPreferenceChangeListener(new PinRemindersChangedListener());
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
this.findPreference(PREFERENCE_CATEGORY_BLOCKED).setOnPreferenceClickListener(new BlockedContactsClickListener());
this.findPreference(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS).setOnPreferenceChangeListener(new ShowUnidentifiedDeliveryIndicatorsChangedListener());
this.findPreference(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS).setOnPreferenceChangeListener(new UniversalUnidentifiedAccessChangedListener());
this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
this.findPreference(PREFERENCE_INCOGNITO_LEARN_MORE).setOnPreferenceClickListener(new IncognitoLearnMoreClickListener());
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
if (FeatureFlags.phoneNumberPrivacy()) {
Preference whoCanSeePhoneNumber = this.findPreference(PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER);
Preference whoCanFindByPhoneNumber = this.findPreference(PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER);
whoCanSeePhoneNumber.setPreferenceDataStore(null);
whoCanSeePhoneNumber.setOnPreferenceClickListener(new PhoneNumberPrivacyWhoCanSeeClickListener());
whoCanFindByPhoneNumber.setPreferenceDataStore(null);
whoCanFindByPhoneNumber.setOnPreferenceClickListener(new PhoneNumberPrivacyWhoCanFindClickListener());
} else {
this.findPreference("category_phone_number_privacy").setVisible(false);
}
SwitchPreferenceCompat linkPreviewPref = (SwitchPreferenceCompat) this.findPreference(SettingsValues.LINK_PREVIEWS);
linkPreviewPref.setChecked(SignalStore.settings().isLinkPreviewsEnabled());
linkPreviewPref.setPreferenceDataStore(SignalStore.getPreferenceDataStore());
linkPreviewPref.setOnPreferenceChangeListener(new LinkPreviewToggleListener());
initializeVisibility();
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_app_protection);
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__privacy);
if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
else initializeScreenLockTimeoutSummary();
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
Preference signalPinCreateChange = this.findPreference(TextSecurePreferences.SIGNAL_PIN_CHANGE);
SwitchPreferenceCompat signalPinReminders = (SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED);
SwitchPreferenceCompat registrationLockV2 = (SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED);
if (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) {
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinUpdateListener());
signalPinCreateChange.setTitle(R.string.preferences_app_protection__change_your_pin);
signalPinReminders.setEnabled(true);
registrationLockV2.setEnabled(true);
} else {
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinCreateListener());
signalPinCreateChange.setTitle(R.string.preferences_app_protection__create_a_pin);
signalPinReminders.setEnabled(false);
registrationLockV2.setEnabled(false);
}
initializePhoneNumberPrivacyWhoCanSeeSummary();
initializePhoneNumberPrivacyWhoCanFindSummary();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
Snackbar.make(requireView(), R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).setTextColor(Color.WHITE).show();
}
}
private void initializePassphraseTimeoutSummary() {
int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setSummary(getResources().getQuantityString(R.plurals.AppProtectionPreferenceFragment_minutes, timeoutMinutes, timeoutMinutes));
}
private void initializeScreenLockTimeoutSummary() {
long timeoutSeconds = TextSecurePreferences.getScreenLockTimeout(getContext());
long hours = TimeUnit.SECONDS.toHours(timeoutSeconds);
long minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - (TimeUnit.SECONDS.toHours(timeoutSeconds) * 60 );
findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT)
.setSummary(timeoutSeconds <= 0 ? getString(R.string.AppProtectionPreferenceFragment_none) :
String.format(Locale.getDefault(), "%02d:%02d:00", hours, minutes));
}
private void initializePhoneNumberPrivacyWhoCanSeeSummary() {
Preference preference = findPreference(PREFERENCE_WHO_CAN_SEE_PHONE_NUMBER);
switch (SignalStore.phoneNumberPrivacy().getPhoneNumberSharingMode()) {
case EVERYONE: preference.setSummary(R.string.PhoneNumberPrivacy_everyone); break;
case CONTACTS: preference.setSummary(R.string.PhoneNumberPrivacy_my_contacts); break;
case NOBODY : preference.setSummary(R.string.PhoneNumberPrivacy_nobody); break;
default : throw new AssertionError();
}
}
private void initializePhoneNumberPrivacyWhoCanFindSummary() {
Preference preference = findPreference(PREFERENCE_WHO_CAN_FIND_BY_PHONE_NUMBER);
switch (SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode()) {
case LISTED : preference.setSummary(R.string.PhoneNumberPrivacy_everyone); break;
case UNLISTED: preference.setSummary(R.string.PhoneNumberPrivacy_nobody); break;
default : throw new AssertionError();
}
}
private void initializeVisibility() {
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
findPreference("pref_enable_passphrase_temporary").setVisible(false);
findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setVisible(false);
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setVisible(false);
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF).setVisible(false);
KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
findPreference(TextSecurePreferences.SCREEN_LOCK).setEnabled(false);
}
} else {
findPreference(TextSecurePreferences.SCREEN_LOCK).setVisible(false);
findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setVisible(false);
}
}
private class ScreenLockListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.w(TAG, "Screen lock preference changed: " + newValue);
boolean enabled = (Boolean)newValue;
TextSecurePreferences.setScreenLockEnabled(getContext(), enabled);
Intent intent = new Intent(getContext(), KeyCachingService.class);
intent.setAction(KeyCachingService.LOCK_TOGGLED_EVENT);
getContext().startService(intent);
ConversationUtil.refreshRecipientShortcuts();
return true;
}
}
private class ScreenLockTimeoutListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
long timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration);
TextSecurePreferences.setScreenLockTimeout(getContext(), timeoutSeconds);
initializeScreenLockTimeoutSummary();
}, 0, TimeDurationPicker.HH_MM).show();
return true;
}
}
private class KbsPinUpdateListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
return true;
}
}
private class KbsPinCreateListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
return true;
}
}
private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(getActivity(), BlockedUsersActivity.class);
startActivity(intent);
return true;
}
}
private class ReadReceiptToggleListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
SignalExecutors.BOUNDED.execute(() -> {
boolean enabled = (boolean)newValue;
DatabaseFactory.getRecipientDatabase(getContext()).markNeedsSync(Recipient.self().getId());
StorageSyncHelper.scheduleSyncForDataChange();
ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(enabled,
TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()),
SignalStore.settings().isLinkPreviewsEnabled()));
});
return true;
}
}
private class TypingIndicatorsToggleListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
SignalExecutors.BOUNDED.execute(() -> {
boolean enabled = (boolean)newValue;
DatabaseFactory.getRecipientDatabase(getContext()).markNeedsSync(Recipient.self().getId());
StorageSyncHelper.scheduleSyncForDataChange();
ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(requireContext()),
enabled,
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()),
SignalStore.settings().isLinkPreviewsEnabled()));
if (!enabled) {
ApplicationDependencies.getTypingStatusRepository().clear();
}
});
return true;
}
}
private class LinkPreviewToggleListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
SignalExecutors.BOUNDED.execute(() -> {
boolean enabled = (boolean)newValue;
DatabaseFactory.getRecipientDatabase(getContext()).markNeedsSync(Recipient.self().getId());
StorageSyncHelper.scheduleSyncForDataChange();
ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(requireContext()),
TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(requireContext()),
enabled));
if (enabled) {
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.LINK_PREVIEWS);
}
});
return true;
}
}
public static CharSequence getSummary(Context context) {
final int privacySummaryResId = R.string.ApplicationPreferencesActivity_privacy_summary;;
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
boolean registrationLockEnabled = RegistrationLockUtil.userHasRegistrationLock(context);
if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
if (registrationLockEnabled) {
return context.getString(privacySummaryResId, offRes, onRes);
} else {
return context.getString(privacySummaryResId, offRes, offRes);
}
} else {
if (registrationLockEnabled) {
return context.getString(privacySummaryResId, onRes, onRes);
} else {
return context.getString(privacySummaryResId, onRes, offRes);
}
}
}
// Derecated
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (MasterSecretUtil.isPassphraseInitialized(getActivity())) {
startActivity(new Intent(getActivity(), PassphraseChangeActivity.class));
} else {
Toast.makeText(getActivity(),
R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet,
Toast.LENGTH_LONG).show();
}
return true;
}
}
private class PassphraseIntervalClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
int timeoutMinutes = Math.max((int)TimeUnit.MILLISECONDS.toMinutes(duration), 1);
TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes);
initializePassphraseTimeoutSummary();
}, 0, TimeDurationPicker.HH_MM).show();
return true;
}
}
private class DisablePassphraseClickListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase);
builder.setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications);
builder.setIcon(R.drawable.ic_warning);
builder.setPositiveButton(R.string.ApplicationPreferencesActivity_disable, (dialog, which) -> {
MasterSecretUtil.changeMasterSecretPassphrase(getActivity(),
KeyCachingService.getMasterSecret(getContext()),
MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
TextSecurePreferences.setPasswordDisabled(getActivity(), true);
((CheckBoxPreference)preference).setChecked(false);
Intent intent = new Intent(getActivity(), KeyCachingService.class);
intent.setAction(KeyCachingService.DISABLE_ACTION);
getActivity().startService(intent);
initializeVisibility();
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
} else {
Intent intent = new Intent(getActivity(), PassphraseChangeActivity.class);
startActivity(intent);
}
return false;
}
}
private class ShowUnidentifiedDeliveryIndicatorsChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean enabled = (boolean) newValue;
SignalExecutors.BOUNDED.execute(() -> {
DatabaseFactory.getRecipientDatabase(getContext()).markNeedsSync(Recipient.self().getId());
StorageSyncHelper.scheduleSyncForDataChange();
ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(getContext()),
TextSecurePreferences.isTypingIndicatorsEnabled(getContext()),
enabled,
SignalStore.settings().isLinkPreviewsEnabled()));
});
return true;
}
}
private class UniversalUnidentifiedAccessChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
return true;
}
}
private class UnidentifiedLearnMoreClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
CommunicationActions.openBrowserLink(preference.getContext(), "https://signal.org/blog/sealed-sender/");
return true;
}
}
private class IncognitoLearnMoreClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
CommunicationActions.openBrowserLink(preference.getContext(), "https://support.signal.org/hc/en-us/articles/360055276112");
return true;
}
}
private class RegistrationLockV2ChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean value = (boolean) newValue;
Log.i(TAG, "Getting ready to change registration lock setting to: " + value);
if (value) {
RegistrationLockV2Dialog.showEnableDialog(requireContext(), () -> ((CheckBoxPreference) preference).setChecked(true));
} else {
RegistrationLockV2Dialog.showDisableDialog(requireContext(), () -> ((CheckBoxPreference) preference).setChecked(false));
}
return false;
}
}
private class PinRemindersChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean value = (boolean) newValue;
if (!value) {
Context context = preference.getContext();
DisplayMetrics metrics = preference.getContext().getResources().getDisplayMetrics();
AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.Theme_Signal_AlertDialog_Dark_Cornered_ColoredAccent : R.style.Theme_Signal_AlertDialog_Light_Cornered_ColoredAccent)
.setView(R.layout.pin_disable_reminders_dialog)
.create();
dialog.show();
dialog.getWindow().setLayout((int)(metrics.widthPixels * .80), ViewGroup.LayoutParams.WRAP_CONTENT);
EditText pinEditText = (EditText) DialogCompat.requireViewById(dialog, R.id.reminder_disable_pin);
TextView statusText = (TextView) DialogCompat.requireViewById(dialog, R.id.reminder_disable_status);
View cancelButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_cancel);
View turnOffButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_turn_off);
pinEditText.post(() -> {
if (pinEditText.requestFocus()) {
ServiceUtil.getInputMethodManager(pinEditText.getContext()).showSoftInput(pinEditText, 0);
}
});
ViewCompat.setAutofillHints(pinEditText, HintConstants.AUTOFILL_HINT_PASSWORD);
switch (SignalStore.pinValues().getKeyboardType()) {
case NUMERIC:
pinEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
break;
case ALPHA_NUMERIC:
pinEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
break;
default:
throw new AssertionError("Unexpected type!");
}
pinEditText.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
turnOffButton.setEnabled(text.length() >= KbsConstants.MINIMUM_PIN_LENGTH);
}
});
pinEditText.setTypeface(Typeface.DEFAULT);
turnOffButton.setOnClickListener(v -> {
String pin = pinEditText.getText().toString();
boolean correct = PinHashing.verifyLocalPinHash(Objects.requireNonNull(SignalStore.kbsValues().getLocalPinHash()), pin);
if (correct) {
SignalStore.pinValues().setPinRemindersEnabled(false);
((SwitchPreferenceCompat) findPreference(PinValues.PIN_REMINDERS_ENABLED)).setChecked(false);
dialog.dismiss();
} else {
statusText.setText(R.string.preferences_app_protection__incorrect_pin_try_again);
}
});
cancelButton.setOnClickListener(v -> dialog.dismiss());
return false;
} else {
return true;
}
}
}
private final class PhoneNumberPrivacyWhoCanSeeClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
PhoneNumberPrivacyValues phoneNumberPrivacyValues = SignalStore.phoneNumberPrivacy();
final PhoneNumberPrivacyValues.PhoneNumberSharingMode[] value = { phoneNumberPrivacyValues.getPhoneNumberSharingMode() };
Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> items = items(requireContext());
List<PhoneNumberPrivacyValues.PhoneNumberSharingMode> modes = new ArrayList<>(items.keySet());
CharSequence[] modeStrings = items.values().toArray(new CharSequence[0]);
int selectedMode = modes.indexOf(value[0]);
new AlertDialog.Builder(requireActivity())
.setTitle(R.string.preferences_app_protection__see_my_phone_number)
.setCancelable(true)
.setSingleChoiceItems(modeStrings, selectedMode, (dialog, which) -> value[0] = modes.get(which))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
PhoneNumberPrivacyValues.PhoneNumberSharingMode phoneNumberSharingMode = value[0];
phoneNumberPrivacyValues.setPhoneNumberSharingMode(phoneNumberSharingMode);
Log.i(TAG, String.format("PhoneNumberSharingMode changed to %s. Scheduling storage value sync", phoneNumberSharingMode));
StorageSyncHelper.scheduleSyncForDataChange();
initializePhoneNumberPrivacyWhoCanSeeSummary();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
private Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> items(Context context) {
Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> map = new LinkedHashMap<>();
map.put(PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE, titleAndDescription(context, context.getString(R.string.PhoneNumberPrivacy_everyone), context.getString(R.string.PhoneNumberPrivacy_everyone_see_description)));
map.put(PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY, context.getString(R.string.PhoneNumberPrivacy_nobody));
return map;
}
}
private final class PhoneNumberPrivacyWhoCanFindClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
PhoneNumberPrivacyValues phoneNumberPrivacyValues = SignalStore.phoneNumberPrivacy();
final PhoneNumberPrivacyValues.PhoneNumberListingMode[] value = { phoneNumberPrivacyValues.getPhoneNumberListingMode() };
new AlertDialog.Builder(requireActivity())
.setTitle(R.string.preferences_app_protection__find_me_by_phone_number)
.setCancelable(true)
.setSingleChoiceItems(items(requireContext()),
value[0].ordinal(),
(dialog, which) -> value[0] = PhoneNumberPrivacyValues.PhoneNumberListingMode.values()[which])
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
PhoneNumberPrivacyValues.PhoneNumberListingMode phoneNumberListingMode = value[0];
phoneNumberPrivacyValues.setPhoneNumberListingMode(phoneNumberListingMode);
Log.i(TAG, String.format("PhoneNumberListingMode changed to %s. Scheduling storage value sync", phoneNumberListingMode));
StorageSyncHelper.scheduleSyncForDataChange();
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
initializePhoneNumberPrivacyWhoCanFindSummary();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
private CharSequence[] items(Context context) {
return new CharSequence[]{
titleAndDescription(context, context.getString(R.string.PhoneNumberPrivacy_everyone), context.getString(R.string.PhoneNumberPrivacy_everyone_find_description)),
context.getString(R.string.PhoneNumberPrivacy_nobody) };
}
}
/** Adds a detail row for radio group descriptions. */
private static CharSequence titleAndDescription(@NonNull Context context, @NonNull String header, @NonNull String description) {
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append("\n");
builder.append(header);
builder.append("\n");
builder.setSpan(new TextAppearanceSpan(context, android.R.style.TextAppearance_Small), builder.length(), builder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
builder.append(description);
builder.append("\n");
return builder;
}
}

View File

@@ -1,75 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
import java.util.Arrays;
public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment {
private static final String WALLPAPER_PREF = "pref_wallpaper";
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
this.findPreference(TextSecurePreferences.THEME_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
this.findPreference(TextSecurePreferences.LANGUAGE_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
this.findPreference(WALLPAPER_PREF).setOnPreferenceClickListener(preference -> {
startActivity(ChatWallpaperActivity.createIntent(requireContext()));
ActivityTransitionUtil.setSlideInTransition(requireActivity());
return true;
});
initializeListSummary((ListPreference)findPreference(TextSecurePreferences.THEME_PREF));
initializeListSummary((ListPreference)findPreference(TextSecurePreferences.LANGUAGE_PREF));
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_appearance);
}
@Override
public void onStart() {
super.onStart();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener((ApplicationPreferencesActivity)getActivity());
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__appearance);
}
@Override
public void onStop() {
super.onStop();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener((ApplicationPreferencesActivity) getActivity());
}
public static CharSequence getSummary(Context context) {
String[] languageEntries = context.getResources().getStringArray(R.array.language_entries);
String[] languageEntryValues = context.getResources().getStringArray(R.array.language_values);
String[] themeEntries = context.getResources().getStringArray(R.array.pref_theme_entries);
String[] themeEntryValues = context.getResources().getStringArray(R.array.pref_theme_values);
int langIndex = Arrays.asList(languageEntryValues).indexOf(TextSecurePreferences.getLanguage(context));
int themeIndex = Arrays.asList(themeEntryValues).indexOf(TextSecurePreferences.getTheme(context));
if (langIndex == -1) langIndex = 0;
if (themeIndex == -1) themeIndex = 0;
return context.getString(R.string.ApplicationPreferencesActivity_appearance_summary,
themeEntries[themeIndex],
languageEntries[langIndex]);
}
}

View File

@@ -34,7 +34,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
import java.util.Objects;
@@ -83,7 +82,6 @@ public class BackupsPreferenceFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.BackupsPreferenceFragment__chat_backups);
setBackupStatus();
setBackupSummary();
@@ -133,7 +131,7 @@ public class BackupsPreferenceFragment extends Fragment {
}
private void setBackupStatus() {
if (TextSecurePreferences.isBackupEnabled(requireContext())) {
if (SignalStore.settings().isBackupEnabled()) {
if (BackupUtil.canUserAccessBackupDirectory(requireContext())) {
setBackupsEnabled();
} else {
@@ -191,7 +189,7 @@ public class BackupsPreferenceFragment extends Fragment {
@RequiresApi(29)
private void onToggleClickedApi29() {
if (!TextSecurePreferences.isBackupEnabled(requireContext())) {
if (!SignalStore.settings().isBackupEnabled()) {
BackupDialog.showChooseBackupLocationDialog(this, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE);
} else {
BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);
@@ -203,7 +201,7 @@ public class BackupsPreferenceFragment extends Fragment {
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
if (!TextSecurePreferences.isBackupEnabled(requireContext())) {
if (!SignalStore.settings().isBackupEnabled()) {
BackupDialog.showEnableBackupDialog(requireContext(), null, null, this::setBackupsEnabled);
} else {
BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);

View File

@@ -1,72 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
public class BlockedContactListItem extends RelativeLayout implements RecipientForeverObserver {
private AvatarImageView contactPhotoImage;
private TextView nameView;
private GlideRequests glideRequests;
private LiveRecipient recipient;
public BlockedContactListItem(Context context) {
super(context);
}
public BlockedContactListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BlockedContactListItem(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.contactPhotoImage = findViewById(R.id.contact_photo_image);
this.nameView = findViewById(R.id.name);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (this.recipient != null) {
recipient.removeForeverObserver(this);
}
}
public void set(@NonNull GlideRequests glideRequests, @NonNull LiveRecipient recipient) {
this.glideRequests = glideRequests;
this.recipient = recipient;
onRecipientChanged(recipient.get());
this.recipient.observeForever(this);
}
@Override
public void onRecipientChanged(@NonNull Recipient recipient) {
final AvatarImageView contactPhotoImage = this.contactPhotoImage;
final TextView nameView = this.nameView;
contactPhotoImage.setAvatar(glideRequests, recipient, false);
nameView.setText(recipient.getDisplayName(getContext()));
}
public Recipient getRecipient() {
return recipient.get();
}
}

View File

@@ -1,88 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThrottledDebouncer;
public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
private static final String PREFER_SYSTEM_CONTACT_PHOTOS = "pref_system_contact_photos";
private final ThrottledDebouncer refreshDebouncer = new ThrottledDebouncer(500);
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
findPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF)
.setOnPreferenceChangeListener(new ListSummaryListener());
findPreference(TextSecurePreferences.BACKUP).setOnPreferenceClickListener(unused -> {
goToBackupsPreferenceFragment();
return true;
});
findPreference(TextSecurePreferences.TRANSFER).setOnPreferenceClickListener(unused -> {
goToTransferAccount();
return true;
});
findPreference(PREFER_SYSTEM_CONTACT_PHOTOS)
.setOnPreferenceChangeListener((preference, newValue) -> {
SignalStore.settings().setPreferSystemContactPhotos(newValue == Boolean.TRUE);
refreshDebouncer.publish(ConversationUtil::refreshRecipientShortcuts);
StorageSyncHelper.scheduleSyncForDataChange();
return true;
});
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF));
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_chats);
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity)getActivity()).getSupportActionBar().setTitle(R.string.preferences_chats__chats);
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void goToBackupsPreferenceFragment() {
((ApplicationPreferencesActivity) requireActivity()).pushFragment(new BackupsPreferenceFragment());
}
private void goToTransferAccount() {
requireContext().startActivity(new Intent(requireContext(), OldDeviceTransferActivity.class));
}
public static CharSequence getSummary(Context context) {
return null;
}
}

View File

@@ -1,138 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragment {
private static final String TAG = Log.tag(DataAndStoragePreferenceFragment.class);
private static final String MANAGE_STORAGE_KEY = "pref_data_manage";
private static final String USE_PROXY_KEY = "pref_use_proxy";
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF)
.setOnPreferenceChangeListener(new MediaDownloadChangeListener());
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF)
.setOnPreferenceChangeListener(new MediaDownloadChangeListener());
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF)
.setOnPreferenceChangeListener(new MediaDownloadChangeListener());
findPreference(TextSecurePreferences.CALL_BANDWIDTH_PREF)
.setOnPreferenceChangeListener(new CallBandwidthChangeListener());
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.CALL_BANDWIDTH_PREF));
Preference manageStorage = findPreference(MANAGE_STORAGE_KEY);
manageStorage.setOnPreferenceClickListener(unused -> {
requireApplicationPreferencesActivity().pushFragment(new StoragePreferenceFragment());
return false;
});
ApplicationPreferencesViewModel viewModel = ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(requireActivity());
viewModel.getStorageBreakdown()
.observe(requireActivity(),
breakdown -> manageStorage.setSummary(Util.getPrettyFileSize(breakdown.getTotalSize())));
findPreference(USE_PROXY_KEY).setOnPreferenceClickListener(unused -> {
requireApplicationPreferencesActivity().pushFragment(EditProxyFragment.newInstance());
return false;
});
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_data_and_storage);
}
@Override
public void onResume() {
super.onResume();
requireApplicationPreferencesActivity().getSupportActionBar().setTitle(R.string.preferences__data_and_storage);
setMediaDownloadSummaries();
ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(requireActivity()).refreshStorageBreakdown(requireContext());
findPreference(USE_PROXY_KEY).setSummary(SignalStore.proxy().isProxyEnabled() ? R.string.preferences_on : R.string.preferences_off);
}
private @NonNull ApplicationPreferencesActivity requireApplicationPreferencesActivity() {
return (ApplicationPreferencesActivity) requireActivity();
}
private void setMediaDownloadSummaries() {
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF)
.setSummary(getSummaryForMediaPreference(TextSecurePreferences.getMobileMediaDownloadAllowed(getActivity())));
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF)
.setSummary(getSummaryForMediaPreference(TextSecurePreferences.getWifiMediaDownloadAllowed(getActivity())));
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF)
.setSummary(getSummaryForMediaPreference(TextSecurePreferences.getRoamingMediaDownloadAllowed(getActivity())));
}
private CharSequence getSummaryForMediaPreference(Set<String> allowedNetworks) {
String[] keys = getResources().getStringArray(R.array.pref_media_download_entries);
String[] values = getResources().getStringArray(R.array.pref_media_download_values);
List<String> outValues = new ArrayList<>(allowedNetworks.size());
for (int i=0; i < keys.length; i++) {
if (allowedNetworks.contains(keys[i])) outValues.add(values[i]);
}
return outValues.isEmpty() ? getResources().getString(R.string.preferences__none)
: TextUtils.join(", ", outValues);
}
private class MediaDownloadChangeListener implements Preference.OnPreferenceChangeListener {
@SuppressWarnings("unchecked")
@Override public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.i(TAG, "onPreferenceChange");
preference.setSummary(getSummaryForMediaPreference((Set<String>)newValue));
return true;
}
}
private class CallBandwidthChangeListener extends ListSummaryListener {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
ListPreference listPref = (ListPreference) preference;
int entryIndex = Arrays.asList(listPref.getEntryValues()).indexOf(value);
switch (entryIndex) {
case 0:
SignalStore.settings().setCallBandwidthMode(CallBandwidthMode.HIGH_ALWAYS);
break;
case 1:
SignalStore.settings().setCallBandwidthMode(CallBandwidthMode.HIGH_ON_WIFI);
break;
case 2:
SignalStore.settings().setCallBandwidthMode(CallBandwidthMode.LOW_ALWAYS);
break;
default:
throw new AssertionError();
}
ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate();
return super.onPreferenceChange(preference, value);
}
}
}

View File

@@ -11,7 +11,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.app.ShareCompat;
import androidx.fragment.app.Fragment;
@@ -33,13 +32,13 @@ import org.whispersystems.signalservice.internal.configuration.SignalProxy;
public class EditProxyFragment extends Fragment {
private SwitchCompat proxySwitch;
private EditText proxyText;
private TextView proxyTitle;
private TextView proxyStatus;
private View shareButton;
private CircularProgressButton saveButton;
private EditProxyViewModel viewModel;
private SwitchCompat proxySwitch;
private EditText proxyText;
private TextView proxyTitle;
private TextView proxyStatus;
private View shareButton;
private CircularProgressButton saveButton;
private EditProxyViewModel viewModel;
public static EditProxyFragment newInstance() {
return new EditProxyFragment();
@@ -85,7 +84,7 @@ public class EditProxyFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(R.string.preferences_use_proxy);
SignalProxyUtil.startListeningToWebsocket();
}

View File

@@ -1,145 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceDataStore;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.emoji.EmojiFiles;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
import org.thoughtcrime.securesms.keyvalue.InternalValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.payments.DataExportUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
public class InternalOptionsPreferenceFragment extends CorrectedPreferenceFragment {
private static final String TAG = Log.tag(InternalOptionsPreferenceFragment.class);
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_internal);
PreferenceDataStore preferenceDataStore = SignalStore.getPreferenceDataStore();
initializeSwitchPreference(preferenceDataStore, InternalValues.RECIPIENT_DETAILS, SignalStore.internalValues().recipientDetails());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_DO_NOT_CREATE_GV2, SignalStore.internalValues().gv2DoNotCreateGv2Groups());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_FORCE_INVITES, SignalStore.internalValues().gv2ForceInvites());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_IGNORE_SERVER_CHANGES, SignalStore.internalValues().gv2IgnoreServerChanges());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_IGNORE_P2P_CHANGES, SignalStore.internalValues().gv2IgnoreP2PChanges());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_DISABLE_AUTOMIGRATE_INITIATION, SignalStore.internalValues().disableGv1AutoMigrateInitiation());
initializeSwitchPreference(preferenceDataStore, InternalValues.GV2_DISABLE_AUTOMIGRATE_NOTIFICATION, SignalStore.internalValues().disableGv1AutoMigrateNotification());
initializeSwitchPreference(preferenceDataStore, InternalValues.FORCE_CENSORSHIP, SignalStore.internalValues().forcedCensorship());
initializeSwitchPreference(preferenceDataStore, InternalValues.FORCE_BUILT_IN_EMOJI, SignalStore.internalValues().forceBuiltInEmoji());
findPreference("pref_copy_payments_data").setOnPreferenceClickListener(preference -> {
new AlertDialog.Builder(getContext())
.setMessage("Local payments history will be copied to the clipboard.\n" +
"It may therefore compromise privacy.\n" +
"However, no private keys will be copied.")
.setPositiveButton("Copy", (dialog, which) -> {
SimpleTask.run(SignalExecutors.UNBOUNDED,
() -> {
Context context = ApplicationDependencies.getApplication();
android.content.ClipboardManager clipboard =
(android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
String tsv = DataExportUtil.createTsv();
ClipData clip = ClipData.newPlainText(context.getString(R.string.app_name), tsv);
clipboard.setPrimaryClip(clip);
return null;
},
r -> Toast.makeText(getContext(), "Payments have been copied", Toast.LENGTH_SHORT).show()
);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
});
findPreference("pref_refresh_attributes").setOnPreferenceClickListener(preference -> {
ApplicationDependencies.getJobManager()
.startChain(new RefreshAttributesJob())
.then(new RefreshOwnProfileJob())
.enqueue();
Toast.makeText(getContext(), "Scheduled attribute refresh", Toast.LENGTH_SHORT).show();
return true;
});
findPreference("pref_rotate_profile_key").setOnPreferenceClickListener(preference -> {
ApplicationDependencies.getJobManager().add(new RotateProfileKeyJob());
Toast.makeText(getContext(), "Scheduled profile key rotation", Toast.LENGTH_SHORT).show();
return true;
});
findPreference("pref_refresh_remote_values").setOnPreferenceClickListener(preference -> {
ApplicationDependencies.getJobManager().add(new RemoteConfigRefreshJob());
Toast.makeText(getContext(), "Scheduled remote config refresh", Toast.LENGTH_SHORT).show();
return true;
});
findPreference("pref_force_send").setOnPreferenceClickListener(preference -> {
ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
Toast.makeText(getContext(), "Scheduled storage force push", Toast.LENGTH_SHORT).show();
return true;
});
findPreference("pref_delete_dynamic_shortcuts").setOnPreferenceClickListener(preference -> {
ConversationUtil.clearAllShortcuts(requireContext());
Toast.makeText(getContext(), "Deleted all dynamic shortcuts.", Toast.LENGTH_SHORT).show();
return true;
});
}
private void initializeSwitchPreference(@NonNull PreferenceDataStore preferenceDataStore,
@NonNull String key,
boolean checked)
{
SwitchPreferenceCompat forceGv2Preference = (SwitchPreferenceCompat) findPreference(key);
forceGv2Preference.setPreferenceDataStore(preferenceDataStore);
forceGv2Preference.setChecked(checked);
}
@Override
public void onResume() {
super.onResume();
//noinspection ConstantConditions
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__internal_preferences);
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
() -> EmojiFiles.Version.readVersion(requireContext()),
version -> {
if (version != null) {
findPreference(InternalValues.FORCE_BUILT_IN_EMOJI).setSummary(getString(R.string.preferences__internal_current_version_d_at_density_s, version.getVersion(), version.getDensity()));
} else {
findPreference(InternalValues.FORCE_BUILT_IN_EMOJI).setSummary(getString(R.string.preferences__internal_current_version_builtin));
}
});
}
@Override
public void onPause() {
super.onPause();
SignalExecutors.BOUNDED.execute(EmojiSource::refresh);
}
}

Some files were not shown because too many files have changed in this diff Show More