package org.thoughtcrime.securesms.components.settings.app.notifications.profiles import android.os.Bundle import android.text.Editable import android.text.TextUtils import android.view.View import android.widget.EditText import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import com.google.android.material.textfield.TextInputLayout import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.core.util.BreakIteratorCompat import org.signal.core.util.EditTextUtil import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.emoji.EmojiUtil import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.EditNotificationProfileViewModel.SaveNotificationProfileResult import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileNamePreset import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment import org.thoughtcrime.securesms.util.BottomSheetUtil import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.text.AfterTextChanged import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton /** * Dual use Edit/Create notification profile fragment. Use to create in the create profile flow, * and then to edit from profile details. Responsible for naming and emoji. */ class EditNotificationProfileFragment : DSLSettingsFragment(layoutId = R.layout.fragment_edit_notification_profile), ReactWithAnyEmojiBottomSheetDialogFragment.Callback { private val viewModel: EditNotificationProfileViewModel by viewModels(factoryProducer = this::createFactory) private val lifecycleDisposable = LifecycleDisposable() private var emojiView: ImageView? = null private var nameView: EditText? = null private fun createFactory(): ViewModelProvider.Factory { val profileId = EditNotificationProfileFragmentArgs.fromBundle(requireArguments()).profileId return EditNotificationProfileViewModel.Factory(profileId) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val toolbar: Toolbar = view.findViewById(R.id.toolbar) toolbar.setNavigationOnClickListener { ViewUtil.hideKeyboard(requireContext(), requireView()) requireActivity().onBackPressed() } val title: TextView = view.findViewById(R.id.edit_notification_profile_title) val countView: TextView = view.findViewById(R.id.edit_notification_profile_count) val saveButton: CircularProgressMaterialButton = view.findViewById(R.id.edit_notification_profile_save) val emojiView: ImageView = view.findViewById(R.id.edit_notification_profile_emoji) val nameView: EditText = view.findViewById(R.id.edit_notification_profile_name) val nameTextWrapper: TextInputLayout = view.findViewById(R.id.edit_notification_profile_name_wrapper) EditTextUtil.addGraphemeClusterLimitFilter(nameView, NOTIFICATION_PROFILE_NAME_MAX_GLYPHS) nameView.addTextChangedListener( AfterTextChanged { editable: Editable -> presentCount(countView, editable.toString()) nameTextWrapper.error = null } ) emojiView.setOnClickListener { ReactWithAnyEmojiBottomSheetDialogFragment.createForAboutSelection() .show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) } view.findViewById(R.id.edit_notification_profile_clear).setOnClickListener { nameView.setText("") onEmojiSelectedInternal("") } lifecycleDisposable.bindTo(viewLifecycleOwner.lifecycle) saveButton.setOnClickListener { if (TextUtils.isEmpty(nameView.text)) { nameTextWrapper.error = getString(R.string.EditNotificationProfileFragment__profile_must_have_a_name) return@setOnClickListener } lifecycleDisposable += viewModel.save(nameView.text.toString()) .doOnSubscribe { saveButton.setSpinning() } .doAfterTerminate { saveButton.cancelSpinning() } .subscribeBy( onSuccess = { saveResult -> when (saveResult) { is SaveNotificationProfileResult.Success -> { ViewUtil.hideKeyboard(requireContext(), nameView) if (saveResult.createMode) { findNavController().safeNavigate(EditNotificationProfileFragmentDirections.actionEditNotificationProfileFragmentToAddAllowedMembersFragment(saveResult.profile.id)) } else { findNavController().navigateUp() } } SaveNotificationProfileResult.DuplicateNameFailure -> { nameTextWrapper.error = getString(R.string.EditNotificationProfileFragment__a_profile_with_this_name_already_exists) } } } ) } lifecycleDisposable += viewModel.getInitialState() .subscribeBy( onSuccess = { initial -> if (initial.createMode) { saveButton.text = getString(R.string.EditNotificationProfileFragment__create) title.setText(R.string.EditNotificationProfileFragment__name_your_profile) } else { saveButton.text = getString(R.string.EditNotificationProfileFragment__save) title.setText(R.string.EditNotificationProfileFragment__edit_this_profile) } nameView.setText(initial.name) onEmojiSelectedInternal(initial.emoji) ViewUtil.focusAndMoveCursorToEndAndOpenKeyboard(nameView) } ) this.nameView = nameView this.emojiView = emojiView } override fun bindAdapter(adapter: MappingAdapter) { NotificationProfileNamePreset.register(adapter) val onClick = { preset: NotificationProfileNamePreset.Model -> nameView?.apply { setText(preset.bodyResource) setSelection(length(), length()) } onEmojiSelectedInternal(preset.emoji) } adapter.submitList( listOf( NotificationProfileNamePreset.Model("\uD83D\uDCAA", R.string.EditNotificationProfileFragment__work, onClick), NotificationProfileNamePreset.Model("\uD83D\uDE34", R.string.EditNotificationProfileFragment__sleep, onClick), NotificationProfileNamePreset.Model("\uD83D\uDE97", R.string.EditNotificationProfileFragment__driving, onClick), NotificationProfileNamePreset.Model("\uD83D\uDE0A", R.string.EditNotificationProfileFragment__downtime, onClick), NotificationProfileNamePreset.Model("\uD83D\uDCA1", R.string.EditNotificationProfileFragment__focus, onClick) ) ) } override fun onReactWithAnyEmojiSelected(emoji: String) { onEmojiSelectedInternal(emoji) } override fun onReactWithAnyEmojiDialogDismissed() = Unit private fun presentCount(countView: TextView, profileName: String) { val breakIterator = BreakIteratorCompat.getInstance() breakIterator.setText(profileName) val glyphCount = breakIterator.countBreaks() if (glyphCount >= NOTIFICATION_PROFILE_NAME_LIMIT_DISPLAY_THRESHOLD) { countView.visibility = View.VISIBLE countView.text = resources.getString(R.string.EditNotificationProfileFragment__count, glyphCount, NOTIFICATION_PROFILE_NAME_MAX_GLYPHS) } else { countView.visibility = View.GONE } } private fun onEmojiSelectedInternal(emoji: String) { val drawable = EmojiUtil.convertToDrawable(requireContext(), emoji) if (drawable != null) { emojiView?.setImageDrawable(drawable) viewModel.onEmojiSelected(emoji) } else { emojiView?.setImageResource(R.drawable.symbol_emoji_plus_24) viewModel.onEmojiSelected("") } } companion object { private const val NOTIFICATION_PROFILE_NAME_MAX_GLYPHS = 32 private const val NOTIFICATION_PROFILE_NAME_LIMIT_DISPLAY_THRESHOLD = 22 } }