From fe9b8a9f47bb8c5092dd1d27fe95b7a674acc68d Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 4 Aug 2021 13:21:26 -0300 Subject: [PATCH] Replace with new custom notifications page. --- .../SoundsAndNotificationsSettingsFragment.kt | 5 +- .../CustomNotificationsSettingsFragment.kt | 168 +++++++++++ .../CustomNotificationsSettingsRepository.kt | 94 ++++++ .../CustomNotificationsSettingsState.kt | 15 + .../CustomNotificationsSettingsViewModel.kt | 73 +++++ .../CustomNotificationsDialogFragment.java | 272 ------------------ .../CustomNotificationsRepository.java | 116 -------- .../CustomNotificationsViewModel.java | 123 -------- .../custom_notifications_dialog_fragment.xml | 255 ---------------- .../res/navigation/conversation_settings.xml | 18 ++ 10 files changed, 371 insertions(+), 768 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsRepository.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsViewModel.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java delete mode 100644 app/src/main/res/layout/custom_notifications_dialog_fragment.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt index 5d1f020286..f83639b1ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.components.settings.conversation.sounds import androidx.fragment.app.viewModels +import androidx.navigation.Navigation import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.R @@ -13,7 +14,6 @@ import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.components.settings.conversation.preferences.Utils.formatMutedUntil import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment( titleId = R.string.ConversationSettingsFragment__sounds_and_notifications @@ -110,7 +110,8 @@ class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment( icon = DSLSettingsIcon.from(R.drawable.ic_speaker_24), summary = DSLSettingsText.from(customSoundSummary), onClick = { - CustomNotificationsDialogFragment.create(state.recipientId).show(parentFragmentManager, null) + val action = SoundsAndNotificationsSettingsFragmentDirections.actionSoundsAndNotificationsSettingsFragmentToCustomNotificationsSettingsFragment(state.recipientId) + Navigation.findNavController(requireView()).navigate(action) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt new file mode 100644 index 0000000000..7bc4c7cf76 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt @@ -0,0 +1,168 @@ +package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.RingtoneManager +import android.net.Uri +import android.provider.Settings +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.viewModels +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.database.RecipientDatabase +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.util.RingtoneUtil + +class CustomNotificationsSettingsFragment : DSLSettingsFragment(R.string.CustomNotificationsDialogFragment__custom_notifications) { + + private val vibrateLabels: Array by lazy { + resources.getStringArray(R.array.recipient_vibrate_entries) + } + + private val viewModel: CustomNotificationsSettingsViewModel by viewModels(factoryProducer = this::createFactory) + + private lateinit var callSoundResultLauncher: ActivityResultLauncher + private lateinit var messageSoundResultLauncher: ActivityResultLauncher + + private fun createFactory(): CustomNotificationsSettingsViewModel.Factory { + val recipientId = CustomNotificationsSettingsFragmentArgs.fromBundle(requireArguments()).recipientId + val repository = CustomNotificationsSettingsRepository(requireContext()) + + return CustomNotificationsSettingsViewModel.Factory(recipientId, repository) + } + + override fun bindAdapter(adapter: DSLSettingsAdapter) { + messageSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleResult(result, viewModel::setMessageSound) + } + + callSoundResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleResult(result, viewModel::setCallSound) + } + + viewModel.state.observe(viewLifecycleOwner) { state -> + adapter.submitList(getConfiguration(state).toMappingModelList()) + } + } + + private fun handleResult(result: ActivityResult, resultHandler: (Uri?) -> Unit) { + val resultCode = result.resultCode + val data = result.data + + if (resultCode == Activity.RESULT_OK && data != null) { + val uri: Uri? = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + resultHandler(uri) + } + } + + private fun getConfiguration(state: CustomNotificationsSettingsState): DSLConfiguration { + return configure { + + val controlsEnabled = state.hasCustomNotifications && state.isInitialLoadComplete + + sectionHeaderPref(R.string.CustomNotificationsDialogFragment__messages) + + if (NotificationChannels.supported()) { + switchPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__use_custom_notifications), + isEnabled = state.isInitialLoadComplete, + isChecked = state.hasCustomNotifications, + onClick = { viewModel.setHasCustomNotifications(!state.hasCustomNotifications) } + ) + } + + clickPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__notification_sound), + summary = DSLSettingsText.from(getRingtoneSummary(requireContext(), state.messageSound, Settings.System.DEFAULT_NOTIFICATION_URI)), + isEnabled = controlsEnabled, + onClick = { requestSound(state.messageSound, false) } + ) + + if (NotificationChannels.supported()) { + switchPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate), + isEnabled = controlsEnabled, + isChecked = state.messageVibrateEnabled, + onClick = { viewModel.setMessageVibrate(RecipientDatabase.VibrateState.fromBoolean(!state.messageVibrateEnabled)) } + ) + } else { + radioListPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate), + isEnabled = controlsEnabled, + listItems = vibrateLabels, + selected = state.messageVibrateState.id, + onSelected = { + viewModel.setMessageVibrate(RecipientDatabase.VibrateState.fromId(it)) + } + ) + } + + if (state.showCallingOptions) { + dividerPref() + + sectionHeaderPref(R.string.CustomNotificationsDialogFragment__call_settings) + + clickPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__ringtone), + summary = DSLSettingsText.from(getRingtoneSummary(requireContext(), state.callSound, Settings.System.DEFAULT_RINGTONE_URI)), + isEnabled = controlsEnabled, + onClick = { requestSound(state.callSound, true) } + ) + + radioListPref( + title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__vibrate), + isEnabled = controlsEnabled, + listItems = vibrateLabels, + selected = state.callVibrateState.id, + onSelected = { + viewModel.setCallVibrate(RecipientDatabase.VibrateState.fromId(it)) + } + ) + } + } + } + + private fun getRingtoneSummary(context: Context, ringtone: Uri?, defaultNotificationUri: Uri?): String { + if (ringtone == null || ringtone == defaultNotificationUri) { + return context.getString(R.string.CustomNotificationsDialogFragment__default) + } else if (ringtone.toString().isEmpty()) { + return context.getString(R.string.preferences__silent) + } else { + val tone = RingtoneUtil.getRingtone(requireContext(), ringtone) + if (tone != null) { + return tone.getTitle(context) + } + } + return context.getString(R.string.CustomNotificationsDialogFragment__default) + } + + private fun requestSound(current: Uri?, forCalls: Boolean) { + val existing: Uri? = when { + current == null -> getDefaultSound(forCalls) + current.toString().isEmpty() -> null + else -> current + } + + val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { + putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true) + putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) + putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, if (forCalls) RingtoneManager.TYPE_RINGTONE else RingtoneManager.TYPE_NOTIFICATION) + putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existing) + } + + if (forCalls) { + callSoundResultLauncher.launch(intent) + } else { + messageSoundResultLauncher.launch(intent) + } + } + + private fun getDefaultSound(forCalls: Boolean) = if (forCalls) Settings.System.DEFAULT_RINGTONE_URI else Settings.System.DEFAULT_NOTIFICATION_URI +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsRepository.kt new file mode 100644 index 0000000000..b9beb8cb87 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsRepository.kt @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom + +import android.content.Context +import android.net.Uri +import androidx.annotation.WorkerThread +import org.signal.core.util.concurrent.SignalExecutors +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.concurrent.SerialExecutor + +class CustomNotificationsSettingsRepository(context: Context) { + + private val context = context.applicationContext + private val executor = SerialExecutor(SignalExecutors.BOUNDED) + + fun initialize(recipientId: RecipientId, onInitializationComplete: () -> Unit) { + executor.execute { + val recipient = Recipient.resolved(recipientId) + val database = DatabaseFactory.getRecipientDatabase(context) + + if (NotificationChannels.supported() && recipient.notificationChannel != null) { + database.setMessageRingtone(recipient.id, NotificationChannels.getMessageRingtone(context, recipient)) + database.setMessageVibrate(recipient.id, RecipientDatabase.VibrateState.fromBoolean(NotificationChannels.getMessageVibrate(context, recipient))) + + NotificationChannels.ensureCustomChannelConsistency(context) + } + + onInitializationComplete() + } + } + + fun setHasCustomNotifications(recipientId: RecipientId, hasCustomNotifications: Boolean) { + executor.execute { + if (hasCustomNotifications) { + createCustomNotificationChannel(recipientId) + } else { + deleteCustomNotificationChannel(recipientId) + } + } + } + + fun setMessageVibrate(recipientId: RecipientId, vibrateState: RecipientDatabase.VibrateState) { + executor.execute { + val recipient: Recipient = Recipient.resolved(recipientId) + + DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient.id, vibrateState) + NotificationChannels.updateMessageVibrate(context, recipient, vibrateState) + } + } + + fun setCallingVibrate(recipientId: RecipientId, vibrateState: RecipientDatabase.VibrateState) { + executor.execute { + DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipientId, vibrateState) + } + } + + fun setMessageSound(recipientId: RecipientId, sound: Uri?) { + executor.execute { + val recipient: Recipient = Recipient.resolved(recipientId) + val defaultValue = SignalStore.settings().messageNotificationSound + val newValue: Uri? = if (defaultValue == sound) null else sound ?: Uri.EMPTY + + DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.id, newValue) + NotificationChannels.updateMessageRingtone(context, recipient, newValue) + } + } + + fun setCallSound(recipientId: RecipientId, sound: Uri?) { + executor.execute { + val defaultValue = SignalStore.settings().callRingtone + val newValue: Uri? = if (defaultValue == sound) null else sound ?: Uri.EMPTY + + DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipientId, newValue) + } + } + + @WorkerThread + private fun createCustomNotificationChannel(recipientId: RecipientId) { + val recipient: Recipient = Recipient.resolved(recipientId) + val channelId = NotificationChannels.createChannelFor(context, recipient) + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.id, channelId) + } + + @WorkerThread + private fun deleteCustomNotificationChannel(recipientId: RecipientId) { + val recipient: Recipient = Recipient.resolved(recipientId) + DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.id, null) + NotificationChannels.deleteChannelFor(context, recipient) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt new file mode 100644 index 0000000000..2c919652fb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom + +import android.net.Uri +import org.thoughtcrime.securesms.database.RecipientDatabase + +data class CustomNotificationsSettingsState( + val isInitialLoadComplete: Boolean = false, + val hasCustomNotifications: Boolean = false, + val messageVibrateState: RecipientDatabase.VibrateState = RecipientDatabase.VibrateState.DEFAULT, + val messageVibrateEnabled: Boolean = false, + val messageSound: Uri? = null, + val callVibrateState: RecipientDatabase.VibrateState = RecipientDatabase.VibrateState.DEFAULT, + val callSound: Uri? = null, + val showCallingOptions: Boolean = false, +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsViewModel.kt new file mode 100644 index 0000000000..5ca3e67454 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsViewModel.kt @@ -0,0 +1,73 @@ +package org.thoughtcrime.securesms.components.settings.conversation.sounds.custom + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.livedata.Store + +class CustomNotificationsSettingsViewModel( + private val recipientId: RecipientId, + private val repository: CustomNotificationsSettingsRepository +) : ViewModel() { + + private val store = Store(CustomNotificationsSettingsState()) + + val state: LiveData = store.stateLiveData + + init { + repository.initialize(recipientId) { + store.update { it.copy(isInitialLoadComplete = true) } + } + + store.update(Recipient.live(recipientId).liveData) { recipient, state -> + state.copy( + hasCustomNotifications = NotificationChannels.supported() && recipient.notificationChannel != null, + messageSound = recipient.messageRingtone, + messageVibrateState = recipient.messageVibrate, + messageVibrateEnabled = when (recipient.messageVibrate) { + RecipientDatabase.VibrateState.DEFAULT -> SignalStore.settings().isMessageVibrateEnabled + RecipientDatabase.VibrateState.ENABLED -> true + RecipientDatabase.VibrateState.DISABLED -> false + }, + showCallingOptions = !recipient.isGroup && recipient.isRegistered, + callSound = recipient.callRingtone, + callVibrateState = recipient.callVibrate + ) + } + } + + fun setHasCustomNotifications(hasCustomNotifications: Boolean) { + repository.setHasCustomNotifications(recipientId, hasCustomNotifications) + } + + fun setMessageVibrate(messageVibrateState: RecipientDatabase.VibrateState) { + repository.setMessageVibrate(recipientId, messageVibrateState) + } + + fun setMessageSound(uri: Uri?) { + repository.setMessageSound(recipientId, uri) + } + + fun setCallVibrate(callVibrateState: RecipientDatabase.VibrateState) { + repository.setCallingVibrate(recipientId, callVibrateState) + } + + fun setCallSound(uri: Uri?) { + repository.setCallSound(recipientId, uri) + } + + class Factory( + private val recipientId: RecipientId, + private val repository: CustomNotificationsSettingsRepository + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return requireNotNull(modelClass.cast(CustomNotificationsSettingsViewModel(recipientId, repository))) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java deleted file mode 100644 index ecb7be34f2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.thoughtcrime.securesms.recipients.ui.notifications; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SwitchCompat; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.DialogFragment; -import androidx.lifecycle.ViewModelProviders; - -import com.annimon.stream.function.Consumer; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.RingtoneUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -import java.util.Objects; - -public class CustomNotificationsDialogFragment extends DialogFragment { - - private static final String TAG = Log.tag(CustomNotificationsDialogFragment.class); - - private static final short MESSAGE_RINGTONE_PICKER_REQUEST_CODE = 13562; - private static final short CALL_RINGTONE_PICKER_REQUEST_CODE = 23621; - - private static final String ARG_RECIPIENT_ID = "recipient_id"; - - private View customNotificationsRow; - private SwitchCompat customNotificationsSwitch; - private View soundRow; - private View soundLabel; - private TextView soundSelector; - private View messageVibrateRow; - private View messageVibrateLabel; - private TextView messageVibrateSelector; - private SwitchCompat messageVibrateSwitch; - private View callHeading; - private View ringtoneRow; - private TextView ringtoneSelector; - private View callVibrateRow; - private TextView callVibrateSelector; - - private CustomNotificationsViewModel viewModel; - - public static DialogFragment create(@NonNull RecipientId recipientId) { - DialogFragment fragment = new CustomNotificationsDialogFragment(); - Bundle args = new Bundle(); - - args.putParcelable(ARG_RECIPIENT_ID, recipientId); - fragment.setArguments(args); - - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_Animated); - } - - @Override - public @Nullable View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) - { - return inflater.inflate(R.layout.custom_notifications_dialog_fragment, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - initializeViewModel(); - initializeViews(view); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (resultCode == Activity.RESULT_OK && data != null) { - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - if (requestCode == MESSAGE_RINGTONE_PICKER_REQUEST_CODE) { - viewModel.setMessageSound(uri); - } else if (requestCode == CALL_RINGTONE_PICKER_REQUEST_CODE) { - viewModel.setCallSound(uri); - } - } - } - - private void initializeViewModel() { - Bundle arguments = requireArguments(); - RecipientId recipientId = Objects.requireNonNull(arguments.getParcelable(ARG_RECIPIENT_ID)); - CustomNotificationsRepository repository = new CustomNotificationsRepository(requireContext(), recipientId); - CustomNotificationsViewModel.Factory factory = new CustomNotificationsViewModel.Factory(recipientId, repository); - - viewModel = ViewModelProviders.of(this, factory).get(CustomNotificationsViewModel.class); - } - - private void initializeViews(@NonNull View view) { - customNotificationsRow = view.findViewById(R.id.custom_notifications_row); - customNotificationsSwitch = view.findViewById(R.id.custom_notifications_enable_switch); - soundRow = view.findViewById(R.id.custom_notifications_sound_row); - soundLabel = view.findViewById(R.id.custom_notifications_sound_label); - soundSelector = view.findViewById(R.id.custom_notifications_sound_selection); - messageVibrateSwitch = view.findViewById(R.id.custom_notifications_vibrate_switch); - messageVibrateRow = view.findViewById(R.id.custom_notifications_message_vibrate_row); - messageVibrateLabel = view.findViewById(R.id.custom_notifications_message_vibrate_label); - messageVibrateSelector = view.findViewById(R.id.custom_notifications_message_vibrate_selector); - callHeading = view.findViewById(R.id.custom_notifications_call_settings_section_header); - ringtoneRow = view.findViewById(R.id.custom_notifications_ringtone_row); - ringtoneSelector = view.findViewById(R.id.custom_notifications_ringtone_selection); - callVibrateRow = view.findViewById(R.id.custom_notifications_call_vibrate_row); - callVibrateSelector = view.findViewById(R.id.custom_notifications_call_vibrate_selectior); - - Toolbar toolbar = view.findViewById(R.id.custom_notifications_toolbar); - - toolbar.setNavigationOnClickListener(v -> dismissAllowingStateLoss()); - - CompoundButton.OnCheckedChangeListener onCustomNotificationsSwitchCheckChangedListener = (buttonView, isChecked) -> { - viewModel.setHasCustomNotifications(isChecked); - }; - - viewModel.isInitialLoadComplete().observe(getViewLifecycleOwner(), customNotificationsSwitch::setEnabled); - - if (NotificationChannels.supported()) { - viewModel.hasCustomNotifications().observe(getViewLifecycleOwner(), hasCustomNotifications -> { - if (customNotificationsSwitch.isChecked() != hasCustomNotifications) { - customNotificationsSwitch.setOnCheckedChangeListener(null); - customNotificationsSwitch.setChecked(hasCustomNotifications); - } - - customNotificationsSwitch.setOnCheckedChangeListener(onCustomNotificationsSwitchCheckChangedListener); - customNotificationsRow.setOnClickListener(v -> customNotificationsSwitch.toggle()); - - soundRow.setEnabled(hasCustomNotifications); - soundLabel.setEnabled(hasCustomNotifications); - messageVibrateRow.setEnabled(hasCustomNotifications); - messageVibrateLabel.setEnabled(hasCustomNotifications); - soundSelector.setVisibility(hasCustomNotifications ? View.VISIBLE : View.GONE); - messageVibrateSwitch.setVisibility(hasCustomNotifications ? View.VISIBLE : View.GONE); - }); - - messageVibrateSelector.setVisibility(View.GONE); - messageVibrateSwitch.setVisibility(View.VISIBLE); - - messageVibrateRow.setOnClickListener(v -> messageVibrateSwitch.toggle()); - - CompoundButton.OnCheckedChangeListener onVibrateSwitchCheckChangedListener = (buttonView, isChecked) -> viewModel.setMessageVibrate(RecipientDatabase.VibrateState.fromBoolean(isChecked)); - - viewModel.getMessageVibrateToggle().observe(getViewLifecycleOwner(), vibrateEnabled -> { - if (messageVibrateSwitch.isChecked() != vibrateEnabled) { - messageVibrateSwitch.setOnCheckedChangeListener(null); - messageVibrateSwitch.setChecked(vibrateEnabled); - } - - messageVibrateSwitch.setOnCheckedChangeListener(onVibrateSwitchCheckChangedListener); - }); - } else { - customNotificationsRow.setVisibility(View.GONE); - - messageVibrateSwitch.setVisibility(View.GONE); - messageVibrateSelector.setVisibility(View.VISIBLE); - - soundRow.setEnabled(true); - soundLabel.setEnabled(true); - messageVibrateRow.setEnabled(true); - messageVibrateLabel.setEnabled(true); - soundSelector.setVisibility(View.VISIBLE); - - viewModel.getMessageVibrateState().observe(getViewLifecycleOwner(), vibrateState -> presentVibrateState(vibrateState, this.messageVibrateRow, this.messageVibrateSelector, (w) -> viewModel.setMessageVibrate(w))); - } - - viewModel.getNotificationSound().observe(getViewLifecycleOwner(), sound -> { - soundSelector.setText(getRingtoneSummary(requireContext(), sound, Settings.System.DEFAULT_NOTIFICATION_URI)); - soundSelector.setTag(sound); - soundRow.setOnClickListener(v -> launchSoundSelector(sound, false)); - }); - - viewModel.getShowCallingOptions().observe(getViewLifecycleOwner(), showCalling -> { - callHeading.setVisibility(showCalling ? View.VISIBLE : View.GONE); - ringtoneRow.setVisibility(showCalling ? View.VISIBLE : View.GONE); - callVibrateRow.setVisibility(showCalling ? View.VISIBLE : View.GONE); - }); - - viewModel.getRingtone().observe(getViewLifecycleOwner(), sound -> { - ringtoneSelector.setText(getRingtoneSummary(requireContext(), sound, Settings.System.DEFAULT_RINGTONE_URI)); - ringtoneSelector.setTag(sound); - ringtoneRow.setOnClickListener(v -> launchSoundSelector(sound, true)); - }); - - viewModel.getCallingVibrateState().observe(getViewLifecycleOwner(), vibrateState -> presentVibrateState(vibrateState, this.callVibrateRow, this.callVibrateSelector, (w) -> viewModel.setCallingVibrate(w))); - } - - private void presentVibrateState(@NonNull RecipientDatabase.VibrateState vibrateState, - @NonNull View vibrateRow, - @NonNull TextView vibrateSelector, - @NonNull Consumer onSelect) - { - vibrateSelector.setText(getVibrateSummary(requireContext(), vibrateState)); - vibrateRow.setOnClickListener(v -> new AlertDialog.Builder(requireContext()) - .setTitle(R.string.CustomNotificationsDialogFragment__vibrate) - .setSingleChoiceItems(R.array.recipient_vibrate_entries, vibrateState.ordinal(), ((dialog, which) -> { - onSelect.accept(RecipientDatabase.VibrateState.fromId(which)); - dialog.dismiss(); - })) - .setNegativeButton(android.R.string.cancel, null) - .show()); - } - - private @NonNull String getRingtoneSummary(@NonNull Context context, @Nullable Uri ringtone, @Nullable Uri defaultNotificationUri) { - if (ringtone == null || ringtone.equals(defaultNotificationUri)) { - return context.getString(R.string.CustomNotificationsDialogFragment__default); - } else if (ringtone.toString().isEmpty()) { - return context.getString(R.string.preferences__silent); - } else { - Ringtone tone = RingtoneUtil.getRingtone(requireContext(), ringtone); - if (tone != null) { - return tone.getTitle(context); - } - } - - return context.getString(R.string.CustomNotificationsDialogFragment__default); - } - - private void launchSoundSelector(@Nullable Uri current, boolean calls) { - Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - - if (current == null) current = calls ? Settings.System.DEFAULT_RINGTONE_URI : Settings.System.DEFAULT_NOTIFICATION_URI; - else if (current.toString().isEmpty()) current = null; - - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, calls ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultSound(calls)); - - startActivityForResult(intent, calls ? CALL_RINGTONE_PICKER_REQUEST_CODE : MESSAGE_RINGTONE_PICKER_REQUEST_CODE); - } - - private Uri defaultSound(boolean calls) { - Uri defaultValue; - - if (calls) defaultValue = SignalStore.settings().getCallRingtone(); - else defaultValue = SignalStore.settings().getMessageNotificationSound(); - return defaultValue; - } - - private static @NonNull String getVibrateSummary(@NonNull Context context, @NonNull RecipientDatabase.VibrateState vibrateState) { - switch (vibrateState) { - case DEFAULT : return context.getString(R.string.CustomNotificationsDialogFragment__default); - case ENABLED : return context.getString(R.string.CustomNotificationsDialogFragment__enabled); - case DISABLED : return context.getString(R.string.CustomNotificationsDialogFragment__disabled); - default : throw new AssertionError(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java deleted file mode 100644 index 837b61f7a2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsRepository.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.thoughtcrime.securesms.recipients.ui.notifications; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import org.signal.core.util.concurrent.SignalExecutors; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -class CustomNotificationsRepository { - - private final Context context; - private final RecipientId recipientId; - - CustomNotificationsRepository(@NonNull Context context, @NonNull RecipientId recipientId) { - this.context = context; - this.recipientId = recipientId; - } - - void onLoad(@NonNull Runnable onLoaded) { - SignalExecutors.SERIAL.execute(() -> { - Recipient recipient = getRecipient(); - RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); - - if (NotificationChannels.supported() && recipient.getNotificationChannel() != null) { - recipientDatabase.setMessageRingtone(recipient.getId(), NotificationChannels.getMessageRingtone(context, recipient)); - recipientDatabase.setMessageVibrate(recipient.getId(), RecipientDatabase.VibrateState.fromBoolean(NotificationChannels.getMessageVibrate(context, recipient))); - - NotificationChannels.ensureCustomChannelConsistency(context); - } - - onLoaded.run(); - }); - } - - void setHasCustomNotifications(final boolean hasCustomNotifications) { - SignalExecutors.SERIAL.execute(() -> { - if (hasCustomNotifications) { - createCustomNotificationChannel(); - } else { - deleteCustomNotificationChannel(); - } - }); - } - - void setMessageVibrate(final RecipientDatabase.VibrateState vibrateState) { - SignalExecutors.SERIAL.execute(() -> { - Recipient recipient = getRecipient(); - - DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient.getId(), vibrateState); - NotificationChannels.updateMessageVibrate(context, recipient, vibrateState); - }); - } - - void setCallingVibrate(final RecipientDatabase.VibrateState vibrateState) { - SignalExecutors.SERIAL.execute(() -> DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipientId, vibrateState)); - } - - void setMessageSound(@Nullable Uri sound) { - SignalExecutors.SERIAL.execute(() -> { - Recipient recipient = getRecipient(); - Uri defaultValue = SignalStore.settings().getMessageNotificationSound(); - Uri newValue; - - if (defaultValue.equals(sound)) newValue = null; - else if (sound == null) newValue = Uri.EMPTY; - else newValue = sound; - - DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.getId(), newValue); - NotificationChannels.updateMessageRingtone(context, recipient, newValue); - }); - } - - void setCallSound(@Nullable Uri sound) { - SignalExecutors.SERIAL.execute(() -> { - Uri defaultValue = SignalStore.settings().getCallRingtone(); - Uri newValue; - - if (defaultValue.equals(sound)) newValue = null; - else if (sound == null) newValue = Uri.EMPTY; - else newValue = sound; - - DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipientId, newValue); - }); - } - - @WorkerThread - private void createCustomNotificationChannel() { - Recipient recipient = getRecipient(); - String channelId = NotificationChannels.createChannelFor(context, recipient); - - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), channelId); - } - - @WorkerThread - private void deleteCustomNotificationChannel() { - Recipient recipient = getRecipient(); - - DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), null); - NotificationChannels.deleteChannelFor(context, recipient); - } - - @WorkerThread - private @NonNull Recipient getRecipient() { - return Recipient.resolved(recipientId); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java deleted file mode 100644 index b7462cfc21..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsViewModel.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.thoughtcrime.securesms.recipients.ui.notifications; - -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Transformations; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -public final class CustomNotificationsViewModel extends ViewModel { - - private final LiveData hasCustomNotifications; - private final LiveData messageVibrateState; - private final LiveData notificationSound; - private final CustomNotificationsRepository repository; - private final MutableLiveData isInitialLoadComplete = new MutableLiveData<>(); - private final LiveData showCallingOptions; - private final LiveData ringtone; - private final LiveData callingVibrateState; - private final LiveData messageVibrateToggle; - - private CustomNotificationsViewModel(@NonNull RecipientId recipientId, @NonNull CustomNotificationsRepository repository) { - LiveData recipient = Recipient.live(recipientId).getLiveData(); - - this.repository = repository; - this.hasCustomNotifications = Transformations.map(recipient, r -> r.getNotificationChannel() != null || !NotificationChannels.supported()); - this.callingVibrateState = Transformations.map(recipient, Recipient::getCallVibrate); - this.messageVibrateState = Transformations.map(recipient, Recipient::getMessageVibrate); - this.notificationSound = Transformations.map(recipient, Recipient::getMessageRingtone); - this.showCallingOptions = Transformations.map(recipient, r -> !r.isGroup() && r.isRegistered()); - this.ringtone = Transformations.map(recipient, Recipient::getCallRingtone); - this.messageVibrateToggle = Transformations.map(messageVibrateState, vibrateState -> { - switch (vibrateState) { - case DISABLED: return false; - case ENABLED : return true; - case DEFAULT : return SignalStore.settings().isMessageVibrateEnabled(); - default : throw new AssertionError(); - } - }); - - repository.onLoad(() -> isInitialLoadComplete.postValue(true)); - } - - LiveData isInitialLoadComplete() { - return isInitialLoadComplete; - } - - LiveData hasCustomNotifications() { - return hasCustomNotifications; - } - - LiveData getNotificationSound() { - return notificationSound; - } - - LiveData getMessageVibrateState() { - return messageVibrateState; - } - - LiveData getMessageVibrateToggle() { - return messageVibrateToggle; - } - - void setHasCustomNotifications(boolean hasCustomNotifications) { - repository.setHasCustomNotifications(hasCustomNotifications); - } - - void setMessageVibrate(@NonNull RecipientDatabase.VibrateState vibrateState) { - repository.setMessageVibrate(vibrateState); - } - - void setMessageSound(@Nullable Uri sound) { - repository.setMessageSound(sound); - } - - void setCallSound(@Nullable Uri sound) { - repository.setCallSound(sound); - } - - LiveData getShowCallingOptions() { - return showCallingOptions; - } - - LiveData getRingtone() { - return ringtone; - } - - LiveData getCallingVibrateState() { - return callingVibrateState; - } - - void setCallingVibrate(@NonNull RecipientDatabase.VibrateState vibrateState) { - repository.setCallingVibrate(vibrateState); - } - - public static final class Factory implements ViewModelProvider.Factory { - - private final RecipientId recipientId; - private final CustomNotificationsRepository repository; - - public Factory(@NonNull RecipientId recipientId, @NonNull CustomNotificationsRepository repository) { - this.recipientId = recipientId; - this.repository = repository; - } - - @Override - public @NonNull T create(@NonNull Class modelClass) { - //noinspection ConstantConditions - return modelClass.cast(new CustomNotificationsViewModel(recipientId, repository)); - } - } -} diff --git a/app/src/main/res/layout/custom_notifications_dialog_fragment.xml b/app/src/main/res/layout/custom_notifications_dialog_fragment.xml deleted file mode 100644 index 3a7470704c..0000000000 --- a/app/src/main/res/layout/custom_notifications_dialog_fragment.xml +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/conversation_settings.xml b/app/src/main/res/navigation/conversation_settings.xml index 2c9c75ea39..6e91c3f93f 100644 --- a/app/src/main/res/navigation/conversation_settings.xml +++ b/app/src/main/res/navigation/conversation_settings.xml @@ -85,6 +85,14 @@ android:name="recipient_id" app:argType="org.thoughtcrime.securesms.recipients.RecipientId" /> + + + + + + + + \ No newline at end of file