diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3d0ae5ac16..4a7a7a3007 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -355,8 +355,9 @@ - diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java deleted file mode 100644 index 8e4ec84fa3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ /dev/null @@ -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 . - */ -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; - } - } - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java index 231c3fb235..d2a8018c13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java index 47cdd66196..cbc2f603cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java b/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java index 67a2915381..58dd8b6e70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MainNavigator.java @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java index cf6ef6ae74..d71e5d4cf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java index d0f8006a7b..5c92c77529 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupFileIOError.java @@ -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)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java index e7701c1e80..d3a572dda3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index 675e40e760..6892893108 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java index dd7d52d7ce..2a8723326d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiEditText.java @@ -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())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index 75873a9889..78cdd6aab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt new file mode 100644 index 0000000000..30ae44f82d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt @@ -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() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt new file mode 100644 index 0000000000..1a832f5f13 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt @@ -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>(itemView: View) : MappingViewHolder(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(itemView) + +class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder(itemView) { + override fun bind(model: ClickPreference) { + super.bind(model) + itemView.setOnClickListener { model.onClick() } + } +} + +class RadioListPreferenceViewHolder(itemView: View) : PreferenceViewHolder(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(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(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(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(itemView) { + override fun bind(model: DividerPreference) = Unit +} + +class SectionHeaderPreferenceViewHolder(itemView: View) : MappingViewHolder(itemView) { + + private val sectionHeader: TextView = itemView.findViewById(R.id.section_header) + + override fun bind(model: SectionHeaderPreference) { + sectionHeader.text = model.title.resolve(context) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt new file mode 100644 index 0000000000..7897d7fcbb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsText.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsText.kt new file mode 100644 index 0000000000..9457fed596 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsText.kt @@ -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) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt new file mode 100644 index 0000000000..9574c9375c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt @@ -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 + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt new file mode 100644 index 0000000000..9d171ec650 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt @@ -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() { + 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(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() { + override fun areContentsTheSame(newItem: PaymentsPreference): Boolean { + return super.areContentsTheSame(newItem) && unreadCount == newItem.unreadCount + } + } + + private class PaymentsPreferenceViewHolder(itemView: View) : MappingViewHolder(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() + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt new file mode 100644 index 0000000000..84c077a5c3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt new file mode 100644 index 0000000000..d8abc7d213 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt @@ -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.self().live().liveData + + val state: LiveData = LiveDataUtil.combineLatest(unreadPaymentsLiveData, selfLiveData) { payments, self -> + val unreadPaymentsCount = payments.transform { it.unreadCount }.or(0) + + AppSettingsState(self, unreadPaymentsCount) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt new file mode 100644 index 0000000000..f3a891b5f0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt @@ -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() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt new file mode 100644 index 0000000000..76d69cfa41 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.components.settings.app.account + +data class AccountSettingsState( + val hasPin: Boolean, + val pinRemindersEnabled: Boolean, + val registrationLockEnabled: Boolean +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt new file mode 100644 index 0000000000..0af83d8aba --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt @@ -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 = Store(getCurrentState()) + + val state: LiveData = 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 + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt new file mode 100644 index 0000000000..3deec34daf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt @@ -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]) + } + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt new file mode 100644 index 0000000000..8652f59d79 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.components.settings.app.appearance + +data class AppearanceSettingsState( + val theme: String, + val messageFontSize: Int, + val language: String +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt new file mode 100644 index 0000000000..4411e1e925 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt @@ -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 + + init { + val initialState = AppearanceSettingsState( + SignalStore.settings().theme, + SignalStore.settings().messageFontSize, + SignalStore.settings().language + ) + + store = Store(initialState) + } + + val state: LiveData = 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 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt new file mode 100644 index 0000000000..340f8e36a3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt @@ -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) + } + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsRepository.kt new file mode 100644 index 0000000000..e7ee1444d6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsRepository.kt @@ -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) + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsState.kt new file mode 100644 index 0000000000..7f80d1dd07 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsViewModel.kt new file mode 100644 index 0000000000..baa7e0c79a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsViewModel.kt @@ -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 = 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 = 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 create(modelClass: Class): T { + return requireNotNull(modelClass.cast(ChatsSettingsViewModel(repository))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt new file mode 100644 index 0000000000..a54df47451 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsFragment.kt @@ -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) + } + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsState.kt new file mode 100644 index 0000000000..98184b7c9c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsViewModel.kt new file mode 100644 index 0000000000..54faaea65f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/sms/SmsSettingsViewModel.kt @@ -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 = 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())) } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt new file mode 100644 index 0000000000..a8874b8443 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt @@ -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) + } + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt new file mode 100644 index 0000000000..7b43c46d74 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt @@ -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()) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt new file mode 100644 index 0000000000..1731022f79 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt @@ -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, + val wifiAutoDownloadValues: Set, + val roamingAutoDownloadValues: Set, + val callBandwidthMode: CallBandwidthMode, + val isProxyEnabled: Boolean +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt new file mode 100644 index 0000000000..a05ae092f7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt @@ -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 = store.stateLiveData + + fun refresh() { + repository.getTotalStorageUse { totalStorageUse -> + store.update { getState().copy(totalStorageUse = totalStorageUse) } + } + } + + fun setMobileAutoDownloadValues(resultSet: Set) { + sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF, resultSet).apply() + getStateAndCopyStorageUsage() + } + + fun setWifiAutoDownloadValues(resultSet: Set) { + sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF, resultSet).apply() + getStateAndCopyStorageUsage() + } + + fun setRoamingAutoDownloadValues(resultSet: Set) { + 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 create(modelClass: Class): T { + return requireNotNull(modelClass.cast(DataAndStorageSettingsViewModel(sharedPreferences, repository))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt new file mode 100644 index 0000000000..9c7767ade2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt @@ -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)) + } + ) + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt new file mode 100644 index 0000000000..770f0ea610 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -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( + 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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt new file mode 100644 index 0000000000..200822e16d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt @@ -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)) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt new file mode 100644 index 0000000000..036c9bbaea --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt @@ -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? +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt new file mode 100644 index 0000000000..b42fc010ee --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt @@ -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 = 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 create(modelClass: Class): T { + return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt new file mode 100644 index 0000000000..3e194371c0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt @@ -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(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + viewModel.setMessageNotificationsSound(uri) + } else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) { + val uri = data.getParcelableExtra(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, + val radioListPreference: RadioListPreference + ) : PreferenceModel( + 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(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) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt new file mode 100644 index 0000000000..02d52f24c3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt new file mode 100644 index 0000000000..afdb1414bb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt @@ -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 = 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 create(modelClass: Class): T { + return requireNotNull(modelClass.cast(NotificationsSettingsViewModel(sharedPreferences))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt new file mode 100644 index 0000000000..1133124a23 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt @@ -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 = 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 { + val map: MutableMap = 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() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsRepository.kt new file mode 100644 index 0000000000..00ffed8ed0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsRepository.kt @@ -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() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt new file mode 100644 index 0000000000..4bab0dd516 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt new file mode 100644 index 0000000000..63bbe956ea --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt @@ -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 = 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 create(modelClass: Class): T { + return requireNotNull(modelClass.cast(PrivacySettingsViewModel(sharedPreferences, repository))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt new file mode 100644 index 0000000000..b709c19e1a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt @@ -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) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt new file mode 100644 index 0000000000..8d7f972598 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt @@ -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 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt new file mode 100644 index 0000000000..69911d8e15 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt new file mode 100644 index 0000000000..b86a0bb157 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt @@ -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() + + val state: LiveData = store.stateLiveData + val events: LiveData = 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 create(modelClass: Class): T { + return requireNotNull( + modelClass.cast( + AdvancedPrivacySettingsViewModel( + sharedPreferences, + repository + ) + ) + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt new file mode 100644 index 0000000000..81a5b0b177 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt @@ -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() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedAdvancedPinPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedAdvancedPinPreferenceFragment.kt new file mode 100644 index 0000000000..134ceafa82 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedAdvancedPinPreferenceFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedBackupsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedBackupsPreferenceFragment.kt new file mode 100644 index 0000000000..c8fdd15e35 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedBackupsPreferenceFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedDeleteAccountFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedDeleteAccountFragment.kt new file mode 100644 index 0000000000..f07b2a22d1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedDeleteAccountFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedEditProxyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedEditProxyFragment.kt new file mode 100644 index 0000000000..033189ccff --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedEditProxyFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedHelpFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedHelpFragment.kt new file mode 100644 index 0000000000..8fc7d626ef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedHelpFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedMmsPreferencesFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedMmsPreferencesFragment.kt new file mode 100644 index 0000000000..d9e4e60a8a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedMmsPreferencesFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedStoragePreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedStoragePreferenceFragment.kt new file mode 100644 index 0000000000..26749ede65 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/WrappedStoragePreferenceFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt new file mode 100644 index 0000000000..bb389a6a15 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt @@ -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>() + + fun customPref(customPreference: PreferenceModel<*>) { + children.add(customPreference) + } + + fun radioListPref( + title: DSLSettingsText, + @DrawableRes iconId: Int = UNSET, + isEnabled: Boolean = true, + listItems: Array, + 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, + 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>( + open val title: DSLSettingsText? = null, + open val summary: DSLSettingsText? = null, + @DrawableRes open val iconId: Int = UNSET, + open val isEnabled: Boolean = true +) : MappingModel { + 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(title = title, summary = summary) + +class DividerPreference : PreferenceModel() { + 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, + val selected: Int, + val onSelected: (Int) -> Unit +) : PreferenceModel(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, + val selected: BooleanArray, + val onSelected: (BooleanArray) -> Unit +) : PreferenceModel(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(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(title = title, summary = summary, iconId = iconId, isEnabled = isEnabled) + +class ExternalLinkPreference( + override val title: DSLSettingsText, + @DrawableRes override val iconId: Int, + @StringRes val linkId: Int +) : PreferenceModel(title = title, iconId = iconId) + +class SectionHeaderPreference(override val title: DSLSettingsText) : PreferenceModel(title = title) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java index da1985fa9b..1395352b5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaDescriptionCompatFactory.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java index 926ad9735d..7ac939c227 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteNotificationManager.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/TurnOffContactJoinedNotificationsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/TurnOffContactJoinedNotificationsActivity.java index 21650ca38f..37f4017cf3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/TurnOffContactJoinedNotificationsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/TurnOffContactJoinedNotificationsActivity.java @@ -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 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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java index 693012bee7..11648445ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java @@ -463,7 +463,7 @@ public class DirectoryHelper { private static void notifyNewUsers(@NonNull Context context, @NonNull Collection newUsers) { - if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return; + if (!SignalStore.settings().isNotifyWhenContactJoinsSignal()) return; for (RecipientId newUser: newUsers) { Recipient recipient = Recipient.resolved(newUser); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index e9adcf81db..6dc1fa2b4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 2f44bd47ef..ac8efd921b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index e9b5f39ba1..db7be642ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java index 21c2c78862..f8075d8f06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 42eb45013c..54babdfa51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 7786e10e06..f3332e1449 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -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 })) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java index 82c4ee792c..6edb07e6e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/delete/DeleteAccountFragment.java @@ -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 countrySpinnerAdapter; - private TextView bullets; - private LabeledEditText countryCode; - private LabeledEditText number; - private AsYouTypeFormatter countryFormatter; - private DeleteAccountViewModel viewModel; - private DialogInterface deletionProgressDialog; + private ArrayAdapter 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 formattedBalance) { bullets.setText(buildBulletsText(formattedBalance)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferSetupFragment.java b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferSetupFragment.java index f7f593f320..63eaecdb30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferSetupFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferSetupFragment.java @@ -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(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index c1433160f1..def96f8e8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java b/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java index 8046479d0d..4b3ca66b64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java @@ -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 emoji; - private HelpViewModel helpViewModel; - private Spinner categorySpinner; - private ArrayAdapter categoryAdapter; + private EditText problem; + private CheckBox includeDebugLogs; + private View debugLogInfo; + private View faq; + private CircularProgressButton next; + private View toaster; + private List emoji; + private HelpViewModel helpViewModel; + private Spinner categorySpinner; + private ArrayAdapter 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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index 71040dfbfc..e0cb9a95c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -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 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 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, ""); diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java index 95a9f8ac5b..afa4d1a96f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java index c7d1623b06..c11bfd96e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 2c3a0d917a..7f57c104e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java index face7921aa..92a3fefbaf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupNotificationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupNotificationMigrationJob.java index 3379453ba6..b5c776c02b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupNotificationMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupNotificationMigrationJob.java @@ -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))); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java index 29c92e1034..f002209469 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/UserNotificationMigrationJob.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index cfa893a0ae..deeaf2059b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -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")) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 648854a63d..b7d847febe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -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 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 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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index b6a101c088..7ef684e198 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt index 4059db6022..ccb43afac1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt @@ -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 = 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, threadsThatAlerted: Set) { - 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)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index 07e5f7d91a..996bbded54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt index 8552fbe62f..91a4487c40 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index 87a9b91775..98c22fccb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt index 32a21a7c44..aba04d7f58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java index 320c720392..572e661cd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java index b1bf21cfb6..0ed9abb1f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java deleted file mode 100644 index 92a92722fd..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ /dev/null @@ -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 { - 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.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; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java deleted file mode 100644 index 74edc83f55..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ /dev/null @@ -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 items = items(requireContext()); - List 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 items(Context context) { - Map 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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java deleted file mode 100644 index 5200b14e42..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppearancePreferenceFragment.java +++ /dev/null @@ -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]); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index ce92c6c57d..a8d7640fc5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java deleted file mode 100644 index eeefe1da02..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactListItem.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java deleted file mode 100644 index e391626483..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java deleted file mode 100644 index 2a6b5d1c9d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/DataAndStoragePreferenceFragment.java +++ /dev/null @@ -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 allowedNetworks) { - String[] keys = getResources().getStringArray(R.array.pref_media_download_entries); - String[] values = getResources().getStringArray(R.array.pref_media_download_values); - List 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)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); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/EditProxyFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/EditProxyFragment.java index 73b47ce96f..8d11b261a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/EditProxyFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/EditProxyFragment.java @@ -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(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java deleted file mode 100644 index 6bd8fa9317..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/MmsPreferencesFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/MmsPreferencesFragment.java index 2ec79f35e2..5aafdfc431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/MmsPreferencesFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/MmsPreferencesFragment.java @@ -23,7 +23,6 @@ import android.os.Bundle; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.CustomDefaultPreference; import org.thoughtcrime.securesms.database.ApnDatabase; @@ -38,14 +37,6 @@ public class MmsPreferencesFragment extends CorrectedPreferenceFragment { private static final String TAG = Log.tag(MmsPreferencesFragment.class); - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - - ((PassphraseRequiredActivity) getActivity()).getSupportActionBar() - .setTitle(R.string.preferences__advanced_mms_access_point_names); - } - @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences_manual_mms); diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java deleted file mode 100644 index f400d98778..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.thoughtcrime.securesms.preferences; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.Settings; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -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.components.SwitchPreferenceCompat; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.util.RingtoneUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -import static android.app.Activity.RESULT_OK; - -public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment { - - @SuppressWarnings("unused") - private static final String TAG = Log.tag(NotificationsPreferenceFragment.class); - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - - Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF); - - if (NotificationChannels.supported()) { - ledBlinkPref.setVisible(false); - TextSecurePreferences.setNotificationRingtone(getContext(), NotificationChannels.getMessageRingtone(getContext()).toString()); - TextSecurePreferences.setNotificationVibrateEnabled(getContext(), NotificationChannels.getMessageVibrate(getContext())); - - } else { - ledBlinkPref.setOnPreferenceChangeListener(new ListSummaryListener()); - initializeListSummary((ListPreference) ledBlinkPref); - } - - this.findPreference(TextSecurePreferences.LED_COLOR_PREF) - .setOnPreferenceChangeListener(new LedColorChangeListener()); - this.findPreference(TextSecurePreferences.RINGTONE_PREF) - .setOnPreferenceChangeListener(new RingtoneSummaryListener()); - this.findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF) - .setOnPreferenceChangeListener(new ListSummaryListener()); - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF) - .setOnPreferenceChangeListener(new NotificationPrivacyListener()); - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF) - .setOnPreferenceChangeListener(new ListSummaryListener()); - this.findPreference(TextSecurePreferences.CALL_RINGTONE_PREF) - .setOnPreferenceChangeListener(new RingtoneSummaryListener()); - this.findPreference(TextSecurePreferences.VIBRATE_PREF) - .setOnPreferenceChangeListener((preference, newValue) -> { - NotificationChannels.updateMessageVibrate(getContext(), (boolean) newValue); - return true; - }); - - this.findPreference(TextSecurePreferences.RINGTONE_PREF) - .setOnPreferenceClickListener(preference -> { - Uri current = TextSecurePreferences.getNotificationRingtone(getContext()); - - Intent intent = new 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, 1); - - return true; - }); - - this.findPreference(TextSecurePreferences.CALL_RINGTONE_PREF) - .setOnPreferenceClickListener(preference -> { - Uri current = TextSecurePreferences.getCallNotificationRingtone(getContext()); - - Intent intent = new 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, 2); - - return true; - }); - - initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_COLOR_PREF)); - initializeListSummary((ListPreference) findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF)); - initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)); - - if (NotificationChannels.supported()) { - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF) - .setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(getContext())); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName()); - startActivity(intent); - return true; - }); - } else { - initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)); - } - - initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)); - initializeCallRingtoneSummary(findPreference(TextSecurePreferences.CALL_RINGTONE_PREF)); - initializeMessageVibrateSummary((SwitchPreferenceCompat)findPreference(TextSecurePreferences.VIBRATE_PREF)); - initializeCallVibrateSummary((SwitchPreferenceCompat)findPreference(TextSecurePreferences.CALL_VIBRATE_PREF)); - } - - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_notifications); - } - - @Override - public void onResume() { - super.onResume(); - ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__notifications); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 1 && resultCode == RESULT_OK && data != null) { - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - - if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)) { - NotificationChannels.updateMessageRingtone(getContext(), uri); - TextSecurePreferences.removeNotificationRingtone(getContext()); - } else { - uri = uri == null ? Uri.EMPTY : uri; - NotificationChannels.updateMessageRingtone(getContext(), uri ); - TextSecurePreferences.setNotificationRingtone(getContext(), uri.toString()); - } - - initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)); - } else if (requestCode == 2 && resultCode == RESULT_OK && data != null) { - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - - if (Settings.System.DEFAULT_RINGTONE_URI.equals(uri)) { - TextSecurePreferences.removeCallNotificationRingtone(getContext()); - } else { - TextSecurePreferences.setCallNotificationRingtone(getContext(), uri != null ? uri.toString() : Uri.EMPTY.toString()); - } - - initializeCallRingtoneSummary(findPreference(TextSecurePreferences.CALL_RINGTONE_PREF)); - } - } - - private class RingtoneSummaryListener implements Preference.OnPreferenceChangeListener { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Uri value = (Uri) newValue; - - if (value == null || TextUtils.isEmpty(value.toString())) { - preference.setSummary(R.string.preferences__silent); - } else { - Ringtone tone = RingtoneUtil.getRingtone(requireContext(), value); - - if (tone != null) { - preference.setSummary(tone.getTitle(getActivity())); - } else { - preference.setSummary(R.string.preferences__default); - } - } - - return true; - } - } - - private void initializeRingtoneSummary(Preference pref) { - RingtoneSummaryListener listener = (RingtoneSummaryListener) pref.getOnPreferenceChangeListener(); - Uri uri = TextSecurePreferences.getNotificationRingtone(getContext()); - - listener.onPreferenceChange(pref, uri); - } - - private void initializeCallRingtoneSummary(Preference pref) { - RingtoneSummaryListener listener = (RingtoneSummaryListener) pref.getOnPreferenceChangeListener(); - Uri uri = TextSecurePreferences.getCallNotificationRingtone(getContext()); - - listener.onPreferenceChange(pref, uri); - } - - private void initializeMessageVibrateSummary(SwitchPreferenceCompat pref) { - pref.setChecked(TextSecurePreferences.isNotificationVibrateEnabled(getContext())); - } - - private void initializeCallVibrateSummary(SwitchPreferenceCompat pref) { - pref.setChecked(TextSecurePreferences.isCallNotificationVibrateEnabled(getContext())); - } - - public static CharSequence getSummary(Context context) { - final int onCapsResId = R.string.ApplicationPreferencesActivity_On; - final int offCapsResId = R.string.ApplicationPreferencesActivity_Off; - - return context.getString(TextSecurePreferences.isNotificationsEnabled(context) ? onCapsResId : offCapsResId); - } - - private class NotificationPrivacyListener extends ListSummaryListener { - @SuppressLint("StaticFieldLeak") - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - ApplicationDependencies.getMessageNotifier().updateNotification(getActivity()); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - return super.onPreferenceChange(preference, value); - } - } - - @SuppressLint("StaticFieldLeak") - private class LedColorChangeListener extends ListSummaryListener { - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - if (NotificationChannels.supported()) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... voids) { - NotificationChannels.updateMessagesLedColor(getActivity(), (String) value); - return null; - } - }.execute(); - } - return super.onPreferenceChange(preference, value); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SmsMmsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/SmsMmsPreferenceFragment.java deleted file mode 100644 index c5f477236d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SmsMmsPreferenceFragment.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.thoughtcrime.securesms.preferences; - -import android.content.Context; -import android.content.Intent; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.provider.Settings; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.SmsUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; - -public class SmsMmsPreferenceFragment extends CorrectedPreferenceFragment { - private static final String KITKAT_DEFAULT_PREF = "pref_set_default"; - private static final String MMS_PREF = "pref_mms_preferences"; - private static final short SMS_ROLE_REQUEST_CODE = 1234; - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - - - this.findPreference(MMS_PREF) - .setOnPreferenceClickListener(new ApnPreferencesClickListener()); - - initializePlatformSpecificOptions(); - } - - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_sms_mms); - } - - @Override - public void onResume() { - super.onResume(); - ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__sms_mms); - - initializeDefaultPreference(); - } - - private void initializePlatformSpecificOptions() { - PreferenceScreen preferenceScreen = getPreferenceScreen(); - Preference defaultPreference = findPreference(KITKAT_DEFAULT_PREF); - Preference allSmsPreference = findPreference(TextSecurePreferences.ALL_SMS_PREF); - Preference allMmsPreference = findPreference(TextSecurePreferences.ALL_MMS_PREF); - Preference manualMmsPreference = findPreference(MMS_PREF); - - if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - if (allSmsPreference != null) preferenceScreen.removePreference(allSmsPreference); - if (allMmsPreference != null) preferenceScreen.removePreference(allMmsPreference); - } else if (defaultPreference != null) { - preferenceScreen.removePreference(defaultPreference); - } - - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && manualMmsPreference != null) { - preferenceScreen.removePreference(manualMmsPreference); - } - } - - private void initializeDefaultPreference() { - Preference defaultPreference = findPreference(KITKAT_DEFAULT_PREF); - if (Util.isDefaultSmsProvider(getActivity())) { - defaultPreference.setOnPreferenceClickListener(null); - - if (VERSION.SDK_INT < VERSION_CODES.M) defaultPreference.setIntent(new Intent(Settings.ACTION_WIRELESS_SETTINGS)); - if (VERSION.SDK_INT < VERSION_CODES.N) defaultPreference.setIntent(new Intent(Settings.ACTION_SETTINGS)); - else defaultPreference.setIntent(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)); - - defaultPreference.setTitle(getString(R.string.ApplicationPreferencesActivity_sms_enabled)); - defaultPreference.setSummary(getString(R.string.ApplicationPreferencesActivity_touch_to_change_your_default_sms_app)); - } else { - defaultPreference.setTitle(getString(R.string.ApplicationPreferencesActivity_sms_disabled)); - defaultPreference.setSummary(getString(R.string.ApplicationPreferencesActivity_touch_to_make_signal_your_default_sms_app)); - - defaultPreference.setOnPreferenceClickListener(preference -> { - startActivityForResult(SmsUtil.getSmsRoleIntent(requireContext()), SMS_ROLE_REQUEST_CODE); - return true; - }); - } - } - - private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener { - - @Override - public boolean onPreferenceClick(Preference preference) { - Fragment fragment = new MmsPreferencesFragment(); - FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); - FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - fragmentTransaction.replace(android.R.id.content, fragment); - fragmentTransaction.addToBackStack(null); - fragmentTransaction.commit(); - - return true; - } - } - - public static CharSequence getSummary(Context context) { - final String on = context.getString(R.string.ApplicationPreferencesActivity_on); - final String onCaps = context.getString(R.string.ApplicationPreferencesActivity_On); - final String off = context.getString(R.string.ApplicationPreferencesActivity_off); - final String offCaps = context.getString(R.string.ApplicationPreferencesActivity_Off); - final int smsMmsSummaryResId = R.string.ApplicationPreferencesActivity_sms_mms_summary; - - boolean postKitkatSMS = Util.isDefaultSmsProvider(context); - boolean preKitkatSMS = TextSecurePreferences.isInterceptAllSmsEnabled(context); - boolean preKitkatMMS = TextSecurePreferences.isInterceptAllMmsEnabled(context); - - if (postKitkatSMS) return onCaps; - else return offCaps; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java index 6b94870b4d..b18e4455e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java @@ -11,19 +11,21 @@ import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import com.annimon.stream.Stream; import org.signal.core.util.concurrent.SignalExecutors; -import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.settings.BaseSettingsAdapter; import org.thoughtcrime.securesms.components.settings.BaseSettingsFragment; import org.thoughtcrime.securesms.components.settings.CustomizableSingleSelectSetting; import org.thoughtcrime.securesms.components.settings.SingleSelectSetting; +import org.thoughtcrime.securesms.components.settings.app.wrapped.SettingsWrapperFragment; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -52,15 +54,15 @@ public class StoragePreferenceFragment extends ListSummaryPreferenceFragment { trimLength = findPreference(SettingsValues.THREAD_TRIM_LENGTH); trimLength.setOnPreferenceClickListener(p -> { - getApplicationPreferencesActivity().requireSupportActionBar().setTitle(R.string.preferences__conversation_length_limit); - getApplicationPreferencesActivity().pushFragment(BaseSettingsFragment.create(new ConversationLengthLimitConfiguration())); + updateToolbarTitle(R.string.preferences__conversation_length_limit); + pushFragment(BaseSettingsFragment.create(new ConversationLengthLimitConfiguration())); return true; }); keepMessages = findPreference(SettingsValues.KEEP_MESSAGES_DURATION); keepMessages.setOnPreferenceClickListener(p -> { - getApplicationPreferencesActivity().requireSupportActionBar().setTitle(R.string.preferences__keep_messages); - getApplicationPreferencesActivity().pushFragment(BaseSettingsFragment.create(new KeepMessagesConfiguration())); + updateToolbarTitle(R.string.preferences__keep_messages); + pushFragment(BaseSettingsFragment.create(new KeepMessagesConfiguration())); return true; }); @@ -81,7 +83,7 @@ public class StoragePreferenceFragment extends ListSummaryPreferenceFragment { @Override public void onResume() { super.onResume(); - ((ApplicationPreferencesActivity) requireActivity()).requireSupportActionBar().setTitle(R.string.preferences__storage); + updateToolbarTitle(R.string.preferences__storage); FragmentActivity activity = requireActivity(); ApplicationPreferencesViewModel viewModel = ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(activity); @@ -99,8 +101,17 @@ public class StoragePreferenceFragment extends ListSummaryPreferenceFragment { Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } - private @NonNull ApplicationPreferencesActivity getApplicationPreferencesActivity() { - return (ApplicationPreferencesActivity) requireActivity(); + private void updateToolbarTitle(@StringRes int title) { + if (getParentFragment() instanceof SettingsWrapperFragment) { + ((SettingsWrapperFragment) getParentFragment()).setTitle(title); + } + } + + private void pushFragment(@NonNull Fragment fragment) { + getParentFragmentManager().beginTransaction() + .replace(R.id.wrapped_fragment, fragment) + .addToBackStack(null) + .commit(); } private class ClearMessageHistoryClickListener implements Preference.OnPreferenceClickListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java deleted file mode 100644 index 8369a17741..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - - -import android.content.Context; -import android.graphics.PorterDuff; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class ContactPreference extends Preference { - - private ImageView messageButton; - private ImageView callButton; - private ImageView secureCallButton; - private ImageView secureVideoButton; - private View itemView; - - private Listener listener; - private boolean secure; - private boolean blocked; - - public ContactPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public ContactPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public ContactPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public ContactPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setWidgetLayoutResource(R.layout.recipient_preference_contact_widget); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - this.itemView = view.itemView; - this.messageButton = (ImageView) view.findViewById(R.id.message); - this.callButton = (ImageView) view.findViewById(R.id.call); - this.secureCallButton = (ImageView) view.findViewById(R.id.secure_call); - this.secureVideoButton = (ImageView) view.findViewById(R.id.secure_video); - - if (listener != null) setListener(listener); - setState(secure, blocked); - } - - public void setState(boolean secure, boolean blocked) { - this.secure = secure; - - if (secureCallButton != null) secureCallButton.setVisibility(secure && !blocked ? View.VISIBLE : View.GONE); - if (secureVideoButton != null) secureVideoButton.setVisibility(secure && !blocked ? View.VISIBLE : View.GONE); - if (callButton != null) callButton.setVisibility(secure || blocked ? View.GONE : View.VISIBLE); - if (messageButton != null) messageButton.setVisibility(blocked ? View.GONE : View.VISIBLE); - - int color; - - if (secure) { - color = getContext().getResources().getColor(R.color.core_ultramarine); - } else { - color = getContext().getResources().getColor(R.color.grey_600); - } - - if (messageButton != null) messageButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - if (secureCallButton != null) secureCallButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - if (secureVideoButton != null) secureVideoButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - if (callButton != null) callButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - - public void setListener(Listener listener) { - this.listener = listener; - - if (this.messageButton != null) this.messageButton.setOnClickListener(v -> listener.onMessageClicked()); - if (this.secureCallButton != null) this.secureCallButton.setOnClickListener(v -> listener.onSecureCallClicked()); - if (this.secureVideoButton != null) this.secureVideoButton.setOnClickListener(v -> listener.onSecureVideoClicked()); - if (this.callButton != null) this.callButton.setOnClickListener(v -> listener.onInSecureCallClicked()); - - if (this.itemView != null) { - itemView.setOnLongClickListener(v -> { - listener.onLongClick(); - return true; - }); - } - } - - public interface Listener { - void onMessageClicked(); - void onSecureCallClicked(); - void onSecureVideoClicked(); - void onInSecureCallClicked(); - void onLongClick(); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java deleted file mode 100644 index 7bfaabc0d1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/LEDColorListPreference.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (C) 2017 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 . - */ -package org.thoughtcrime.securesms.preferences.widgets; - -import android.content.Context; -import android.graphics.drawable.GradientDrawable; -import android.util.AttributeSet; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.preference.ListPreference; -import androidx.preference.PreferenceViewHolder; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.R; - -/** - * List preference that disables dependents when set to "none", similar to a CheckBoxPreference. - * - * @author Taylor Kline - */ - -public class LEDColorListPreference extends ListPreference { - - private static final String TAG = Log.tag(LEDColorListPreference.class); - - private ImageView colorImageView; - - public LEDColorListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setWidgetLayoutResource(R.layout.led_color_preference_widget); - } - - public LEDColorListPreference(Context context) { - super(context); - setWidgetLayoutResource(R.layout.led_color_preference_widget); - } - - @Override - public void setValue(String value) { - CharSequence oldEntry = getEntry(); - super.setValue(value); - CharSequence newEntry = getEntry(); - if (oldEntry != newEntry) { - notifyDependencyChange(shouldDisableDependents()); - } - - if (value != null) setPreviewColor(value); - } - - @Override - public boolean shouldDisableDependents() { - CharSequence newEntry = getValue(); - boolean shouldDisable = newEntry.equals("none"); - return shouldDisable || super.shouldDisableDependents(); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - this.colorImageView = (ImageView)view.findViewById(R.id.color_view); - setPreviewColor(getValue()); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(null); - } - - private void setPreviewColor(@NonNull String value) { - int color; - - switch (value) { - case "green": color = getContext().getResources().getColor(R.color.green_500); break; - case "red": color = getContext().getResources().getColor(R.color.red_500); break; - case "blue": color = getContext().getResources().getColor(R.color.blue_500); break; - case "yellow": color = getContext().getResources().getColor(R.color.yellow_500); break; - case "cyan": color = getContext().getResources().getColor(R.color.cyan_500); break; - case "magenta": color = getContext().getResources().getColor(R.color.pink_500); break; - case "white": color = getContext().getResources().getColor(R.color.white); break; - default: color = getContext().getResources().getColor(R.color.transparent); break; - } - - if (colorImageView != null) { - GradientDrawable drawable = new GradientDrawable(); - drawable.setShape(GradientDrawable.OVAL); - drawable.setColor(color); - - colorImageView.setImageDrawable(drawable); - } - } - - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/PaymentsPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/PaymentsPreference.java deleted file mode 100644 index 5126503f36..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/PaymentsPreference.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class PaymentsPreference extends Preference { - - private TextView unreadIndicator; - private int unreadCount; - - public PaymentsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public PaymentsPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public PaymentsPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public PaymentsPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setLayoutResource(R.layout.payments_preference); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - - unreadIndicator = holder.itemView.findViewById(R.id.unread_indicator); - - setUnreadCount(unreadCount); - } - - public void setUnreadCount(int unreadCount) { - this.unreadCount = unreadCount; - - if (unreadIndicator != null) { - unreadIndicator.setVisibility(unreadCount > 0 ? View.VISIBLE : View.GONE); - unreadIndicator.setText(String.valueOf(unreadCount)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java deleted file mode 100644 index 784ef38c39..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - - -import android.content.Context; -import android.os.Build; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.RequiresApi; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.bumptech.glide.load.engine.DiskCacheStrategy; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; -import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; -import org.thoughtcrime.securesms.recipients.Recipient; - -public class ProfilePreference extends Preference { - - private ImageView avatarView; - private TextView profileNameView; - private TextView profileSubtextView; - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public ProfilePreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public ProfilePreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setLayoutResource(R.layout.profile_preference_view); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder viewHolder) { - super.onBindViewHolder(viewHolder); - avatarView = (ImageView)viewHolder.findViewById(R.id.avatar); - profileNameView = (TextView)viewHolder.findViewById(R.id.profile_name); - profileSubtextView = (TextView)viewHolder.findViewById(R.id.number); - - refresh(); - } - - public void refresh() { - if (profileSubtextView == null) return; - - final Recipient self = Recipient.self(); - final String profileName = Recipient.self().getProfileName().toString(); - - GlideApp.with(getContext().getApplicationContext()) - .load(new ProfileContactPhoto(self, self.getProfileAvatar())) - .error(new ResourceContactPhoto(R.drawable.ic_camera_solid_white_24).asDrawable(getContext(), getContext().getResources().getColor(R.color.grey_400))) - .circleCrop() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(avatarView); - - if (!TextUtils.isEmpty(profileName)) { - profileNameView.setText(profileName); - } - - profileSubtextView.setText(self.getUsername().transform(username -> "@" + username).or(self.getE164().transform(PhoneNumberFormatter::prettyPrint)).orNull()); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProgressPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProgressPreference.java deleted file mode 100644 index 25591f6196..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/ProgressPreference.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class ProgressPreference extends Preference { - - private View container; - private TextView progressText; - - public ProgressPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public ProgressPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public ProgressPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public ProgressPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setWidgetLayoutResource(R.layout.preference_widget_progress); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - this.container = view.findViewById(R.id.container); - this.progressText = (TextView) view.findViewById(R.id.progress_text); - - this.container.setVisibility(View.GONE); - } - - public void setProgress(int count) { - container.setVisibility(View.VISIBLE); - progressText.setText(getContext().getString(R.string.ProgressPreference_d_messages_so_far, count)); - } - - public void setProgressVisible(boolean visible) { - container.setVisibility(visible ? View.VISIBLE : View.GONE); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java deleted file mode 100644 index 10a8294399..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - - -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.widget.TextView; - -import androidx.annotation.RequiresApi; -import androidx.preference.ListPreference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class SignalListPreference extends ListPreference { - - private TextView rightSummary; - private CharSequence summary; - private OnPreferenceClickListener clickListener; - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public SignalListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public SignalListPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setWidgetLayoutResource(R.layout.preference_right_summary_widget); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - this.rightSummary = (TextView)view.findViewById(R.id.right_summary); - setSummary(this.summary); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(null); - - this.summary = summary; - - if (this.rightSummary != null) { - this.rightSummary.setText(summary); - } - } - - @Override - public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) { - this.clickListener = onPreferenceClickListener; - } - - @Override - protected void onClick() { - if (clickListener == null || !clickListener.onPreferenceClick(this)) { - super.onClick(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalPreference.java deleted file mode 100644 index 9be8af157c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalPreference.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextView; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class SignalPreference extends Preference { - - private TextView rightSummary; - private CharSequence summary; - - public SignalPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public SignalPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public SignalPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public SignalPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setWidgetLayoutResource(R.layout.preference_right_summary_widget); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - this.rightSummary = (TextView)view.findViewById(R.id.right_summary); - setSummary(this.summary); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(null); - - this.summary = summary; - - if (this.rightSummary != null) { - this.rightSummary.setText(summary); - } - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/UsernamePreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/UsernamePreference.java deleted file mode 100644 index 5b70d93c0b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/UsernamePreference.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.thoughtcrime.securesms.preferences.widgets; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import org.thoughtcrime.securesms.R; - -public class UsernamePreference extends Preference { - - private View.OnLongClickListener onLongClickListener; - - public UsernamePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - public UsernamePreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public UsernamePreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public UsernamePreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setLayoutResource(R.layout.preference_username); - } - - public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) { - this.onLongClickListener = onLongClickListener; - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - holder.itemView.setOnLongClickListener(onLongClickListener); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java index 206721ca54..ecb7be34f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java @@ -27,6 +27,7 @@ import com.annimon.stream.function.Consumer; 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.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.RingtoneUtil; @@ -255,8 +256,8 @@ public class CustomNotificationsDialogFragment extends DialogFragment { private Uri defaultSound(boolean calls) { Uri defaultValue; - if (calls) defaultValue = TextSecurePreferences.getCallNotificationRingtone(requireContext()); - else defaultValue = TextSecurePreferences.getNotificationRingtone(requireContext()); + if (calls) defaultValue = SignalStore.settings().getCallRingtone(); + else defaultValue = SignalStore.settings().getMessageNotificationSound(); return defaultValue; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java index 9925bb28d2..837b61f7a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java @@ -10,6 +10,7 @@ import androidx.annotation.WorkerThread; import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -67,7 +68,7 @@ class CustomNotificationsRepository { void setMessageSound(@Nullable Uri sound) { SignalExecutors.SERIAL.execute(() -> { Recipient recipient = getRecipient(); - Uri defaultValue = TextSecurePreferences.getNotificationRingtone(context); + Uri defaultValue = SignalStore.settings().getMessageNotificationSound(); Uri newValue; if (defaultValue.equals(sound)) newValue = null; @@ -81,7 +82,7 @@ class CustomNotificationsRepository { void setCallSound(@Nullable Uri sound) { SignalExecutors.SERIAL.execute(() -> { - Uri defaultValue = TextSecurePreferences.getCallNotificationRingtone(context); + Uri defaultValue = SignalStore.settings().getCallRingtone(); Uri newValue; if (defaultValue.equals(sound)) newValue = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java index 90c0caa695..b7462cfc21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java @@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModelProvider; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -43,7 +44,7 @@ public final class CustomNotificationsViewModel extends ViewModel { switch (vibrateState) { case DISABLED: return false; case ENABLED : return true; - case DEFAULT : return TextSecurePreferences.isNotificationVibrateEnabled(ApplicationDependencies.getApplication()); + case DEFAULT : return SignalStore.settings().isMessageVibrateEnabled(); default : throw new AssertionError(); } }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RestoreBackupFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RestoreBackupFragment.java index fa1be64559..ef2dc17bc1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RestoreBackupFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RestoreBackupFragment.java @@ -101,7 +101,7 @@ public final class RestoreBackupFragment extends BaseRegistrationFragment { return; } - if (TextSecurePreferences.isBackupEnabled(requireContext())) { + if (SignalStore.settings().isBackupEnabled()) { Log.i(TAG, "Backups enabled, so a backup must have been previously restored."); Navigation.findNavController(view) .navigate(RestoreBackupFragmentDirections.actionSkipNoReturn()); @@ -329,7 +329,7 @@ public final class RestoreBackupFragment extends BaseRegistrationFragment { private void enableBackups(@NonNull Context context) { if (BackupUtil.canUserAccessBackupDirectory(context)) { LocalBackupListener.setNextBackupTimeToIntervalFromNow(context); - TextSecurePreferences.setBackupEnabled(context, true); + SignalStore.settings().setBackupEnabled(true); LocalBackupListener.schedule(context); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java index e922c9f883..82ab7c6d41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java @@ -31,6 +31,7 @@ import org.signal.core.util.logging.Log; import org.signal.devicetransfer.DeviceToDeviceTransferService; import org.signal.devicetransfer.TransferStatus; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.util.BackupUtil; @@ -228,7 +229,7 @@ public final class WelcomeFragment extends BaseRegistrationFragment { private boolean canUserSelectBackup() { return BackupUtil.isUserSelectionRequired(requireContext()) && !isReregister() && - !TextSecurePreferences.isBackupEnabled(requireContext()); + !SignalStore.settings().isBackupEnabled(); } @SuppressLint("NewApi") diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java index a7587c7df9..ebab241c80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -7,6 +7,7 @@ import android.content.Intent; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.jobs.LocalBackupJob; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.concurrent.TimeUnit; @@ -22,7 +23,7 @@ public class LocalBackupListener extends PersistentAlarmManagerListener { @Override protected long onAlarm(Context context, long scheduledTime) { - if (TextSecurePreferences.isBackupEnabled(context)) { + if (SignalStore.settings().isBackupEnabled()) { LocalBackupJob.enqueue(false); } @@ -30,7 +31,7 @@ public class LocalBackupListener extends PersistentAlarmManagerListener { } public static void schedule(Context context) { - if (TextSecurePreferences.isBackupEnabled(context)) { + if (SignalStore.settings().isBackupEnabled()) { new LocalBackupListener().onReceive(context, new Intent()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index 728417bbd8..a8c41cc61b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.ringrtc.CallState; @@ -165,15 +166,15 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { } webRtcInteractor.initializeAudioForCall(); - if (shouldDisturbUserWithCall && TextSecurePreferences.isCallNotificationsEnabled(context)) { + if (shouldDisturbUserWithCall && SignalStore.settings().isCallNotificationsEnabled()) { Uri ringtone = recipient.resolve().getCallRingtone(); RecipientDatabase.VibrateState vibrateState = recipient.resolve().getCallVibrate(); if (ringtone == null) { - ringtone = TextSecurePreferences.getCallNotificationRingtone(context); + ringtone = SignalStore.settings().getCallRingtone(); } - webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientDatabase.VibrateState.ENABLED || (vibrateState == RecipientDatabase.VibrateState.DEFAULT && TextSecurePreferences.isCallNotificationVibrateEnabled(context))); + webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientDatabase.VibrateState.ENABLED || (vibrateState == RecipientDatabase.VibrateState.DEFAULT && SignalStore.settings().isCallVibrateEnabled())); } webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java index f8277bb1a4..b3cb0bc7ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareActivity.java @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.recipients.Recipient; @@ -58,7 +59,6 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; @@ -213,7 +213,7 @@ public class ShareActivity extends PassphraseRequiredActivity if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { int mode = DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_SELF | DisplayMode.FLAG_HIDE_NEW; - if (TextSecurePreferences.isSmsEnabled(this)) { + if (Util.isDefaultSmsProvider(this)) { mode |= DisplayMode.FLAG_SMS; } @@ -291,7 +291,7 @@ public class ShareActivity extends PassphraseRequiredActivity return; } - if (TextSecurePreferences.isSmsEnabled(this) && (displayMode & DisplayMode.FLAG_SMS) == 0) { + if (Util.isDefaultSmsProvider(this) && (displayMode & DisplayMode.FLAG_SMS) == 0) { getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode | DisplayMode.FLAG_SMS); contactsFragment.setQueryFilter(null); } @@ -436,7 +436,7 @@ public class ShareActivity extends PassphraseRequiredActivity if (mode == -1) return; - boolean isMmsOrSmsSupported = data != null ? data.isMmsOrSmsSupported() : TextSecurePreferences.isSmsEnabled(this); + boolean isMmsOrSmsSupported = data != null ? data.isMmsOrSmsSupported() : Util.isDefaultSmsProvider(this); mode = isMmsOrSmsSupported ? mode | DisplayMode.FLAG_SMS : mode & ~DisplayMode.FLAG_SMS; getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, mode); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java index ec9e68616b..64d0015018 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareRepository.java @@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.UriUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -130,7 +129,7 @@ class ShareRepository { private boolean isMmsSupported(@NonNull Context context, @NonNull Attachment attachment) { boolean canReadPhoneState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; - if (!TextSecurePreferences.isSmsEnabled(context) || !canReadPhoneState || !Util.isMmsCapable(context)) { + if (!Util.isDefaultSmsProvider(context) || !canReadPhoneState || !Util.isMmsCapable(context)) { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityTransitionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ActivityTransitionUtil.java deleted file mode 100644 index 60027f7aaa..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ActivityTransitionUtil.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import androidx.activity.ComponentActivity; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.R; - -public final class ActivityTransitionUtil { - - private ActivityTransitionUtil() {} - - /** - * To be used with finish - */ - public static void setSlideOutTransition(@NonNull ComponentActivity activity) { - activity.overridePendingTransition(R.anim.slide_from_start, R.anim.slide_to_end); - } - - /** - * To be used with startActivity - */ - public static void setSlideInTransition(@NonNull ComponentActivity activity) { - activity.overridePendingTransition(R.anim.slide_from_end, R.anim.slide_to_start); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java index 9a4690b72b..147d009f47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java @@ -103,7 +103,7 @@ public class BackupUtil { public static void disableBackups(@NonNull Context context) { BackupPassphrase.set(context, null); - TextSecurePreferences.setBackupEnabled(context, false); + SignalStore.settings().setBackupEnabled(false); BackupUtil.deleteAllBackups(); if (BackupUtil.isUserSelectionRequired(context)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java index bfe7360308..7f95cb6ab6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java @@ -17,6 +17,7 @@ import com.annimon.stream.Stream; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationIds; import org.thoughtcrime.securesms.notifications.SingleRecipientNotificationBuilder; @@ -51,7 +52,7 @@ public final class BubbleUtil { return false; } - NotificationPrivacyPreference privacyPreference = TextSecurePreferences.getNotificationPrivacy(context); + NotificationPrivacyPreference privacyPreference = SignalStore.settings().getMessageNotificationsPrivacy(); if (!privacyPreference.isDisplayContact()) { Log.i(TAG, "Bubbles are not available when notification privacy settings are enabled."); return false; @@ -90,7 +91,7 @@ public final class BubbleUtil { ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, BubbleState.SHOWN); } else { Recipient recipient = Recipient.resolved(recipientId); - SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context)); + SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, SignalStore.settings().getMessageNotificationsPrivacy()); builder.addMessageBody(recipient, recipient, "", System.currentTimeMillis(), null); builder.setThread(recipient); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DynamicLanguage.java b/app/src/main/java/org/thoughtcrime/securesms/util/DynamicLanguage.java index 2909e84ca6..61bf32434e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DynamicLanguage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DynamicLanguage.java @@ -5,6 +5,7 @@ import android.app.Service; import android.content.Context; import android.content.res.Configuration; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.dynamiclanguage.LanguageString; import java.util.Locale; @@ -45,7 +46,7 @@ public class DynamicLanguage { } private static Locale getSelectedLocale(Context context) { - Locale locale = LanguageString.parseLocale(TextSecurePreferences.getLanguage(context)); + Locale locale = LanguageString.parseLocale(SignalStore.settings().getLanguage()); if (locale == null) { return Locale.getDefault(); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DynamicTheme.java b/app/src/main/java/org/thoughtcrime/securesms/util/DynamicTheme.java index 4dfa1b162d..77a5995691 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DynamicTheme.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DynamicTheme.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.keyvalue.SignalStore; public class DynamicTheme { @@ -54,7 +55,7 @@ public class DynamicTheme { } public static void setDefaultDayNightMode(@NonNull Context context) { - String theme = TextSecurePreferences.getTheme(context); + String theme = SignalStore.settings().getTheme(); if (theme.equals(SYSTEM)) { Log.d(TAG, "Setting to follow system expecting: " + ConfigurationUtil.getNightModeConfiguration(context.getApplicationContext())); @@ -74,7 +75,7 @@ public class DynamicTheme { * Takes the system theme into account. */ public static boolean isDarkTheme(@NonNull Context context) { - String theme = TextSecurePreferences.getTheme(context); + String theme = SignalStore.settings().getTheme(); if (theme.equals(SYSTEM) && systemThemeAvailable()) { return isSystemInDarkTheme(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ServiceUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ServiceUtil.java index 67baf5b2ba..86b2158a1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ServiceUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ServiceUtil.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util; import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.KeyguardManager; import android.app.NotificationManager; import android.app.job.JobScheduler; import android.content.ClipboardManager; @@ -27,6 +28,8 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; +import org.jetbrains.annotations.NotNull; + public class ServiceUtil { public static InputMethodManager getInputMethodManager(Context context) { return (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); @@ -106,4 +109,8 @@ public class ServiceUtil { public static LocationManager getLocationManager(@NonNull Context context) { return ContextCompat.getSystemService(context, LocationManager.class); } + + public static KeyguardManager getKeyguardManager(@NotNull Context context) { + return ContextCompat.getSystemService(context, KeyguardManager.class); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java index 060cb5a18c..e100574e78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java @@ -21,6 +21,7 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.view.View; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; @@ -98,13 +99,35 @@ public final class SpanUtil { return imageSpan; } - public static CharSequence clickSubstring(@NonNull Context context, @NonNull CharSequence fullString, @NonNull CharSequence substring, @NonNull View.OnClickListener clickListener) { + public static CharSequence learnMore(@NonNull Context context, + @ColorInt int color, + @NonNull View.OnClickListener onLearnMoreClicked) + { + String learnMore = context.getString(R.string.LearnMoreTextView_learn_more); + return clickSubstring(learnMore, learnMore, onLearnMoreClicked, color); + } + + public static CharSequence clickSubstring(@NonNull Context context, + @NonNull CharSequence fullString, + @NonNull CharSequence substring, + @NonNull View.OnClickListener clickListener) { + return clickSubstring(fullString, + substring, + clickListener, + ContextCompat.getColor(context, R.color.signal_accent_primary)); + } + + public static CharSequence clickSubstring(@NonNull CharSequence fullString, + @NonNull CharSequence substring, + @NonNull View.OnClickListener clickListener, + @ColorInt int linkColor) + { ClickableSpan clickable = new ClickableSpan() { @Override public void updateDrawState(@NonNull TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); - ds.setColor(ContextCompat.getColor(context, R.color.signal_accent_primary)); + ds.setColor(linkColor); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 8e59afa637..7943c43dbb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.backup.BackupProtos; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; import org.thoughtcrime.securesms.keyvalue.SettingsValues; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; @@ -122,7 +123,7 @@ public class TextSecurePreferences { public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji"; private static final String MULTI_DEVICE_PROVISIONED_PREF = "pref_multi_device"; public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id"; - private static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; + public static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; private static final String PROFILE_NAME_PREF = "pref_profile_name"; private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id"; public static final String READ_RECEIPTS_PREF = "pref_read_receipts"; @@ -283,8 +284,8 @@ public class TextSecurePreferences { } public static void onPostBackupRestore(@NonNull Context context) { - if (NotificationChannels.supported() && PreferenceManager.getDefaultSharedPreferences(context).contains(VIBRATE_PREF)) { - NotificationChannels.updateMessageVibrate(context, isNotificationVibrateEnabled(context)); + if (NotificationChannels.supported()) { + NotificationChannels.updateMessageVibrate(context, SignalStore.settings().isMessageVibrateEnabled()); } } @@ -376,10 +377,7 @@ public class TextSecurePreferences { return getStringPreference(context, ENCRYPTED_BACKUP_PASSPHRASE, null); } - public static void setBackupEnabled(@NonNull Context context, boolean value) { - setBooleanPreference(context, BACKUP_ENABLED, value); - } - + @Deprecated public static boolean isBackupEnabled(@NonNull Context context) { return getBooleanPreference(context, BACKUP_ENABLED, false); } @@ -506,6 +504,9 @@ public class TextSecurePreferences { return Integer.valueOf(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH))); } + /** + * @deprecated Use {@link SettingsValues#getMessageFontSize()} via {@link org.thoughtcrime.securesms.keyvalue.SignalStore} instead. + */ public static int getMessageBodyTextSize(Context context) { return Integer.valueOf(getStringPreference(context, MESSAGE_BODY_TEXT_SIZE_PREF, "16")); } @@ -555,14 +556,11 @@ public class TextSecurePreferences { return getIntegerPreference(context, SIGNED_PREKEY_FAILURE_COUNT_PREF, 0); } + @Deprecated public static NotificationPrivacyPreference getNotificationPrivacy(Context context) { return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); } - public static void setNewContactsNotificationEnabled(Context context, boolean isEnabled) { - setBooleanPreference(context, NEW_CONTACTS_NOTIFICATIONS, isEnabled); - } - public static boolean isNewContactsNotificationEnabled(Context context) { return getBooleanPreference(context, NEW_CONTACTS_NOTIFICATIONS, true); } @@ -591,10 +589,12 @@ public class TextSecurePreferences { setBooleanPreference(context, WEBSOCKET_REGISTERED_PREF, registered); } + @Deprecated public static boolean isWifiSmsEnabled(Context context) { return getBooleanPreference(context, WIFI_SMS_PREF, false); } + @Deprecated public static int getRepeatAlertsCount(Context context) { try { return Integer.parseInt(getStringPreference(context, REPEAT_ALERTS_PREF, "0")); @@ -604,10 +604,6 @@ public class TextSecurePreferences { } } - public static void setRepeatAlertsCount(Context context, int count) { - setStringPreference(context, REPEAT_ALERTS_PREF, String.valueOf(count)); - } - public static boolean isSignedPreKeyRegistered(Context context) { return getBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, false); } @@ -639,10 +635,6 @@ public class TextSecurePreferences { setLongPreference(context, GCM_REGISTRATION_ID_TIME_PREF, timestamp); } - public static boolean isSmsEnabled(Context context) { - return Util.isDefaultSmsProvider(context); - } - public static int getLocalRegistrationId(Context context) { return getIntegerPreference(context, LOCAL_REGISTRATION_ID_PREF, 0); } @@ -651,6 +643,7 @@ public class TextSecurePreferences { setIntegerPrefrence(context, LOCAL_REGISTRATION_ID_PREF, registrationId); } + @Deprecated public static boolean isInThreadNotifications(Context context) { return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true); } @@ -751,6 +744,7 @@ public class TextSecurePreferences { return getBooleanPreference(context, ENTER_PRESENT_PREF, false); } + @Deprecated public static boolean isEnterSendsEnabled(Context context) { return getBooleanPreference(context, ENTER_SENDS_PREF, false); } @@ -901,6 +895,9 @@ public class TextSecurePreferences { setIntegerPrefrence(context, EXPERIENCE_DISMISSED_PREF, versionCode); } + /** + * @deprecated Use {@link SettingsValues#getTheme()} via {@link org.thoughtcrime.securesms.keyvalue.SignalStore} instead. + */ public static String getTheme(Context context) { return getStringPreference(context, THEME_PREF, DynamicTheme.systemThemeAvailable() ? DynamicTheme.SYSTEM : DynamicTheme.LIGHT); } @@ -931,14 +928,21 @@ public class TextSecurePreferences { setIntegerPrefrence(context, PASSPHRASE_TIMEOUT_INTERVAL_PREF, interval); } + /** + * @deprecated Use {@link SettingsValues#getLanguage()} via {@link org.thoughtcrime.securesms.keyvalue.SignalStore} instead. + */ public static String getLanguage(Context context) { return getStringPreference(context, LANGUAGE_PREF, "zz"); } + /** + * @deprecated Use {@link SettingsValues#setLanguage(String)} via {@link org.thoughtcrime.securesms.keyvalue.SignalStore} instead. + */ public static void setLanguage(Context context, String language) { setStringPreference(context, LANGUAGE_PREF, language); } + @Deprecated public static boolean isSmsDeliveryReportsEnabled(Context context) { return getBooleanPreference(context, SMS_DELIVERY_REPORT_PREF, false); } @@ -975,18 +979,17 @@ public class TextSecurePreferences { return getBooleanPreference(context, ALL_SMS_PREF, true); } + @Deprecated public static boolean isNotificationsEnabled(Context context) { return getBooleanPreference(context, NOTIFICATION_PREF, true); } - public static void setCallNotificationsEnabled(Context context, boolean enabled) { - setBooleanPreference(context, CALL_NOTIFICATIONS_PREF, enabled); - } - + @Deprecated public static boolean isCallNotificationsEnabled(Context context) { return getBooleanPreference(context, CALL_NOTIFICATIONS_PREF, true); } + @Deprecated public static @NonNull Uri getNotificationRingtone(Context context) { String result = getStringPreference(context, RINGTONE_PREF, Settings.System.DEFAULT_NOTIFICATION_URI.toString()); @@ -997,6 +1000,7 @@ public class TextSecurePreferences { return Uri.parse(result); } + @Deprecated public static @NonNull Uri getCallNotificationRingtone(Context context) { String result = getStringPreference(context, CALL_RINGTONE_PREF, Settings.System.DEFAULT_RINGTONE_URI.toString()); @@ -1007,44 +1011,28 @@ public class TextSecurePreferences { return Uri.parse(result); } - public static void removeNotificationRingtone(Context context) { - removePreference(context, RINGTONE_PREF); - } - - public static void removeCallNotificationRingtone(Context context) { - removePreference(context, CALL_RINGTONE_PREF); - } - - public static void setNotificationRingtone(Context context, String ringtone) { - setStringPreference(context, RINGTONE_PREF, ringtone); - } - - public static void setCallNotificationRingtone(Context context, String ringtone) { - setStringPreference(context, CALL_RINGTONE_PREF, ringtone); - } - - public static void setNotificationVibrateEnabled(Context context, boolean enabled) { - setBooleanPreference(context, VIBRATE_PREF, enabled); - } - + @Deprecated public static boolean isNotificationVibrateEnabled(Context context) { return getBooleanPreference(context, VIBRATE_PREF, true); } + @Deprecated public static boolean isCallNotificationVibrateEnabled(Context context) { boolean defaultValue = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= 23) { defaultValue = (Settings.System.getInt(context.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 1) == 1); } return getBooleanPreference(context, CALL_VIBRATE_PREF, defaultValue); } + @Deprecated public static String getNotificationLedColor(Context context) { return getStringPreference(context, LED_COLOR_PREF, "blue"); } + @Deprecated public static String getNotificationLedPattern(Context context) { return getStringPreference(context, LED_BLINK_PREF, "500,2000"); } @@ -1057,6 +1045,7 @@ public class TextSecurePreferences { setStringPreference(context, LED_BLINK_PREF_CUSTOM, pattern); } + @Deprecated public static boolean isSystemEmojiPreferred(Context context) { return getBooleanPreference(context, SYSTEM_EMOJI_PREF, false); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/dynamiclanguage/DynamicLanguageContextWrapper.java b/app/src/main/java/org/thoughtcrime/securesms/util/dynamiclanguage/DynamicLanguageContextWrapper.java index 72746f8970..958594be30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/dynamiclanguage/DynamicLanguageContextWrapper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/dynamiclanguage/DynamicLanguageContextWrapper.java @@ -5,6 +5,7 @@ import android.content.res.Configuration; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.Locale; @@ -22,8 +23,9 @@ public final class DynamicLanguageContextWrapper { base.setLocale(newLocale); } + @SuppressWarnings("deprecated") public static @NonNull Locale getUsersSelectedLocale(@NonNull Context context) { - String language = TextSecurePreferences.getLanguage(context); + String language = TextSecurePreferences.getLanguage(context); return LocaleParser.findBestMatchingLocaleForLanguage(language); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/views/LearnMoreTextView.java b/app/src/main/java/org/thoughtcrime/securesms/util/views/LearnMoreTextView.java index 0adf24a1da..9437744031 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/views/LearnMoreTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/views/LearnMoreTextView.java @@ -11,6 +11,7 @@ import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.View; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -26,6 +27,7 @@ public class LearnMoreTextView extends AppCompatTextView { private Spannable link; private boolean visible; private CharSequence baseText; + private int linkColor; public LearnMoreTextView(Context context) { super(context); @@ -40,6 +42,7 @@ public class LearnMoreTextView extends AppCompatTextView { private void init() { setMovementMethod(LinkMovementMethod.getInstance()); setLinkTextInternal(R.string.LearnMoreTextView_learn_more); + setLinkColor(ThemeUtil.getThemedColor(getContext(), R.attr.colorAccent)); visible = true; } @@ -73,13 +76,17 @@ public class LearnMoreTextView extends AppCompatTextView { setOnLinkClickListener(new OpenUrlOnClickListener(url)); } + public void setLinkColor(@ColorInt int linkColor) { + this.linkColor = linkColor; + } + private void setLinkTextInternal(@StringRes int linkText) { ClickableSpan clickable = new ClickableSpan() { @Override public void updateDrawState(@NonNull TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); - ds.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.colorAccent)); + ds.setColor(linkColor); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperActivity.java index b32bfe5777..681c290ba3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperActivity.java @@ -14,7 +14,6 @@ import androidx.navigation.Navigation; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -47,7 +46,6 @@ public final class ChatWallpaperActivity extends PassphraseRequiredActivity { toolbar.setNavigationOnClickListener(unused -> { if (!Navigation.findNavController(this, R.id.nav_host_fragment).popBackStack()) { finish(); - ActivityTransitionUtil.setSlideOutTransition(this); } }); @@ -59,12 +57,6 @@ public final class ChatWallpaperActivity extends PassphraseRequiredActivity { } } - @Override - public void onBackPressed() { - super.onBackPressed(); - ActivityTransitionUtil.setSlideOutTransition(this); - } - @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java index f41901cf0e..5c047694c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java @@ -8,10 +8,7 @@ import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; -import androidx.core.graphics.drawable.DrawableCompat; -import androidx.core.view.ViewCompat; import androidx.viewpager2.widget.ViewPager2; import com.annimon.stream.Stream; @@ -20,7 +17,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FullscreenHelper; @@ -65,7 +61,6 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { toolbar.setNavigationOnClickListener(unused -> { finish(); - ActivityTransitionUtil.setSlideOutTransition(this); }); viewPager.setAdapter(adapter); @@ -95,12 +90,6 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { WindowUtil.setLightNavigationBarFromTheme(this); } - @Override - public void onBackPressed() { - super.onBackPressed(); - ActivityTransitionUtil.setSlideOutTransition(this); - } - @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java index 1dcd2a040e..61b2818dcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java @@ -22,7 +22,6 @@ import com.google.android.flexbox.JustifyContent; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity; public class ChatWallpaperSelectionFragment extends Fragment { @@ -52,7 +51,6 @@ public class ChatWallpaperSelectionFragment extends Fragment { @SuppressWarnings("CodeBlock2Expr") ChatWallpaperSelectionAdapter adapter = new ChatWallpaperSelectionAdapter(chatWallpaper -> { startActivityForResult(ChatWallpaperPreviewActivity.create(requireActivity(), chatWallpaper, viewModel.getRecipientId(), viewModel.getDimInDarkTheme().getValue()), CHOOSE_WALLPAPER); - ActivityTransitionUtil.setSlideInTransition(requireActivity()); }, displayMetrics); flexboxLayoutManager.setJustifyContent(JustifyContent.CENTER); @@ -88,7 +86,6 @@ public class ChatWallpaperSelectionFragment extends Fragment { .ifNecessary() .onAllGranted(() -> { startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER); - ActivityTransitionUtil.setSlideInTransition(requireActivity()); }) .onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT) .show()) diff --git a/app/src/main/res/drawable-night/ic_invite_24.xml b/app/src/main/res/drawable-night/ic_invite_24.xml new file mode 100644 index 0000000000..7128ab738d --- /dev/null +++ b/app/src/main/res/drawable-night/ic_invite_24.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable-night/ic_profile_circle_24.xml b/app/src/main/res/drawable-night/ic_profile_circle_24.xml new file mode 100644 index 0000000000..606e6f13ff --- /dev/null +++ b/app/src/main/res/drawable-night/ic_profile_circle_24.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable-v21/dsl_preference_item_background.xml b/app/src/main/res/drawable-v21/dsl_preference_item_background.xml new file mode 100644 index 0000000000..9fc9970d96 --- /dev/null +++ b/app/src/main/res/drawable-v21/dsl_preference_item_background.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dsl_preference_item_background.xml b/app/src/main/res/drawable/dsl_preference_item_background.xml new file mode 100644 index 0000000000..b17ebdd4fb --- /dev/null +++ b/app/src/main/res/drawable/dsl_preference_item_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_invite_24.xml b/app/src/main/res/drawable/ic_invite_24.xml new file mode 100644 index 0000000000..eda5cb5143 --- /dev/null +++ b/app/src/main/res/drawable/ic_invite_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_profile_circle_24.xml b/app/src/main/res/drawable/ic_profile_circle_24.xml new file mode 100644 index 0000000000..f5c111bdb7 --- /dev/null +++ b/app/src/main/res/drawable/ic_profile_circle_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/bio_preference_item.xml b/app/src/main/res/layout/bio_preference_item.xml new file mode 100644 index 0000000000..7f3b05f042 --- /dev/null +++ b/app/src/main/res/layout/bio_preference_item.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/blocked_users_fragment.xml b/app/src/main/res/layout/blocked_users_fragment.xml index 240144f47d..f88de50f15 100644 --- a/app/src/main/res/layout/blocked_users_fragment.xml +++ b/app/src/main/res/layout/blocked_users_fragment.xml @@ -9,9 +9,9 @@ android:id="@+id/add_blocked_user" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:paddingTop="7dp" + android:layout_marginStart="@dimen/dsl_settings_gutter" + android:layout_marginEnd="@dimen/dsl_settings_gutter" + android:paddingTop="8dp" android:text="@string/BlockedUsersActivity__add_blocked_user" android:textAppearance="@style/Signal.Text.Body" app:layout_constraintEnd_toEndOf="parent" @@ -22,11 +22,13 @@ android:id="@+id/add_blocked_user_description" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:paddingBottom="7dp" + android:layout_marginStart="@dimen/dsl_settings_gutter" + android:layout_marginEnd="@dimen/dsl_settings_gutter" + android:lineSpacingExtra="4sp" + android:paddingBottom="8dp" android:text="@string/BlockedUsersActivity__blocked_users_will" android:textAppearance="@style/TextAppearance.Signal.Body2" + android:textColor="@color/signal_text_secondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/add_blocked_user" /> @@ -45,12 +47,13 @@ android:id="@+id/blocked_users_header" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="29dp" - android:layout_marginEnd="16dp" + android:minHeight="48dp" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingTop="16dp" + android:paddingEnd="@dimen/dsl_settings_gutter" + android:paddingBottom="12dp" android:text="@string/BlockedUsersActivity__blocked_users" - android:textAppearance="@style/TextAppearance.Signal.Body2.Bold" - android:textColor="?colorAccent" + android:textAppearance="@style/TextAppearance.Signal.Body1.Bold" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/device_list_fragment.xml b/app/src/main/res/layout/device_list_fragment.xml index fd3af65d55..456415bb0d 100644 --- a/app/src/main/res/layout/device_list_fragment.xml +++ b/app/src/main/res/layout/device_list_fragment.xml @@ -38,8 +38,9 @@ android:layout_height="match_parent" android:layout_weight="1" android:drawSelectorOnTop="false" - android:paddingStart="16dip" - android:paddingEnd="16dip" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter" + android:divider="@null" tools:visibility="gone"/> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_payments_preference.xml b/app/src/main/res/layout/dsl_payments_preference.xml new file mode 100644 index 0000000000..63ee7992c8 --- /dev/null +++ b/app/src/main/res/layout/dsl_payments_preference.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_preference_item.xml b/app/src/main/res/layout/dsl_preference_item.xml new file mode 100644 index 0000000000..d7c14acb6c --- /dev/null +++ b/app/src/main/res/layout/dsl_preference_item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_section_header.xml b/app/src/main/res/layout/dsl_section_header.xml new file mode 100644 index 0000000000..2a8bcb7449 --- /dev/null +++ b/app/src/main/res/layout/dsl_section_header.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/layout/dsl_settings_activity.xml b/app/src/main/res/layout/dsl_settings_activity.xml new file mode 100644 index 0000000000..c2fe2cfdde --- /dev/null +++ b/app/src/main/res/layout/dsl_settings_activity.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_settings_fragment.xml b/app/src/main/res/layout/dsl_settings_fragment.xml new file mode 100644 index 0000000000..2e2c4bc3de --- /dev/null +++ b/app/src/main/res/layout/dsl_settings_fragment.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_settings_toolbar.xml b/app/src/main/res/layout/dsl_settings_toolbar.xml new file mode 100644 index 0000000000..2758c1ad41 --- /dev/null +++ b/app/src/main/res/layout/dsl_settings_toolbar.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dsl_switch_preference_item.xml b/app/src/main/res/layout/dsl_switch_preference_item.xml new file mode 100644 index 0000000000..8cd77ce595 --- /dev/null +++ b/app/src/main/res/layout/dsl_switch_preference_item.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/manage_profile_fragment.xml b/app/src/main/res/layout/manage_profile_fragment.xml index d9729631e6..ff6b928f82 100644 --- a/app/src/main/res/layout/manage_profile_fragment.xml +++ b/app/src/main/res/layout/manage_profile_fragment.xml @@ -76,8 +76,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:paddingStart="26dp" - android:paddingEnd="26dp" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="?selectableItemBackground" @@ -97,7 +97,7 @@ android:id="@+id/manage_profile_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="26dp" + android:layout_marginStart="24dp" style="@style/Signal.Text.Body" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@id/manage_profile_name_icon" @@ -121,8 +121,8 @@ android:id="@+id/manage_profile_username_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="26dp" - android:paddingEnd="26dp" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="?selectableItemBackground" @@ -142,7 +142,7 @@ android:id="@+id/manage_profile_username" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="26dp" + android:layout_marginStart="24dp" style="@style/Signal.Text.Body" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@id/manage_profile_username_icon" @@ -166,8 +166,8 @@ android:id="@+id/manage_profile_about_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="26dp" - android:paddingEnd="26dp" + android:paddingStart="@dimen/dsl_settings_gutter" + android:paddingEnd="@dimen/dsl_settings_gutter" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="?selectableItemBackground" @@ -187,7 +187,7 @@ android:id="@+id/manage_profile_about" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="26dp" + android:layout_marginStart="24dp" style="@style/Signal.Text.Body" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@id/manage_profile_about_icon" @@ -215,7 +215,7 @@ android:layout_height="wrap_content" android:layout_marginTop="33dp" android:layout_marginBottom="16dp" - android:padding="26dp" + android:padding="@dimen/dsl_settings_gutter" android:text="@string/CreateProfileActivity_signal_profiles_are_end_to_end_encrypted" android:textColor="@color/signal_text_secondary" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/new_device_transfer_instructions_fragment.xml b/app/src/main/res/layout/new_device_transfer_instructions_fragment.xml index 6212fd1c64..d1b1e79c0b 100644 --- a/app/src/main/res/layout/new_device_transfer_instructions_fragment.xml +++ b/app/src/main/res/layout/new_device_transfer_instructions_fragment.xml @@ -63,7 +63,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/transfer_item_spacing" - android:text="@string/NewDeviceTransferInstructions__tap_on_chats" + android:text="@string/NewDeviceTransferInstructions__tap_on_account" android:textAlignment="viewStart" android:textAppearance="@style/TextAppearance.Signal.Body1" app:layout_columnWeight="1" /> diff --git a/app/src/main/res/layout/settings_wrapper_fragment.xml b/app/src/main/res/layout/settings_wrapper_fragment.xml new file mode 100644 index 0000000000..d586d8d0e0 --- /dev/null +++ b/app/src/main/res/layout/settings_wrapper_fragment.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/help_settings.xml b/app/src/main/res/menu/help_settings.xml new file mode 100644 index 0000000000..32cdd53302 --- /dev/null +++ b/app/src/main/res/menu/help_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/app_settings.xml b/app/src/main/res/navigation/app_settings.xml new file mode 100644 index 0000000000..3ba93c1547 --- /dev/null +++ b/app/src/main/res/navigation/app_settings.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/chat_wallpaper.xml b/app/src/main/res/navigation/chat_wallpaper.xml index fec1d0fe4d..1a9d32378b 100644 --- a/app/src/main/res/navigation/chat_wallpaper.xml +++ b/app/src/main/res/navigation/chat_wallpaper.xml @@ -14,10 +14,10 @@ + app:enterAnim="@anim/fragment_open_enter" + app:exitAnim="@anim/fragment_open_exit" + app:popEnterAnim="@anim/fragment_close_enter" + app:popExitAnim="@anim/fragment_close_exit" /> @color/transparent_black_80 @color/transparent + @color/transparent_white_05 @color/transparent_white_15 @color/transparent_white_20 @color/transparent_white_40 @@ -128,4 +129,6 @@ @color/core_grey_55 @color/core_grey_55 + + @color/transparent_white_10 diff --git a/app/src/main/res/values-sw360dp/dimens.xml b/app/src/main/res/values-sw360dp/dimens.xml index 1a3bfe7291..81bed47eb7 100644 --- a/app/src/main/res/values-sw360dp/dimens.xml +++ b/app/src/main/res/values-sw360dp/dimens.xml @@ -16,4 +16,5 @@ 48dp 260dp + 24dp \ No newline at end of file diff --git a/app/src/main/res/values-v21/text_styles.xml b/app/src/main/res/values-v21/text_styles.xml new file mode 100644 index 0000000000..64dff679dd --- /dev/null +++ b/app/src/main/res/values-v21/text_styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index a727dd3183..c768662b5c 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -16,6 +16,8 @@ + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7043990a15..0d08f873dd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -14,6 +14,8 @@ #CC000000 #e6000000 + #0Dffffff + #18ffffff #26ffffff #33ffffff #4Dffffff diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b9f6d3e560..a6505fa079 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -178,4 +178,6 @@ 49dp 32dp + + 16dp diff --git a/app/src/main/res/values/light_colors.xml b/app/src/main/res/values/light_colors.xml index 2819ef94f0..89ace9c5c4 100644 --- a/app/src/main/res/values/light_colors.xml +++ b/app/src/main/res/values/light_colors.xml @@ -49,6 +49,7 @@ @color/transparent_white_80 @color/transparent_black + @color/transparent_black_05 @color/transparent_black_15 @color/transparent_black_20 @color/transparent_black_40 @@ -128,4 +129,6 @@ @color/core_grey_45 @color/core_grey_45 + + @color/transparent_black_10 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eaeaa0cb7b..a6624ea91d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,8 @@ https://signal.org/donate https://support.signal.org/hc/articles/360007059752 https://support.signal.org/hc/articles/360007059752 + https://support.signal.org/ + https://signal.org/legal Yes No @@ -2324,7 +2326,9 @@ If read receipts are disabled, you won\'t be able to see read receipts from others. Typing indicators If typing indicators are disabled, you won\'t be able to see typing indicators from others. - Request keyboard to disable personalized learning. This setting is not a guarantee, and your keyboard may ignore it. + Request keyboard to disable personalized learning. + This setting is not a guarantee, and your keyboard may ignore it. + https://support.signal.org/hc/articles/360055276112 Blocked users When using mobile data When using Wi-Fi @@ -2981,7 +2985,7 @@ 1. Tap on your profile photo in the top left to open Settings 2. - Tap on "Chats" + Tap on "Account" 3. Tap "Transfer Account" and then "Continue" on both devices @@ -3383,6 +3387,51 @@ Slower, more data Photo quality + + Invite your friends + + + Account + You\'ll be asked less frequently over time + Require your Signal PIN to register your phone number with Signal again + + + Keyboard + Enter key sends + + + Use as default SMS app + + + Messages + Calls + Notify when… + Contact joins Signal + + + Blocked + %1$d contacts + Messaging + App security + Block screenshots in the recents list and inside the app + Signal messages and calls, always relay calls, and sealed sender + + + https://signal.org/blog/sealed-sender + Show status icon + Show an icon in message details when they were delivered using sealed sender. + + + Support center + Contact us + Version + Terms & Privacy Policy + Copyright Signal Messenger + Licensed under the GPLv3 + + + Calls + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 935b224843..43d7d43746 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -69,6 +69,14 @@ @style/Signal.Toolbar.Overflow.Light + + + + diff --git a/app/src/main/res/values/text_styles.xml b/app/src/main/res/values/text_styles.xml index be7b716f21..134e509e25 100644 --- a/app/src/main/res/values/text_styles.xml +++ b/app/src/main/res/values/text_styles.xml @@ -173,4 +173,10 @@ @color/signal_text_secondary + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 9813363b72..5b34cd8f44 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -151,6 +151,8 @@ @drawable/ic_arrow_left_24 @drawable/ic_arrow_left_24 + @style/TextSecure.LightActionBar.Settings + @color/signal_icon_tint_primary @drawable/ic_recent_20 @@ -170,7 +172,7 @@ @color/core_grey_90 - + @style/Signal.ThemeOverlay.Dialog.Rounded