mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-27 20:24:32 +01:00
Refactor app settings.
This commit is contained in:
committed by
Greyson Parrelli
parent
a94d77d81e
commit
f2d5ea0391
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package org.thoughtcrime.securesms.components.settings
|
||||
|
||||
import android.text.Spanned
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
class DSLSettingsAdapter : MappingAdapter() {
|
||||
init {
|
||||
registerFactory(ClickPreference::class.java, LayoutFactory(::ClickPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(TextPreference::class.java, LayoutFactory(::TextPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(RadioListPreference::class.java, LayoutFactory(::RadioListPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(MultiSelectListPreference::class.java, LayoutFactory(::MultiSelectListPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(ExternalLinkPreference::class.java, LayoutFactory(::ExternalLinkPreferenceViewHolder, R.layout.dsl_preference_item))
|
||||
registerFactory(DividerPreference::class.java, LayoutFactory(::DividerPreferenceViewHolder, R.layout.dsl_divider_item))
|
||||
registerFactory(SectionHeaderPreference::class.java, LayoutFactory(::SectionHeaderPreferenceViewHolder, R.layout.dsl_section_header))
|
||||
registerFactory(SwitchPreference::class.java, LayoutFactory(::SwitchPreferenceViewHolder, R.layout.dsl_switch_preference_item))
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PreferenceViewHolder<T : PreferenceModel<T>>(itemView: View) : MappingViewHolder<T>(itemView) {
|
||||
protected val iconView: ImageView = itemView.findViewById(R.id.icon)
|
||||
protected val titleView: TextView = itemView.findViewById(R.id.title)
|
||||
protected val summaryView: TextView = itemView.findViewById(R.id.summary)
|
||||
|
||||
@CallSuper
|
||||
override fun bind(model: T) {
|
||||
listOf(itemView, titleView, summaryView).forEach {
|
||||
it.isEnabled = model.isEnabled
|
||||
}
|
||||
|
||||
if (model.iconId != -1) {
|
||||
iconView.setImageResource(model.iconId)
|
||||
iconView.visibility = View.VISIBLE
|
||||
} else {
|
||||
iconView.setImageDrawable(null)
|
||||
iconView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val title = model.title?.resolve(context)
|
||||
if (title != null) {
|
||||
titleView.text = model.title?.resolve(context)
|
||||
titleView.visibility = View.VISIBLE
|
||||
} else {
|
||||
titleView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val summary = model.summary?.resolve(context)
|
||||
if (summary != null) {
|
||||
summaryView.text = summary
|
||||
summaryView.visibility = View.VISIBLE
|
||||
|
||||
val spans = (summaryView.text as? Spanned)?.getSpans(0, summaryView.text.length, ClickableSpan::class.java)
|
||||
if (spans?.isEmpty() == false) {
|
||||
summaryView.movementMethod = LinkMovementMethod.getInstance()
|
||||
} else {
|
||||
summaryView.movementMethod = null
|
||||
}
|
||||
} else {
|
||||
summaryView.visibility = View.GONE
|
||||
summaryView.movementMethod = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextPreferenceViewHolder(itemView: View) : PreferenceViewHolder<TextPreference>(itemView)
|
||||
|
||||
class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ClickPreference>(itemView) {
|
||||
override fun bind(model: ClickPreference) {
|
||||
super.bind(model)
|
||||
itemView.setOnClickListener { model.onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
class RadioListPreferenceViewHolder(itemView: View) : PreferenceViewHolder<RadioListPreference>(itemView) {
|
||||
override fun bind(model: RadioListPreference) {
|
||||
super.bind(model)
|
||||
|
||||
summaryView.visibility = View.VISIBLE
|
||||
summaryView.text = model.listItems[model.selected]
|
||||
|
||||
itemView.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(model.title.resolve(context))
|
||||
.setSingleChoiceItems(model.listItems, model.selected) { dialog, which ->
|
||||
model.onSelected(which)
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectListPreferenceViewHolder(itemView: View) : PreferenceViewHolder<MultiSelectListPreference>(itemView) {
|
||||
override fun bind(model: MultiSelectListPreference) {
|
||||
super.bind(model)
|
||||
|
||||
summaryView.visibility = View.VISIBLE
|
||||
val summaryText = model.selected
|
||||
.mapIndexed { index, isChecked -> if (isChecked) model.listItems[index] else null }
|
||||
.filterNotNull()
|
||||
.joinToString(", ")
|
||||
|
||||
if (summaryText.isEmpty()) {
|
||||
summaryView.setText(R.string.preferences__none)
|
||||
} else {
|
||||
summaryView.text = summaryText
|
||||
}
|
||||
|
||||
val selected = model.selected.copyOf()
|
||||
|
||||
itemView.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(model.title.resolve(context))
|
||||
.setMultiChoiceItems(model.listItems, selected) { _, _, _ ->
|
||||
// Intentionally empty
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||
.setPositiveButton(android.R.string.ok) { d, _ ->
|
||||
model.onSelected(selected)
|
||||
d.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchPreferenceViewHolder(itemView: View) : PreferenceViewHolder<SwitchPreference>(itemView) {
|
||||
|
||||
private val switchWidget: SwitchMaterial = itemView.findViewById(R.id.switch_widget)
|
||||
|
||||
override fun bind(model: SwitchPreference) {
|
||||
super.bind(model)
|
||||
switchWidget.isEnabled = model.isEnabled
|
||||
switchWidget.isChecked = model.isChecked
|
||||
itemView.setOnClickListener {
|
||||
model.onClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalLinkPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ExternalLinkPreference>(itemView) {
|
||||
override fun bind(model: ExternalLinkPreference) {
|
||||
super.bind(model)
|
||||
|
||||
val externalLinkIcon = requireNotNull(ContextCompat.getDrawable(context, R.drawable.ic_open_20))
|
||||
externalLinkIcon.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
|
||||
|
||||
if (itemView.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
titleView.setCompoundDrawables(null, null, externalLinkIcon, null)
|
||||
} else {
|
||||
titleView.setCompoundDrawables(externalLinkIcon, null, null, null)
|
||||
}
|
||||
|
||||
itemView.setOnClickListener { CommunicationActions.openBrowserLink(itemView.context, itemView.context.getString(model.linkId)) }
|
||||
}
|
||||
}
|
||||
|
||||
class DividerPreferenceViewHolder(itemView: View) : MappingViewHolder<DividerPreference>(itemView) {
|
||||
override fun bind(model: DividerPreference) = Unit
|
||||
}
|
||||
|
||||
class SectionHeaderPreferenceViewHolder(itemView: View) : MappingViewHolder<SectionHeaderPreference>(itemView) {
|
||||
|
||||
private val sectionHeader: TextView = itemView.findViewById(R.id.section_header)
|
||||
|
||||
override fun bind(model: SectionHeaderPreference) {
|
||||
sectionHeader.text = model.title.resolve(context)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.Navigation
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
|
||||
class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__menu_settings) {
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
adapter.registerFactory(BioPreference::class.java, MappingAdapter.LayoutFactory(::BioPreferenceViewHolder, R.layout.bio_preference_item))
|
||||
adapter.registerFactory(PaymentsPreference::class.java, MappingAdapter.LayoutFactory(::PaymentsPreferenceViewHolder, R.layout.dsl_payments_preference))
|
||||
|
||||
val viewModel = ViewModelProviders.of(this)[AppSettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: AppSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
|
||||
customPref(
|
||||
BioPreference(state.self) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_manageProfileActivity)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.AccountSettingsFragment__account),
|
||||
iconId = R.drawable.ic_profile_circle_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_accountSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__linked_devices),
|
||||
iconId = R.drawable.ic_linked_devices_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_deviceActivity)
|
||||
}
|
||||
)
|
||||
|
||||
if (SignalStore.paymentsValues().paymentsAvailability.showPaymentsMenu()) {
|
||||
customPref(
|
||||
PaymentsPreference(
|
||||
unreadCount = state.unreadPaymentsCount
|
||||
) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_paymentsActivity)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
dividerPref()
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__appearance),
|
||||
iconId = R.drawable.ic_appearance_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_appearanceSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_chats__chats),
|
||||
iconId = R.drawable.ic_message_tinted_bitmap_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_chatsSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__notifications),
|
||||
iconId = R.drawable.ic_bell_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_notificationsSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__privacy),
|
||||
iconId = R.drawable.ic_lock_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_privacySettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__data_and_storage),
|
||||
iconId = R.drawable.ic_archive_24dp,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_dataAndStorageSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__help),
|
||||
iconId = R.drawable.ic_help_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_helpSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.AppSettingsFragment__invite_your_friends),
|
||||
iconId = R.drawable.ic_invite_24,
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_inviteActivity)
|
||||
}
|
||||
)
|
||||
|
||||
externalLinkPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
|
||||
iconId = R.drawable.ic_heart_24,
|
||||
linkId = R.string.donate_url
|
||||
)
|
||||
|
||||
if (FeatureFlags.internalUser()) {
|
||||
dividerPref()
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_preferences),
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_appSettingsFragment_to_internalSettingsFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BioPreference(val recipient: Recipient, val onClick: () -> Unit) : PreferenceModel<BioPreference>() {
|
||||
override fun areContentsTheSame(newItem: BioPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) && recipient.hasSameContent(newItem.recipient)
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(newItem: BioPreference): Boolean {
|
||||
return recipient == newItem.recipient
|
||||
}
|
||||
}
|
||||
|
||||
private class BioPreferenceViewHolder(itemView: View) : PreferenceViewHolder<BioPreference>(itemView) {
|
||||
|
||||
private val avatarView: AvatarImageView = itemView.findViewById(R.id.icon)
|
||||
|
||||
override fun bind(model: BioPreference) {
|
||||
super.bind(model)
|
||||
|
||||
itemView.setOnClickListener { model.onClick() }
|
||||
|
||||
titleView.text = model.recipient.getDisplayName(itemView.context)
|
||||
summaryView.text = model.recipient.requireE164()
|
||||
avatarView.setAvatar(GlideApp.with(itemView), model.recipient, false, true)
|
||||
|
||||
titleView.visibility = View.VISIBLE
|
||||
summaryView.visibility = View.VISIBLE
|
||||
avatarView.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private class PaymentsPreference(val unreadCount: Int, val onClick: () -> Unit) : PreferenceModel<PaymentsPreference>() {
|
||||
override fun areContentsTheSame(newItem: PaymentsPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) && unreadCount == newItem.unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
private class PaymentsPreferenceViewHolder(itemView: View) : MappingViewHolder<PaymentsPreference>(itemView) {
|
||||
|
||||
private val unreadCountView: TextView = itemView.findViewById(R.id.unread_indicator)
|
||||
|
||||
override fun bind(model: PaymentsPreference) {
|
||||
unreadCountView.text = model.unreadCount.toString()
|
||||
unreadCountView.visibility = if (model.unreadCount > 0) View.VISIBLE else View.GONE
|
||||
|
||||
itemView.setOnClickListener {
|
||||
model.onClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
|
||||
class AppSettingsViewModel : ViewModel() {
|
||||
|
||||
val unreadPaymentsLiveData = UnreadPaymentsLiveData()
|
||||
val selfLiveData: LiveData<Recipient> = Recipient.self().live().liveData
|
||||
|
||||
val state: LiveData<AppSettingsState> = LiveDataUtil.combineLatest(unreadPaymentsLiveData, selfLiveData) { payments, self ->
|
||||
val unreadPaymentsCount = payments.transform { it.unreadCount }.or(0)
|
||||
|
||||
AppSettingsState(self, unreadPaymentsCount)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.account
|
||||
|
||||
data class AccountSettingsState(
|
||||
val hasPin: Boolean,
|
||||
val pinRemindersEnabled: Boolean,
|
||||
val registrationLockEnabled: Boolean
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.account
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class AccountSettingsViewModel : ViewModel() {
|
||||
private val store: Store<AccountSettingsState> = Store(getCurrentState())
|
||||
|
||||
val state: LiveData<AccountSettingsState> = store.stateLiveData
|
||||
|
||||
fun refreshState() {
|
||||
store.update { getCurrentState() }
|
||||
}
|
||||
|
||||
private fun getCurrentState(): AccountSettingsState {
|
||||
return AccountSettingsState(
|
||||
hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(),
|
||||
pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(),
|
||||
registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.appearance
|
||||
|
||||
data class AppearanceSettingsState(
|
||||
val theme: String,
|
||||
val messageFontSize: Int,
|
||||
val language: String
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.appearance
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class AppearanceSettingsViewModel : ViewModel() {
|
||||
private val store: Store<AppearanceSettingsState>
|
||||
|
||||
init {
|
||||
val initialState = AppearanceSettingsState(
|
||||
SignalStore.settings().theme,
|
||||
SignalStore.settings().messageFontSize,
|
||||
SignalStore.settings().language
|
||||
)
|
||||
|
||||
store = Store(initialState)
|
||||
}
|
||||
|
||||
val state: LiveData<AppearanceSettingsState> = store.stateLiveData
|
||||
|
||||
fun setTheme(theme: String) {
|
||||
store.update { it.copy(theme = theme) }
|
||||
SignalStore.settings().theme = theme
|
||||
}
|
||||
|
||||
fun setLanguage(language: String) {
|
||||
store.update { it.copy(language = language) }
|
||||
SignalStore.settings().language = language
|
||||
}
|
||||
|
||||
fun setMessageFontSize(size: Int) {
|
||||
store.update { it.copy(messageFontSize = size) }
|
||||
SignalStore.settings().messageFontSize = size
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.ThrottledDebouncer
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class ChatsSettingsViewModel(private val repository: ChatsSettingsRepository) : ViewModel() {
|
||||
|
||||
private val refreshDebouncer = ThrottledDebouncer(500L)
|
||||
|
||||
private val store: Store<ChatsSettingsState> = Store(
|
||||
ChatsSettingsState(
|
||||
generateLinkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
|
||||
useAddressBook = SignalStore.settings().isPreferSystemContactPhotos,
|
||||
useSystemEmoji = SignalStore.settings().isPreferSystemEmoji,
|
||||
enterKeySends = SignalStore.settings().isEnterKeySends,
|
||||
chatBackupsEnabled = SignalStore.settings().isBackupEnabled
|
||||
)
|
||||
)
|
||||
|
||||
val state: LiveData<ChatsSettingsState> = store.stateLiveData
|
||||
|
||||
fun setGenerateLinkPreviewsEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(generateLinkPreviews = enabled) }
|
||||
SignalStore.settings().isLinkPreviewsEnabled = enabled
|
||||
repository.syncLinkPreviewsState()
|
||||
}
|
||||
|
||||
fun setUseAddressBook(enabled: Boolean) {
|
||||
store.update { it.copy(useAddressBook = enabled) }
|
||||
SignalStore.settings().isPreferSystemContactPhotos = enabled
|
||||
refreshDebouncer.publish { ConversationUtil.refreshRecipientShortcuts() }
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
fun setUseSystemEmoji(enabled: Boolean) {
|
||||
store.update { it.copy(useSystemEmoji = enabled) }
|
||||
SignalStore.settings().isPreferSystemEmoji = enabled
|
||||
}
|
||||
|
||||
fun setEnterKeySends(enabled: Boolean) {
|
||||
store.update { it.copy(enterKeySends = enabled) }
|
||||
SignalStore.settings().isEnterKeySends = enabled
|
||||
}
|
||||
|
||||
class Factory(private val repository: ChatsSettingsRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(ChatsSettingsViewModel(repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.sms
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class SmsSettingsViewModel : ViewModel() {
|
||||
|
||||
private val store = Store(
|
||||
SmsSettingsState(
|
||||
useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication()),
|
||||
smsDeliveryReportsEnabled = SignalStore.settings().isSmsDeliveryReportsEnabled,
|
||||
wifiCallingCompatibilityEnabled = SignalStore.settings().isWifiCallingCompatibilityModeEnabled
|
||||
)
|
||||
)
|
||||
|
||||
val state: LiveData<SmsSettingsState> = store.stateLiveData
|
||||
|
||||
fun setSmsDeliveryReportsEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(smsDeliveryReportsEnabled = enabled) }
|
||||
SignalStore.settings().isSmsDeliveryReportsEnabled = enabled
|
||||
}
|
||||
|
||||
fun setWifiCallingCompatibilityEnabled(enabled: Boolean) {
|
||||
store.update { it.copy(wifiCallingCompatibilityEnabled = enabled) }
|
||||
SignalStore.settings().isWifiCallingCompatibilityModeEnabled = enabled
|
||||
}
|
||||
|
||||
fun checkSmsEnabled() {
|
||||
store.update { it.copy(useAsDefaultSmsApp = Util.isDefaultSmsProvider(ApplicationDependencies.getApplication())) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.data
|
||||
|
||||
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
|
||||
|
||||
data class DataAndStorageSettingsState(
|
||||
val totalStorageUse: Long,
|
||||
val mobileAutoDownloadValues: Set<String>,
|
||||
val wifiAutoDownloadValues: Set<String>,
|
||||
val roamingAutoDownloadValues: Set<String>,
|
||||
val callBandwidthMode: CallBandwidthMode,
|
||||
val isProxyEnabled: Boolean
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.data
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import org.thoughtcrime.securesms.webrtc.CallBandwidthMode
|
||||
|
||||
class DataAndStorageSettingsViewModel(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: DataAndStorageSettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(getState())
|
||||
|
||||
val state: LiveData<DataAndStorageSettingsState> = store.stateLiveData
|
||||
|
||||
fun refresh() {
|
||||
repository.getTotalStorageUse { totalStorageUse ->
|
||||
store.update { getState().copy(totalStorageUse = totalStorageUse) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setMobileAutoDownloadValues(resultSet: Set<String>) {
|
||||
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF, resultSet).apply()
|
||||
getStateAndCopyStorageUsage()
|
||||
}
|
||||
|
||||
fun setWifiAutoDownloadValues(resultSet: Set<String>) {
|
||||
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF, resultSet).apply()
|
||||
getStateAndCopyStorageUsage()
|
||||
}
|
||||
|
||||
fun setRoamingAutoDownloadValues(resultSet: Set<String>) {
|
||||
sharedPreferences.edit().putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF, resultSet).apply()
|
||||
getStateAndCopyStorageUsage()
|
||||
}
|
||||
|
||||
fun setCallBandwidthMode(callBandwidthMode: CallBandwidthMode) {
|
||||
SignalStore.settings().callBandwidthMode = callBandwidthMode
|
||||
ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate()
|
||||
getStateAndCopyStorageUsage()
|
||||
}
|
||||
|
||||
private fun getStateAndCopyStorageUsage() {
|
||||
store.update { getState().copy(totalStorageUse = it.totalStorageUse) }
|
||||
}
|
||||
|
||||
private fun getState() = DataAndStorageSettingsState(
|
||||
totalStorageUse = 0,
|
||||
mobileAutoDownloadValues = TextSecurePreferences.getMobileMediaDownloadAllowed(
|
||||
ApplicationDependencies.getApplication()
|
||||
),
|
||||
wifiAutoDownloadValues = TextSecurePreferences.getWifiMediaDownloadAllowed(
|
||||
ApplicationDependencies.getApplication()
|
||||
),
|
||||
roamingAutoDownloadValues = TextSecurePreferences.getRoamingMediaDownloadAllowed(
|
||||
ApplicationDependencies.getApplication()
|
||||
),
|
||||
callBandwidthMode = SignalStore.settings().callBandwidthMode,
|
||||
isProxyEnabled = SignalStore.proxy().isProxyEnabled
|
||||
)
|
||||
|
||||
class Factory(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: DataAndStorageSettingsRepository
|
||||
) :
|
||||
ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(DataAndStorageSettingsViewModel(sharedPreferences, repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
|
||||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
|
||||
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.payments.DataExportUtil
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask
|
||||
|
||||
class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__internal_preferences) {
|
||||
|
||||
private lateinit var viewModel: InternalSettingsViewModel
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
val repository = InternalSettingsRepository(requireContext())
|
||||
val factory = InternalSettingsViewModel.Factory(repository)
|
||||
viewModel = ViewModelProviders.of(this, factory)[InternalSettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(getConfiguration(it).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: InternalSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
sectionHeaderPref(R.string.preferences__internal_payments)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_payment_copy_data),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_payment_copy_data_description),
|
||||
onClick = {
|
||||
copyPaymentsDataToClipboard()
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_account)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_refresh_attributes),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_refresh_attributes_description),
|
||||
onClick = {
|
||||
refreshAttributes()
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_rotate_profile_key),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_rotate_profile_key_description),
|
||||
onClick = {
|
||||
rotateProfileKey()
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_refresh_remote_values),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_refresh_remote_values_description),
|
||||
onClick = {
|
||||
refreshRemoteValues()
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_display)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_user_details),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_user_details_description),
|
||||
isChecked = state.seeMoreUserDetails,
|
||||
onClick = {
|
||||
viewModel.setSeeMoreUserDetails(!state.seeMoreUserDetails)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_storage_service)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_force_storage_service_sync),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_force_storage_service_sync_description),
|
||||
onClick = {
|
||||
forceStorageServiceSync()
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v2)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_create_gv2_description),
|
||||
isChecked = state.gv2doNotCreateGv2Groups,
|
||||
onClick = {
|
||||
viewModel.setGv2DoNotCreateGv2Groups(!state.gv2doNotCreateGv2Groups)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_force_gv2_invites_description),
|
||||
isChecked = state.gv2forceInvites,
|
||||
onClick = {
|
||||
viewModel.setGv2ForceInvites(!state.gv2forceInvites)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes_description),
|
||||
isChecked = state.gv2ignoreServerChanges,
|
||||
onClick = {
|
||||
viewModel.setGv2IgnoreServerChanges(!state.gv2ignoreServerChanges)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_p2p_changes),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_ignore_gv2_server_changes_description),
|
||||
isChecked = state.gv2ignoreP2PChanges,
|
||||
onClick = {
|
||||
viewModel.setGv2IgnoreP2PChanges(!state.gv2ignoreP2PChanges)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_preferences_groups_v1_migration)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_initiate_automigrate_description),
|
||||
isChecked = state.disableAutoMigrationInitiation,
|
||||
onClick = {
|
||||
viewModel.setDisableAutoMigrationInitiation(!state.disableAutoMigrationInitiation)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_do_not_notify_automigrate_description),
|
||||
isChecked = state.disableAutoMigrationNotification,
|
||||
onClick = {
|
||||
viewModel.setDisableAutoMigrationNotification(!state.disableAutoMigrationNotification)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_network)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_force_censorship),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_force_censorship_description),
|
||||
isChecked = state.forceCensorship,
|
||||
onClick = {
|
||||
viewModel.setDisableAutoMigrationNotification(!state.forceCensorship)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_conversations_and_shortcuts)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_delete_all_dynamic_shortcuts),
|
||||
summary = DSLSettingsText.from(R.string.preferences__internal_click_to_delete_all_dynamic_shortcuts),
|
||||
onClick = {
|
||||
deleteAllDynamicShortcuts()
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.preferences__internal_emoji)
|
||||
|
||||
val emojiSummary = if (state.emojiVersion == null) {
|
||||
getString(R.string.preferences__internal_use_built_in_emoji_set)
|
||||
} else {
|
||||
getString(
|
||||
R.string.preferences__internal_current_version_d_at_density_s,
|
||||
state.emojiVersion.version,
|
||||
state.emojiVersion.density
|
||||
)
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__internal_use_built_in_emoji_set),
|
||||
summary = DSLSettingsText.from(emojiSummary),
|
||||
isChecked = state.useBuiltInEmojiSet,
|
||||
onClick = {
|
||||
viewModel.setDisableAutoMigrationNotification(!state.useBuiltInEmojiSet)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyPaymentsDataToClipboard() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(
|
||||
"""
|
||||
Local payments history will be copied to the clipboard.
|
||||
It may therefore compromise privacy.
|
||||
However, no private keys will be copied.
|
||||
""".trimIndent()
|
||||
)
|
||||
.setPositiveButton(
|
||||
"Copy"
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
SimpleTask.run<Any?>(
|
||||
SignalExecutors.UNBOUNDED,
|
||||
{
|
||||
val context: Context = ApplicationDependencies.getApplication()
|
||||
val clipboard =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val tsv = DataExportUtil.createTsv()
|
||||
val clip = ClipData.newPlainText(context.getString(R.string.app_name), tsv)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
null
|
||||
},
|
||||
{
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Payments have been copied",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun refreshAttributes() {
|
||||
ApplicationDependencies.getJobManager()
|
||||
.startChain(RefreshAttributesJob())
|
||||
.then(RefreshOwnProfileJob())
|
||||
.enqueue()
|
||||
Toast.makeText(context, "Scheduled attribute refresh", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun rotateProfileKey() {
|
||||
ApplicationDependencies.getJobManager().add(RotateProfileKeyJob())
|
||||
Toast.makeText(context, "Scheduled profile key rotation", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun refreshRemoteValues() {
|
||||
ApplicationDependencies.getJobManager().add(RemoteConfigRefreshJob())
|
||||
Toast.makeText(context, "Scheduled remote config refresh", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun forceStorageServiceSync() {
|
||||
ApplicationDependencies.getJobManager().add(StorageForcePushJob())
|
||||
Toast.makeText(context, "Scheduled storage force push", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun deleteAllDynamicShortcuts() {
|
||||
ConversationUtil.clearAllShortcuts(requireContext())
|
||||
Toast.makeText(context, "Deleted all dynamic shortcuts.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.internal
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.keyvalue.InternalValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class InternalSettingsViewModel(private val repository: InternalSettingsRepository) : ViewModel() {
|
||||
private val preferenceDataStore = SignalStore.getPreferenceDataStore()
|
||||
|
||||
private val store = Store(getState())
|
||||
|
||||
init {
|
||||
repository.getEmojiVersionInfo { version ->
|
||||
store.update { it.copy(emojiVersion = version) }
|
||||
}
|
||||
}
|
||||
|
||||
val state: LiveData<InternalSettingsState> = store.stateLiveData
|
||||
|
||||
fun setSeeMoreUserDetails(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.RECIPIENT_DETAILS, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setGv2DoNotCreateGv2Groups(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_DO_NOT_CREATE_GV2, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setGv2ForceInvites(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_FORCE_INVITES, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setGv2IgnoreServerChanges(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_IGNORE_SERVER_CHANGES, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setGv2IgnoreP2PChanges(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_IGNORE_P2P_CHANGES, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setDisableAutoMigrationInitiation(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_INITIATION, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setDisableAutoMigrationNotification(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.GV2_DISABLE_AUTOMIGRATE_NOTIFICATION, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setForceCensorship(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.FORCE_CENSORSHIP, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setUseBuiltInEmoji(enabled: Boolean) {
|
||||
preferenceDataStore.putBoolean(InternalValues.FORCE_BUILT_IN_EMOJI, enabled)
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
store.update { getState().copy(emojiVersion = it.emojiVersion) }
|
||||
}
|
||||
|
||||
private fun getState() = InternalSettingsState(
|
||||
seeMoreUserDetails = SignalStore.internalValues().recipientDetails(),
|
||||
gv2doNotCreateGv2Groups = SignalStore.internalValues().gv2DoNotCreateGv2Groups(),
|
||||
gv2forceInvites = SignalStore.internalValues().gv2ForceInvites(),
|
||||
gv2ignoreServerChanges = SignalStore.internalValues().gv2IgnoreServerChanges(),
|
||||
gv2ignoreP2PChanges = SignalStore.internalValues().gv2IgnoreP2PChanges(),
|
||||
disableAutoMigrationInitiation = SignalStore.internalValues().disableGv1AutoMigrateInitiation(),
|
||||
disableAutoMigrationNotification = SignalStore.internalValues().disableGv1AutoMigrateNotification(),
|
||||
forceCensorship = SignalStore.internalValues().forcedCensorship(),
|
||||
useBuiltInEmojiSet = SignalStore.internalValues().forceBuiltInEmoji(),
|
||||
emojiVersion = null
|
||||
)
|
||||
|
||||
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.components.settings.RadioListPreference
|
||||
import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
private const val MESSAGE_SOUND_SELECT: Int = 1
|
||||
private const val CALL_RINGTONE_SELECT: Int = 2
|
||||
|
||||
class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__notifications) {
|
||||
|
||||
private val repeatAlertsValues by lazy { resources.getStringArray(R.array.pref_repeat_alerts_values) }
|
||||
private val repeatAlertsLabels by lazy { resources.getStringArray(R.array.pref_repeat_alerts_entries) }
|
||||
|
||||
private val notificationPrivacyValues by lazy { resources.getStringArray(R.array.pref_notification_privacy_values) }
|
||||
private val notificationPrivacyLabels by lazy { resources.getStringArray(R.array.pref_notification_privacy_entries) }
|
||||
|
||||
private val notificationPriorityValues by lazy { resources.getStringArray(R.array.pref_notification_priority_values) }
|
||||
private val notificationPriorityLabels by lazy { resources.getStringArray(R.array.pref_notification_priority_entries) }
|
||||
|
||||
private val ledColorValues by lazy { resources.getStringArray(R.array.pref_led_color_values) }
|
||||
private val ledColorLabels by lazy { resources.getStringArray(R.array.pref_led_color_entries) }
|
||||
|
||||
private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) }
|
||||
private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) }
|
||||
|
||||
private lateinit var viewModel: NotificationsSettingsViewModel
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
||||
val uri = data.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
viewModel.setMessageNotificationsSound(uri)
|
||||
} else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
||||
val uri = data.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
viewModel.setCallRingtone(uri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
adapter.registerFactory(
|
||||
LedColorPreference::class.java,
|
||||
MappingAdapter.LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
||||
)
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val factory = NotificationsSettingsViewModel.Factory(sharedPreferences)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, factory)[NotificationsSettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(getConfiguration(it).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: NotificationsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
sectionHeaderPref(R.string.NotificationsSettingsFragment__messages)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__notifications),
|
||||
isChecked = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setMessageNotificationsEnabled(!state.messageNotificationsState.notificationsEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__sound),
|
||||
summary = DSLSettingsText.from(getRingtoneSummary(state.messageNotificationsState.sound)),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
launchMessageSoundSelectionIntent()
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__vibrate),
|
||||
isChecked = state.messageNotificationsState.vibrateEnabled,
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setMessageNotificationVibration(!state.messageNotificationsState.vibrateEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
customPref(
|
||||
LedColorPreference(
|
||||
colorValues = ledColorValues,
|
||||
radioListPreference = RadioListPreference(
|
||||
title = DSLSettingsText.from(R.string.preferences__led_color),
|
||||
listItems = ledColorLabels,
|
||||
selected = ledColorValues.indexOf(state.messageNotificationsState.ledColor),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = {
|
||||
viewModel.setMessageNotificationLedColor(ledColorValues[it])
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if (!NotificationChannels.supported()) {
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__pref_led_blink_title),
|
||||
listItems = ledBlinkLabels,
|
||||
selected = ledBlinkValues.indexOf(state.messageNotificationsState.ledBlink),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = {
|
||||
viewModel.setMessageNotificationLedBlink(ledBlinkValues[it])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_notifications__in_chat_sounds),
|
||||
isChecked = state.messageNotificationsState.inChatSoundsEnabled,
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setMessageNotificationInChatSoundsEnabled(!state.messageNotificationsState.inChatSoundsEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__repeat_alerts),
|
||||
listItems = repeatAlertsLabels,
|
||||
selected = repeatAlertsValues.indexOf(state.messageNotificationsState.repeatAlerts.toString()),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = {
|
||||
viewModel.setMessageRepeatAlerts(repeatAlertsValues[it].toInt())
|
||||
}
|
||||
)
|
||||
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_notifications__show),
|
||||
listItems = notificationPrivacyLabels,
|
||||
selected = notificationPrivacyValues.indexOf(state.messageNotificationsState.messagePrivacy),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = {
|
||||
viewModel.setMessageNotificationPrivacy(notificationPrivacyValues[it])
|
||||
}
|
||||
)
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
launchNotificationPriorityIntent()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_notifications__priority),
|
||||
listItems = notificationPriorityLabels,
|
||||
selected = notificationPriorityValues.indexOf(state.messageNotificationsState.priority.toString()),
|
||||
isEnabled = state.messageNotificationsState.notificationsEnabled,
|
||||
onSelected = {
|
||||
viewModel.setMessageNotificationPriority(notificationPriorityValues[it].toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.NotificationsSettingsFragment__calls)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__notifications),
|
||||
isChecked = state.callNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setCallNotificationsEnabled(!state.callNotificationsState.notificationsEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_notifications__ringtone),
|
||||
summary = DSLSettingsText.from(getRingtoneSummary(state.callNotificationsState.ringtone)),
|
||||
isEnabled = state.callNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
launchCallRingtoneSelectionIntent()
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__vibrate),
|
||||
isChecked = state.callNotificationsState.vibrateEnabled,
|
||||
isEnabled = state.callNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setCallVibrateEnabled(!state.callNotificationsState.vibrateEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.NotificationsSettingsFragment__notify_when)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__contact_joins_signal),
|
||||
isChecked = state.notifyWhenContactJoinsSignal,
|
||||
onClick = {
|
||||
viewModel.setNotifyWhenContactJoinsSignal(!state.notifyWhenContactJoinsSignal)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRingtoneSummary(uri: Uri): String {
|
||||
return if (TextUtils.isEmpty(uri.toString())) {
|
||||
getString(R.string.preferences__silent)
|
||||
} else {
|
||||
val tone = RingtoneUtil.getRingtone(requireContext(), uri)
|
||||
if (tone != null) {
|
||||
tone.getTitle(requireContext())
|
||||
} else {
|
||||
getString(R.string.preferences__default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchMessageSoundSelectionIntent() {
|
||||
val current = SignalStore.settings().messageNotificationSound
|
||||
|
||||
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
||||
intent.putExtra(
|
||||
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
||||
Settings.System.DEFAULT_NOTIFICATION_URI
|
||||
)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
||||
|
||||
startActivityForResult(intent, MESSAGE_SOUND_SELECT)
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
private fun launchNotificationPriorityIntent() {
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(
|
||||
Settings.EXTRA_CHANNEL_ID,
|
||||
NotificationChannels.getMessagesChannel(requireContext())
|
||||
)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun launchCallRingtoneSelectionIntent() {
|
||||
val current = SignalStore.settings().callRingtone
|
||||
|
||||
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
|
||||
intent.putExtra(
|
||||
RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
|
||||
Settings.System.DEFAULT_RINGTONE_URI
|
||||
)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current)
|
||||
|
||||
startActivityForResult(intent, CALL_RINGTONE_SELECT)
|
||||
}
|
||||
|
||||
private class LedColorPreference(
|
||||
val colorValues: Array<String>,
|
||||
val radioListPreference: RadioListPreference
|
||||
) : PreferenceModel<LedColorPreference>(
|
||||
title = radioListPreference.title,
|
||||
iconId = radioListPreference.iconId,
|
||||
summary = radioListPreference.summary
|
||||
) {
|
||||
override fun areContentsTheSame(newItem: LedColorPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) && radioListPreference.areContentsTheSame(newItem.radioListPreference)
|
||||
}
|
||||
}
|
||||
|
||||
private class LedColorPreferenceViewHolder(itemView: View) :
|
||||
PreferenceViewHolder<LedColorPreference>(itemView) {
|
||||
|
||||
val radioListPreferenceViewHolder = RadioListPreferenceViewHolder(itemView)
|
||||
|
||||
override fun bind(model: LedColorPreference) {
|
||||
super.bind(model)
|
||||
radioListPreferenceViewHolder.bind(model.radioListPreference)
|
||||
|
||||
summaryView.visibility = View.GONE
|
||||
|
||||
val circleDrawable = requireNotNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable))
|
||||
circleDrawable.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
|
||||
circleDrawable.colorFilter = model.colorValues[model.radioListPreference.selected].toColorFilter()
|
||||
|
||||
if (titleView.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
titleView.setCompoundDrawables(null, null, circleDrawable, null)
|
||||
} else {
|
||||
titleView.setCompoundDrawables(circleDrawable, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toColorFilter(): ColorFilter {
|
||||
val color = when (this) {
|
||||
"green" -> ContextCompat.getColor(context, R.color.green_500)
|
||||
"red" -> ContextCompat.getColor(context, R.color.red_500)
|
||||
"blue" -> ContextCompat.getColor(context, R.color.blue_500)
|
||||
"yellow" -> ContextCompat.getColor(context, R.color.yellow_500)
|
||||
"cyan" -> ContextCompat.getColor(context, R.color.cyan_500)
|
||||
"magenta" -> ContextCompat.getColor(context, R.color.pink_500)
|
||||
"white" -> ContextCompat.getColor(context, R.color.white)
|
||||
else -> ContextCompat.getColor(context, R.color.transparent)
|
||||
}
|
||||
|
||||
return PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.notifications
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class NotificationsSettingsViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {
|
||||
|
||||
init {
|
||||
if (NotificationChannels.supported()) {
|
||||
SignalStore.settings().messageNotificationSound = NotificationChannels.getMessageRingtone(ApplicationDependencies.getApplication())
|
||||
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getMessageVibrate(ApplicationDependencies.getApplication())
|
||||
}
|
||||
}
|
||||
|
||||
private val store = Store(getState())
|
||||
|
||||
val state: LiveData<NotificationsSettingsState> = store.stateLiveData
|
||||
|
||||
fun setMessageNotificationsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageNotificationsEnabled = enabled
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationsSound(sound: Uri?) {
|
||||
SignalStore.settings().messageNotificationSound = sound ?: Uri.EMPTY
|
||||
NotificationChannels.updateMessageRingtone(ApplicationDependencies.getApplication(), sound)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationVibration(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageVibrateEnabled = enabled
|
||||
NotificationChannels.updateMessageVibrate(ApplicationDependencies.getApplication(), enabled)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationLedColor(color: String) {
|
||||
SignalStore.settings().messageLedColor = color
|
||||
NotificationChannels.updateMessagesLedColor(ApplicationDependencies.getApplication(), color)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationLedBlink(blink: String) {
|
||||
SignalStore.settings().messageLedBlinkPattern = blink
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationInChatSoundsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageNotificationsInChatSoundsEnabled = enabled
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageRepeatAlerts(repeats: Int) {
|
||||
SignalStore.settings().messageNotificationsRepeatAlerts = repeats
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationPrivacy(preference: String) {
|
||||
SignalStore.settings().messageNotificationsPrivacy = NotificationPrivacyPreference(preference)
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationPriority(priority: Int) {
|
||||
sharedPreferences.edit().putInt(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF, priority).apply()
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setCallNotificationsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isCallNotificationsEnabled = enabled
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setCallRingtone(ringtone: Uri?) {
|
||||
SignalStore.settings().callRingtone = ringtone ?: Uri.EMPTY
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setCallVibrateEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isCallVibrateEnabled = enabled
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setNotifyWhenContactJoinsSignal(enabled: Boolean) {
|
||||
SignalStore.settings().isNotifyWhenContactJoinsSignal = enabled
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
private fun getState(): NotificationsSettingsState = NotificationsSettingsState(
|
||||
messageNotificationsState = MessageNotificationsState(
|
||||
notificationsEnabled = SignalStore.settings().isMessageNotificationsEnabled,
|
||||
sound = SignalStore.settings().messageNotificationSound,
|
||||
vibrateEnabled = SignalStore.settings().isMessageVibrateEnabled,
|
||||
ledColor = SignalStore.settings().messageLedColor,
|
||||
ledBlink = SignalStore.settings().messageLedBlinkPattern,
|
||||
inChatSoundsEnabled = SignalStore.settings().isMessageNotificationsInChatSoundsEnabled,
|
||||
repeatAlerts = SignalStore.settings().messageNotificationsRepeatAlerts,
|
||||
messagePrivacy = SignalStore.settings().messageNotificationsPrivacy.toString(),
|
||||
priority = TextSecurePreferences.getNotificationPriority(ApplicationDependencies.getApplication())
|
||||
),
|
||||
callNotificationsState = CallNotificationsState(
|
||||
notificationsEnabled = SignalStore.settings().isCallNotificationsEnabled,
|
||||
ringtone = SignalStore.settings().callRingtone,
|
||||
vibrateEnabled = SignalStore.settings().isCallVibrateEnabled
|
||||
),
|
||||
notifyWhenContactJoinsSignal = SignalStore.settings().isNotifyWhenContactJoinsSignal
|
||||
)
|
||||
|
||||
class Factory(private val sharedPreferences: SharedPreferences) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(NotificationsSettingsViewModel(sharedPreferences)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.TextAppearanceSpan
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.Navigation
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import mobi.upod.timedurationpicker.TimeDurationPicker
|
||||
import mobi.upod.timedurationpicker.TimeDurationPickerDialog
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.PassphraseChangeActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import java.lang.Integer.max
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedHashMap
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private val TAG = Log.tag(PrivacySettingsFragment::class.java)
|
||||
|
||||
class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privacy) {
|
||||
|
||||
private lateinit var viewModel: PrivacySettingsViewModel
|
||||
|
||||
private val incognitoSummary: CharSequence by lazy {
|
||||
SpannableStringBuilder(getString(R.string.preferences__this_setting_is_not_a_guarantee))
|
||||
.append(" ")
|
||||
.append(
|
||||
SpanUtil.learnMore(requireContext(), ContextCompat.getColor(requireContext(), R.color.signal_text_primary)) {
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.preferences__incognito_keyboard_learn_more))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refreshBlockedCount()
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val repository = PrivacySettingsRepository()
|
||||
val factory = PrivacySettingsViewModel.Factory(sharedPreferences, repository)
|
||||
viewModel = ViewModelProviders.of(this, factory)[PrivacySettingsViewModel::class.java]
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: PrivacySettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.PrivacySettingsFragment__blocked),
|
||||
summary = DSLSettingsText.from(getString(R.string.PrivacySettingsFragment__d_contacts, state.blockedCount)),
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(R.id.action_privacySettingsFragment_to_blockedUsersActivity)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
sectionHeaderPref(R.string.preferences_app_protection__who_can)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__see_my_phone_number),
|
||||
summary = DSLSettingsText.from(getWhoCanSeeMyPhoneNumberSummary(state.seeMyPhoneNumber)),
|
||||
onClick = {
|
||||
onSeeMyPhoneNumberClicked(state.seeMyPhoneNumber)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__find_me_by_phone_number),
|
||||
summary = DSLSettingsText.from(getWhoCanFindMeByPhoneNumberSummary(state.findMeByPhoneNumber)),
|
||||
onClick = {
|
||||
onFindMyPhoneNumberClicked(state.findMeByPhoneNumber)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
}
|
||||
|
||||
sectionHeaderPref(R.string.PrivacySettingsFragment__messaging)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__read_receipts),
|
||||
summary = DSLSettingsText.from(R.string.preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts),
|
||||
isChecked = state.readReceipts,
|
||||
onClick = {
|
||||
viewModel.setReadReceiptsEnabled(!state.readReceipts)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__typing_indicators),
|
||||
summary = DSLSettingsText.from(R.string.preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators),
|
||||
isChecked = state.typingIndicators,
|
||||
onClick = {
|
||||
viewModel.setTypingIndicatorsEnabled(!state.typingIndicators)
|
||||
}
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
sectionHeaderPref(R.string.PrivacySettingsFragment__app_security)
|
||||
|
||||
if (state.isObsoletePasswordEnabled) {
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__enable_passphrase),
|
||||
summary = DSLSettingsText.from(R.string.preferences__lock_signal_and_message_notifications_with_a_passphrase),
|
||||
isChecked = true,
|
||||
onClick = {
|
||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||
setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase)
|
||||
setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications)
|
||||
setIcon(R.drawable.ic_warning)
|
||||
setPositiveButton(R.string.ApplicationPreferencesActivity_disable) { dialog, which ->
|
||||
MasterSecretUtil.changeMasterSecretPassphrase(
|
||||
activity,
|
||||
KeyCachingService.getMasterSecret(context),
|
||||
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
|
||||
)
|
||||
TextSecurePreferences.setPasswordDisabled(activity, true)
|
||||
val intent = Intent(activity, KeyCachingService::class.java)
|
||||
intent.action = KeyCachingService.DISABLE_ACTION
|
||||
requireActivity().startService(intent)
|
||||
viewModel.refresh()
|
||||
}
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
show()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__change_passphrase),
|
||||
summary = DSLSettingsText.from(R.string.preferences__change_your_passphrase),
|
||||
onClick = {
|
||||
if (MasterSecretUtil.isPassphraseInitialized(activity)) {
|
||||
startActivity(Intent(activity, PassphraseChangeActivity::class.java))
|
||||
} else {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__inactivity_timeout_passphrase),
|
||||
summary = DSLSettingsText.from(R.string.preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity),
|
||||
isChecked = state.isObsoletePasswordTimeoutEnabled,
|
||||
onClick = {
|
||||
viewModel.setObsoletePasswordTimeoutEnabled(!state.isObsoletePasswordEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__inactivity_timeout_interval),
|
||||
onClick = {
|
||||
TimeDurationPickerDialog(
|
||||
context,
|
||||
{ _: TimeDurationPicker?, duration: Long ->
|
||||
val timeoutMinutes = max(TimeUnit.MILLISECONDS.toMinutes(duration).toInt(), 1)
|
||||
viewModel.setObsoletePasswordTimeout(timeoutMinutes)
|
||||
},
|
||||
0, TimeDurationPicker.HH_MM
|
||||
).show()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val isKeyguardSecure = ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
|
||||
summary = DSLSettingsText.from(R.string.preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint),
|
||||
isChecked = state.screenLock && isKeyguardSecure,
|
||||
isEnabled = isKeyguardSecure,
|
||||
onClick = {
|
||||
viewModel.setScreenLockEnabled(!state.screenLock)
|
||||
|
||||
val intent = Intent(requireContext(), KeyCachingService::class.java)
|
||||
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
|
||||
requireContext().startService(intent)
|
||||
|
||||
ConversationUtil.refreshRecipientShortcuts()
|
||||
}
|
||||
)
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock_inactivity_timeout),
|
||||
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(state.screenLockActivityTimeout)),
|
||||
isEnabled = isKeyguardSecure,
|
||||
onClick = {
|
||||
TimeDurationPickerDialog(
|
||||
context,
|
||||
{ _: TimeDurationPicker?, duration: Long ->
|
||||
val timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration)
|
||||
viewModel.setScreenLockTimeout(timeoutSeconds)
|
||||
},
|
||||
0, TimeDurationPicker.HH_MM
|
||||
).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__screen_security),
|
||||
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__block_screenshots_in_the_recents_list_and_inside_the_app),
|
||||
isChecked = state.screenSecurity,
|
||||
onClick = {
|
||||
viewModel.setScreenSecurityEnabled(!state.screenSecurity)
|
||||
}
|
||||
)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__incognito_keyboard),
|
||||
summary = DSLSettingsText.from(R.string.preferences__request_keyboard_to_disable),
|
||||
isChecked = state.incognitoKeyboard,
|
||||
onClick = {
|
||||
viewModel.setIncognitoKeyboard(!state.incognitoKeyboard)
|
||||
}
|
||||
)
|
||||
|
||||
textPref(
|
||||
summary = DSLSettingsText.from(incognitoSummary),
|
||||
)
|
||||
|
||||
dividerPref()
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__advanced),
|
||||
summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__signal_message_and_calls),
|
||||
onClick = {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.action_privacySettingsFragment_to_advancedPrivacySettingsFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
|
||||
val hours = TimeUnit.SECONDS.toHours(timeoutSeconds)
|
||||
val minutes =
|
||||
TimeUnit.SECONDS.toMinutes(timeoutSeconds) - TimeUnit.SECONDS.toHours(timeoutSeconds) * 60
|
||||
|
||||
return if (timeoutSeconds <= 0) {
|
||||
getString(R.string.AppProtectionPreferenceFragment_none)
|
||||
} else {
|
||||
String.format(Locale.getDefault(), "%02d:%02d:00", hours, minutes)
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private fun getWhoCanSeeMyPhoneNumberSummary(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode): Int {
|
||||
return when (phoneNumberSharingMode) {
|
||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacy_everyone
|
||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS -> R.string.PhoneNumberPrivacy_my_contacts
|
||||
PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacy_nobody
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private fun getWhoCanFindMeByPhoneNumberSummary(phoneNumberListingMode: PhoneNumberListingMode): Int {
|
||||
return when (phoneNumberListingMode) {
|
||||
PhoneNumberListingMode.LISTED -> R.string.PhoneNumberPrivacy_everyone
|
||||
PhoneNumberListingMode.UNLISTED -> R.string.PhoneNumberPrivacy_nobody
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSeeMyPhoneNumberClicked(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
|
||||
val value = arrayOf(phoneNumberSharingMode)
|
||||
val items = items(requireContext())
|
||||
val modes: List<PhoneNumberPrivacyValues.PhoneNumberSharingMode> = ArrayList(items.keys)
|
||||
val modeStrings = items.values.toTypedArray()
|
||||
val selectedMode = modes.indexOf(value[0])
|
||||
|
||||
MaterialAlertDialogBuilder(requireActivity()).apply {
|
||||
setTitle(R.string.preferences_app_protection__see_my_phone_number)
|
||||
setCancelable(true)
|
||||
setSingleChoiceItems(
|
||||
modeStrings,
|
||||
selectedMode
|
||||
) { _: DialogInterface?, which: Int -> value[0] = modes[which] }
|
||||
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val newSharingMode = value[0]
|
||||
Log.i(
|
||||
TAG,
|
||||
String.format(
|
||||
"PhoneNumberSharingMode changed to %s. Scheduling storage value sync",
|
||||
newSharingMode
|
||||
)
|
||||
)
|
||||
viewModel.setPhoneNumberSharingMode(value[0])
|
||||
}
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun items(context: Context): Map<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> {
|
||||
val map: MutableMap<PhoneNumberPrivacyValues.PhoneNumberSharingMode, CharSequence> = LinkedHashMap()
|
||||
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE] = titleAndDescription(
|
||||
context,
|
||||
context.getString(R.string.PhoneNumberPrivacy_everyone),
|
||||
context.getString(R.string.PhoneNumberPrivacy_everyone_see_description)
|
||||
)
|
||||
map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY] =
|
||||
context.getString(R.string.PhoneNumberPrivacy_nobody)
|
||||
return map
|
||||
}
|
||||
|
||||
private fun titleAndDescription(
|
||||
context: Context,
|
||||
header: String,
|
||||
description: String
|
||||
): CharSequence {
|
||||
return SpannableStringBuilder().apply {
|
||||
append("\n")
|
||||
append(header)
|
||||
append("\n")
|
||||
setSpan(
|
||||
TextAppearanceSpan(context, android.R.style.TextAppearance_Small),
|
||||
length,
|
||||
length,
|
||||
Spanned.SPAN_INCLUSIVE_INCLUSIVE
|
||||
)
|
||||
append(description)
|
||||
append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
fun onFindMyPhoneNumberClicked(phoneNumberListingMode: PhoneNumberListingMode) {
|
||||
val context = requireContext()
|
||||
val value = arrayOf(phoneNumberListingMode)
|
||||
MaterialAlertDialogBuilder(requireActivity()).apply {
|
||||
setTitle(R.string.preferences_app_protection__find_me_by_phone_number)
|
||||
setCancelable(true)
|
||||
setSingleChoiceItems(
|
||||
arrayOf(
|
||||
titleAndDescription(
|
||||
context,
|
||||
context.getString(R.string.PhoneNumberPrivacy_everyone),
|
||||
context.getString(R.string.PhoneNumberPrivacy_everyone_find_description)
|
||||
),
|
||||
context.getString(R.string.PhoneNumberPrivacy_nobody)
|
||||
),
|
||||
value[0].ordinal
|
||||
) { _: DialogInterface?, which: Int -> value[0] = PhoneNumberListingMode.values()[which] }
|
||||
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
Log.i(
|
||||
TAG,
|
||||
String.format(
|
||||
"PhoneNumberListingMode changed to %s. Scheduling storage value sync",
|
||||
value[0]
|
||||
)
|
||||
)
|
||||
viewModel.setPhoneNumberListingMode(value[0])
|
||||
}
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class PrivacySettingsViewModel(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: PrivacySettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(getState())
|
||||
|
||||
val state: LiveData<PrivacySettingsState> = store.stateLiveData
|
||||
|
||||
fun refreshBlockedCount() {
|
||||
repository.getBlockedCount { count ->
|
||||
store.update { it.copy(blockedCount = count) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setReadReceiptsEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.READ_RECEIPTS_PREF, enabled).apply()
|
||||
repository.syncReadReceiptState()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setTypingIndicatorsEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.TYPING_INDICATORS, enabled).apply()
|
||||
repository.syncTypingIndicatorsState()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenLockEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenLockTimeout(seconds: Long) {
|
||||
TextSecurePreferences.setScreenLockTimeout(ApplicationDependencies.getApplication(), seconds)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setScreenSecurityEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) {
|
||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberPrivacyValues.PhoneNumberListingMode) {
|
||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setIncognitoKeyboard(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setObsoletePasswordTimeout(minutes: Int) {
|
||||
TextSecurePreferences.setPassphraseTimeoutInterval(ApplicationDependencies.getApplication(), minutes)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
store.update(this::updateState)
|
||||
}
|
||||
|
||||
private fun getState(): PrivacySettingsState {
|
||||
return PrivacySettingsState(
|
||||
blockedCount = 0,
|
||||
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(ApplicationDependencies.getApplication()),
|
||||
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(ApplicationDependencies.getApplication()),
|
||||
screenLock = TextSecurePreferences.isScreenLockEnabled(ApplicationDependencies.getApplication()),
|
||||
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(ApplicationDependencies.getApplication()),
|
||||
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(ApplicationDependencies.getApplication()),
|
||||
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(ApplicationDependencies.getApplication()),
|
||||
seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode,
|
||||
findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode,
|
||||
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(ApplicationDependencies.getApplication()),
|
||||
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
|
||||
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication())
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateState(state: PrivacySettingsState): PrivacySettingsState {
|
||||
return getState().copy(blockedCount = state.blockedCount)
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: PrivacySettingsRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(PrivacySettingsViewModel(sharedPreferences, repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class AdvancedPrivacySettingsViewModel(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: AdvancedPrivacySettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(getState())
|
||||
private val singleEvents = SingleLiveEvent<Event>()
|
||||
|
||||
val state: LiveData<AdvancedPrivacySettingsState> = store.stateLiveData
|
||||
val events: LiveData<Event> = singleEvents
|
||||
|
||||
fun disablePushMessages() {
|
||||
store.update { getState().copy(showProgressSpinner = true) }
|
||||
|
||||
repository.disablePushMessages {
|
||||
when (it) {
|
||||
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> {
|
||||
TextSecurePreferences.setPushRegistered(ApplicationDependencies.getApplication(), false)
|
||||
SignalStore.registrationValues().clearRegistrationComplete()
|
||||
}
|
||||
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> {
|
||||
singleEvents.postValue(Event.DISABLE_PUSH_FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
store.update { getState().copy(showProgressSpinner = false) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setAlwaysRelayCalls(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.ALWAYS_RELAY_CALLS_PREF, enabled).apply()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setShowStatusIconForSealedSender(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled).apply()
|
||||
repository.syncShowSealedSenderIconState()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setAllowSealedSenderFromAnyone(enabled: Boolean) {
|
||||
sharedPreferences.edit().putBoolean(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS, enabled).apply()
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
store.update { getState().copy(showProgressSpinner = it.showProgressSpinner) }
|
||||
}
|
||||
|
||||
private fun getState() = AdvancedPrivacySettingsState(
|
||||
isPushEnabled = TextSecurePreferences.isPushRegistered(ApplicationDependencies.getApplication()),
|
||||
alwaysRelayCalls = TextSecurePreferences.isTurnOnly(ApplicationDependencies.getApplication()),
|
||||
showSealedSenderStatusIcon = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(
|
||||
ApplicationDependencies.getApplication()
|
||||
),
|
||||
allowSealedSenderFromAnyone = TextSecurePreferences.isUniversalUnidentifiedAccess(
|
||||
ApplicationDependencies.getApplication()
|
||||
),
|
||||
false
|
||||
)
|
||||
|
||||
enum class Event {
|
||||
DISABLE_PUSH_FAILED
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val repository: AdvancedPrivacySettingsRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(
|
||||
modelClass.cast(
|
||||
AdvancedPrivacySettingsViewModel(
|
||||
sharedPreferences,
|
||||
repository
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package org.thoughtcrime.securesms.components.settings
|
||||
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import org.thoughtcrime.securesms.util.MappingModelList
|
||||
|
||||
private const val UNSET = -1
|
||||
|
||||
fun configure(init: DSLConfiguration.() -> Unit): DSLConfiguration {
|
||||
val configuration = DSLConfiguration()
|
||||
configuration.init()
|
||||
return configuration
|
||||
}
|
||||
|
||||
class DSLConfiguration {
|
||||
private val children = arrayListOf<PreferenceModel<*>>()
|
||||
|
||||
fun customPref(customPreference: PreferenceModel<*>) {
|
||||
children.add(customPreference)
|
||||
}
|
||||
|
||||
fun radioListPref(
|
||||
title: DSLSettingsText,
|
||||
@DrawableRes iconId: Int = UNSET,
|
||||
isEnabled: Boolean = true,
|
||||
listItems: Array<String>,
|
||||
selected: Int,
|
||||
onSelected: (Int) -> Unit
|
||||
) {
|
||||
val preference = RadioListPreference(title, iconId, isEnabled, listItems, selected, onSelected)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun multiSelectPref(
|
||||
title: DSLSettingsText,
|
||||
isEnabled: Boolean = true,
|
||||
listItems: Array<String>,
|
||||
selected: BooleanArray,
|
||||
onSelected: (BooleanArray) -> Unit
|
||||
) {
|
||||
val preference = MultiSelectListPreference(title, isEnabled, listItems, selected, onSelected)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun switchPref(
|
||||
title: DSLSettingsText,
|
||||
summary: DSLSettingsText? = null,
|
||||
@DrawableRes iconId: Int = UNSET,
|
||||
isEnabled: Boolean = true,
|
||||
isChecked: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val preference = SwitchPreference(title, summary, iconId, isEnabled, isChecked, onClick)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun clickPref(
|
||||
title: DSLSettingsText,
|
||||
summary: DSLSettingsText? = null,
|
||||
@DrawableRes iconId: Int = UNSET,
|
||||
isEnabled: Boolean = true,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val preference = ClickPreference(title, summary, iconId, isEnabled, onClick)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun externalLinkPref(
|
||||
title: DSLSettingsText,
|
||||
@DrawableRes iconId: Int = UNSET,
|
||||
@StringRes linkId: Int
|
||||
) {
|
||||
val preference = ExternalLinkPreference(title, iconId, linkId)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun dividerPref() {
|
||||
val preference = DividerPreference()
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun sectionHeaderPref(title: DSLSettingsText) {
|
||||
val preference = SectionHeaderPreference(title)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun sectionHeaderPref(title: Int) {
|
||||
val preference = SectionHeaderPreference(DSLSettingsText.from(title))
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun textPref(
|
||||
title: DSLSettingsText? = null,
|
||||
summary: DSLSettingsText? = null
|
||||
) {
|
||||
val preference = TextPreference(title, summary)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun toMappingModelList(): MappingModelList = MappingModelList().apply { addAll(children) }
|
||||
}
|
||||
|
||||
abstract class PreferenceModel<T : PreferenceModel<T>>(
|
||||
open val title: DSLSettingsText? = null,
|
||||
open val summary: DSLSettingsText? = null,
|
||||
@DrawableRes open val iconId: Int = UNSET,
|
||||
open val isEnabled: Boolean = true
|
||||
) : MappingModel<T> {
|
||||
override fun areItemsTheSame(newItem: T): Boolean {
|
||||
return when {
|
||||
title != null -> title == newItem.title
|
||||
summary != null -> summary == newItem.summary
|
||||
else -> throw AssertionError("Could not determine equality.")
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun areContentsTheSame(newItem: T): Boolean {
|
||||
return areItemsTheSame(newItem) &&
|
||||
newItem.summary == summary &&
|
||||
newItem.iconId == iconId &&
|
||||
newItem.isEnabled == isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
class TextPreference(
|
||||
title: DSLSettingsText?,
|
||||
summary: DSLSettingsText?
|
||||
) : PreferenceModel<TextPreference>(title = title, summary = summary)
|
||||
|
||||
class DividerPreference : PreferenceModel<DividerPreference>() {
|
||||
override fun areItemsTheSame(newItem: DividerPreference) = false
|
||||
}
|
||||
|
||||
class RadioListPreference(
|
||||
override val title: DSLSettingsText,
|
||||
@DrawableRes override val iconId: Int = UNSET,
|
||||
override val isEnabled: Boolean,
|
||||
val listItems: Array<String>,
|
||||
val selected: Int,
|
||||
val onSelected: (Int) -> Unit
|
||||
) : PreferenceModel<RadioListPreference>(title = title, iconId = iconId, isEnabled = isEnabled) {
|
||||
|
||||
override fun areContentsTheSame(newItem: RadioListPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) && listItems.contentEquals(newItem.listItems) && selected == newItem.selected
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectListPreference(
|
||||
override val title: DSLSettingsText,
|
||||
override val isEnabled: Boolean,
|
||||
val listItems: Array<String>,
|
||||
val selected: BooleanArray,
|
||||
val onSelected: (BooleanArray) -> Unit
|
||||
) : PreferenceModel<MultiSelectListPreference>(title = title, isEnabled = isEnabled) {
|
||||
override fun areContentsTheSame(newItem: MultiSelectListPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) &&
|
||||
listItems.contentEquals(newItem.listItems) &&
|
||||
selected.contentEquals(newItem.selected)
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchPreference(
|
||||
override val title: DSLSettingsText,
|
||||
override val summary: DSLSettingsText? = null,
|
||||
@DrawableRes override val iconId: Int = UNSET,
|
||||
isEnabled: Boolean,
|
||||
val isChecked: Boolean,
|
||||
val onClick: () -> Unit
|
||||
) : PreferenceModel<SwitchPreference>(title = title, summary = summary, iconId = iconId, isEnabled = isEnabled) {
|
||||
override fun areContentsTheSame(newItem: SwitchPreference): Boolean {
|
||||
return super.areContentsTheSame(newItem) && isChecked == newItem.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
class ClickPreference(
|
||||
override val title: DSLSettingsText,
|
||||
override val summary: DSLSettingsText?,
|
||||
@DrawableRes override val iconId: Int,
|
||||
isEnabled: Boolean,
|
||||
val onClick: () -> Unit
|
||||
) : PreferenceModel<ClickPreference>(title = title, summary = summary, iconId = iconId, isEnabled = isEnabled)
|
||||
|
||||
class ExternalLinkPreference(
|
||||
override val title: DSLSettingsText,
|
||||
@DrawableRes override val iconId: Int,
|
||||
@StringRes val linkId: Int
|
||||
) : PreferenceModel<ExternalLinkPreference>(title = title, iconId = iconId)
|
||||
|
||||
class SectionHeaderPreference(override val title: DSLSettingsText) : PreferenceModel<SectionHeaderPreference>(title = title)
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user