diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt index 1b6d3ac009..7121fc185b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsFragment.kt @@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.components.settings.app.appearance import androidx.lifecycle.ViewModelProvider import androidx.navigation.Navigation +import org.signal.core.util.concurrent.observe import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsText +import org.thoughtcrime.securesms.components.settings.app.appearance.navbar.ChooseNavigationBarStyleFragment import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.keyvalue.SettingsValues import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter @@ -30,10 +32,25 @@ class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__app viewModel.state.observe(viewLifecycleOwner) { state -> adapter.submitList(getConfiguration(state).toMappingModelList()) } + + childFragmentManager.setFragmentResultListener(ChooseNavigationBarStyleFragment.REQUEST_KEY, viewLifecycleOwner) { key, bundle -> + if (bundle.getBoolean(key, false)) { + viewModel.refreshState() + } + } } private fun getConfiguration(state: AppearanceSettingsState): DSLConfiguration { return configure { + radioListPref( + title = DSLSettingsText.from(R.string.preferences__language), + listItems = languageLabels, + selected = languageValues.indexOf(state.language), + onSelected = { + viewModel.setLanguage(languageValues[it]) + } + ) + radioListPref( title = DSLSettingsText.from(R.string.preferences__theme), listItems = themeLabels, @@ -59,12 +76,17 @@ class AppearanceSettingsFragment : DSLSettingsFragment(R.string.preferences__app } ) - radioListPref( - title = DSLSettingsText.from(R.string.preferences__language), - listItems = languageLabels, - selected = languageValues.indexOf(state.language), - onSelected = { - viewModel.setLanguage(languageValues[it]) + clickPref( + title = DSLSettingsText.from(R.string.preferences_navigation_bar_size), + summary = DSLSettingsText.from( + if (state.isCompactNavigationBar) { + R.string.preferences_compact + } else { + R.string.preferences_normal + } + ), + onClick = { + ChooseNavigationBarStyleFragment().show(childFragmentManager, null) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt index 9001e98438..9e080f65b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsState.kt @@ -5,5 +5,6 @@ import org.thoughtcrime.securesms.keyvalue.SettingsValues data class AppearanceSettingsState( val theme: SettingsValues.Theme, val messageFontSize: Int, - val language: String + val language: String, + val isCompactNavigationBar: Boolean ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt index e93817e5bd..2c86c59fed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/AppearanceSettingsViewModel.kt @@ -1,28 +1,26 @@ package org.thoughtcrime.securesms.components.settings.app.appearance import android.app.Activity -import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import io.reactivex.rxjava3.core.Flowable import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob import org.thoughtcrime.securesms.keyvalue.SettingsValues.Theme import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.SplashScreenUtil -import org.thoughtcrime.securesms.util.livedata.Store +import org.thoughtcrime.securesms.util.rx.RxStore class AppearanceSettingsViewModel : ViewModel() { - private val store: Store + private val store = RxStore(getState()) + val state: Flowable = store.stateFlowable - init { - val initialState = AppearanceSettingsState( - SignalStore.settings().theme, - SignalStore.settings().messageFontSize, - SignalStore.settings().language - ) - - store = Store(initialState) + override fun onCleared() { + super.onCleared() + store.dispose() } - val state: LiveData = store.stateLiveData + fun refreshState() { + store.update { getState() } + } fun setTheme(activity: Activity?, theme: Theme) { store.update { it.copy(theme = theme) } @@ -40,4 +38,13 @@ class AppearanceSettingsViewModel : ViewModel() { store.update { it.copy(messageFontSize = size) } SignalStore.settings().messageFontSize = size } + + private fun getState(): AppearanceSettingsState { + return AppearanceSettingsState( + SignalStore.settings().theme, + SignalStore.settings().messageFontSize, + SignalStore.settings().language, + SignalStore.settings().useCompactNavigationBar + ) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/navbar/ChooseNavigationBarStyleFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/navbar/ChooseNavigationBarStyleFragment.kt new file mode 100644 index 0000000000..a072d86faa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/appearance/navbar/ChooseNavigationBarStyleFragment.kt @@ -0,0 +1,90 @@ +package org.thoughtcrime.securesms.components.settings.app.appearance.navbar + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.View +import androidx.annotation.DrawableRes +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.setFragmentResult +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.ViewBinderDelegate +import org.thoughtcrime.securesms.databinding.ChooseNavigationBarStyleFragmentBinding +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.FeatureFlags + +/** + * Allows the user to choose between a compact and full-sized navigation bar. + */ +class ChooseNavigationBarStyleFragment : DialogFragment(R.layout.choose_navigation_bar_style_fragment) { + private val binding by ViewBinderDelegate(ChooseNavigationBarStyleFragmentBinding::bind) + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + + return dialog + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + presentToggleState(SignalStore.settings().useCompactNavigationBar) + + binding.toggle.addOnButtonCheckedListener { group, checkedId, isChecked -> + if (isChecked) { + presentToggleState(checkedId == R.id.compact) + } + } + + binding.ok.setOnClickListener { + val isCompact = binding.toggle.checkedButtonId == R.id.compact + SignalStore.settings().useCompactNavigationBar = isCompact + dismissAllowingStateLoss() + setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to true)) + } + } + + private fun presentToggleState(isCompact: Boolean) { + binding.toggle.check(if (isCompact) R.id.compact else R.id.normal) + binding.image.setImageResource(PreviewImages.getImageResourceId(isCompact)) + binding.normal.setIconResource(if (isCompact) 0 else R.drawable.ic_check_20) + binding.compact.setIconResource(if (isCompact) R.drawable.ic_check_20 else 0) + } + + private sealed class PreviewImages( + @DrawableRes private val compact: Int, + @DrawableRes private val normal: Int + ) { + + @DrawableRes + fun getImageResource(isCompact: Boolean): Int { + return if (isCompact) compact else normal + } + + private object ThreeButtons : PreviewImages( + compact = R.drawable.navbar_compact, + normal = R.drawable.navbar_normal + ) + + private object TwoButtons : PreviewImages( + compact = R.drawable.navbar_compact_2, + normal = R.drawable.navbar_normal_2 + ) + + companion object { + @DrawableRes + fun getImageResourceId(isCompact: Boolean): Int { + return if (FeatureFlags.callsTab()) { + ThreeButtons.getImageResource(isCompact) + } else { + TwoButtons.getImageResource(isCompact) + } + } + } + } + + companion object { + const val REQUEST_KEY = "ChooseNavigationBarStyle" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index 75ca2f7e88..3d96790808 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.webrtc.CallBandwidthMode; import java.util.Arrays; -import java.util.Collections; import java.util.List; @SuppressWarnings("deprecation") @@ -71,6 +70,7 @@ public final class SettingsValues extends SignalStoreValues { private static final String SENT_MEDIA_QUALITY = "settings.sentMediaQuality"; private static final String CENSORSHIP_CIRCUMVENTION_ENABLED = "settings.censorshipCircumventionEnabled"; private static final String KEEP_MUTED_CHATS_ARCHIVED = "settings.keepMutedChatsArchived"; + private static final String USE_COMPACT_NAVIGATION_BAR = "settings.useCompactNavigationBar"; private final SingleLiveEvent onConfigurationSettingChanged = new SingleLiveEvent<>(); @@ -115,7 +115,8 @@ public final class SettingsValues extends SignalStoreValues { NOTIFY_WHEN_CONTACT_JOINS_SIGNAL, UNIVERSAL_EXPIRE_TIMER, SENT_MEDIA_QUALITY, - KEEP_MUTED_CHATS_ARCHIVED); + KEEP_MUTED_CHATS_ARCHIVED, + USE_COMPACT_NAVIGATION_BAR); } public @NonNull LiveData getOnConfigurationSettingChanged() { @@ -455,6 +456,14 @@ public final class SettingsValues extends SignalStoreValues { return getBoolean(KEEP_MUTED_CHATS_ARCHIVED, false); } + public void setUseCompactNavigationBar(boolean enabled) { + putBoolean(USE_COMPACT_NAVIGATION_BAR, enabled); + } + + public boolean getUseCompactNavigationBar() { + return getBoolean(USE_COMPACT_NAVIGATION_BAR, false); + } + private @Nullable Uri getUri(@NonNull String key) { String uri = getString(key, ""); diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt index eaaacc1b38..2dbde6c54b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt @@ -6,6 +6,7 @@ import android.animation.ValueAnimator import android.os.Bundle import android.view.View import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat import androidx.core.view.animation.PathInterpolatorCompat import androidx.fragment.app.Fragment @@ -14,9 +15,11 @@ import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.model.KeyPath import org.signal.core.util.DimensionUnit +import org.signal.core.util.dp import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.databinding.ConversationListTabsBinding +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.visible @@ -32,9 +35,15 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { private var shouldBeImmediate = true private var pillAnimator: Animator? = null + private val largeConstraintSet: ConstraintSet = ConstraintSet() + private val smallConstraintSet: ConstraintSet = ConstraintSet() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val iconTint = ContextCompat.getColor(requireContext(), R.color.signal_colorOnSecondaryContainer) + largeConstraintSet.clone(binding.root) + smallConstraintSet.clone(requireContext(), R.layout.conversation_list_tabs_small) + binding.chatsTabIcon.addValueCallback( KeyPath("**"), LottieProperty.COLOR @@ -76,6 +85,14 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { } private fun updateTabsVisibility() { + if (SignalStore.settings().useCompactNavigationBar) { + smallConstraintSet.applyTo(binding.root) + binding.root.minHeight = 48.dp + } else { + largeConstraintSet.applyTo(binding.root) + binding.root.minHeight = 80.dp + } + listOf( binding.callsPill, binding.callsTabIcon, @@ -98,6 +115,16 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { it.visible = Stories.isFeatureEnabled() } + if (SignalStore.settings().useCompactNavigationBar) { + listOf( + binding.callsTabLabel, + binding.chatsTabLabel, + binding.storiesTabLabel + ).forEach { + it.visible = false + } + } + update(viewModel.stateSnapshot, true) } diff --git a/app/src/main/res/drawable-night/navbar_compact.xml b/app/src/main/res/drawable-night/navbar_compact.xml new file mode 100644 index 0000000000..fb962b3873 --- /dev/null +++ b/app/src/main/res/drawable-night/navbar_compact.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-night/navbar_compact_2.xml b/app/src/main/res/drawable-night/navbar_compact_2.xml new file mode 100644 index 0000000000..8f82f5136d --- /dev/null +++ b/app/src/main/res/drawable-night/navbar_compact_2.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-night/navbar_normal.xml b/app/src/main/res/drawable-night/navbar_normal.xml new file mode 100644 index 0000000000..326a682100 --- /dev/null +++ b/app/src/main/res/drawable-night/navbar_normal.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-night/navbar_normal_2.xml b/app/src/main/res/drawable-night/navbar_normal_2.xml new file mode 100644 index 0000000000..ec48edfc75 --- /dev/null +++ b/app/src/main/res/drawable-night/navbar_normal_2.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/navbar_compact.xml b/app/src/main/res/drawable/navbar_compact.xml new file mode 100644 index 0000000000..d01475049f --- /dev/null +++ b/app/src/main/res/drawable/navbar_compact.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/navbar_compact_2.xml b/app/src/main/res/drawable/navbar_compact_2.xml new file mode 100644 index 0000000000..a7385c3580 --- /dev/null +++ b/app/src/main/res/drawable/navbar_compact_2.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/navbar_normal.xml b/app/src/main/res/drawable/navbar_normal.xml new file mode 100644 index 0000000000..8562cfd19a --- /dev/null +++ b/app/src/main/res/drawable/navbar_normal.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/navbar_normal_2.xml b/app/src/main/res/drawable/navbar_normal_2.xml new file mode 100644 index 0000000000..329f058b35 --- /dev/null +++ b/app/src/main/res/drawable/navbar_normal_2.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/choose_navigation_bar_style_fragment.xml b/app/src/main/res/layout/choose_navigation_bar_style_fragment.xml new file mode 100644 index 0000000000..d1191c6f9e --- /dev/null +++ b/app/src/main/res/layout/choose_navigation_bar_style_fragment.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/conversation_list_tabs_small.xml b/app/src/main/res/layout/conversation_list_tabs_small.xml new file mode 100644 index 0000000000..661a41c8d1 --- /dev/null +++ b/app/src/main/res/layout/conversation_list_tabs_small.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3c8edce64f..d59a52c2d0 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,6 +16,7 @@ #26000000 #33000000 #40000000 + #4D000000 #66000000 #80000000 #99000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44cc5202a9..5d66a32ac8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2929,6 +2929,12 @@ Success Failed to connect Enter proxy address + + Navigation bar size + + Normal + + Compact Customize option @@ -5910,5 +5916,13 @@ Call name + + + Navigation bar size + + Normal + + Compact + diff --git a/app/src/main/res/values/text_styles.xml b/app/src/main/res/values/text_styles.xml index 58eda16516..c7adfc8248 100644 --- a/app/src/main/res/values/text_styles.xml +++ b/app/src/main/res/values/text_styles.xml @@ -13,6 +13,9 @@ 6sp + + diff --git a/core-util/src/main/java/org/signal/core/util/concurrent/RxExtensions.kt b/core-util/src/main/java/org/signal/core/util/concurrent/RxExtensions.kt index 7032127fb0..43d0bd0f47 100644 --- a/core-util/src/main/java/org/signal/core/util/concurrent/RxExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/concurrent/RxExtensions.kt @@ -3,7 +3,11 @@ package org.signal.core.util.concurrent import android.annotation.SuppressLint +import androidx.lifecycle.LifecycleOwner +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.kotlin.subscribeBy import java.lang.RuntimeException /** @@ -26,3 +30,15 @@ fun Single.safeBlockingGet(): T { } } } + +fun Flowable.observe(viewLifecycleOwner: LifecycleOwner, onNext: (T) -> Unit) { + val lifecycleDisposable = LifecycleDisposable() + lifecycleDisposable.bindTo(viewLifecycleOwner) + lifecycleDisposable += subscribeBy(onNext = onNext) +} + +fun Completable.observe(viewLifecycleOwner: LifecycleOwner, onComplete: () -> Unit) { + val lifecycleDisposable = LifecycleDisposable() + lifecycleDisposable.bindTo(viewLifecycleOwner) + lifecycleDisposable += subscribeBy(onComplete = onComplete) +}