diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index f19b9719a8..c65cc57adc 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -172,3 +172,6 @@ getSystemService\(Context ### Use DefaultSharedPreferences.getInstance() instead for better performance PreferenceManager\.getDefaultSharedPreferences==2 + +### Use ViewBindings +findViewById diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMaterialThemeActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMaterialThemeActivity.kt index fb87ba6299..d290cc42fa 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/DebugMaterialThemeActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMaterialThemeActivity.kt @@ -24,26 +24,27 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.snackbar.Snackbar import im.vector.app.R import im.vector.app.core.utils.toast - +import im.vector.app.databinding.ActivityTestMaterialThemeBinding // Rendering is not the same with VectorBaseActivity abstract class DebugMaterialThemeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_test_material_theme) + val views = ActivityTestMaterialThemeBinding.inflate(layoutInflater) + setContentView(views.root) - debugShowSnackbar.setOnClickListener { - Snackbar.make(debugMaterialCoordinator, "Snackbar!", Snackbar.LENGTH_SHORT) + views.debugShowSnackbar.setOnClickListener { + Snackbar.make(views.debugMaterialCoordinator, "Snackbar!", Snackbar.LENGTH_SHORT) .setAction("Action") { } .show() } - debugShowToast.setOnClickListener { + views.debugShowToast.setOnClickListener { toast("Toast") } - debugShowDialog.setOnClickListener { + views.debugShowDialog.setOnClickListener { AlertDialog.Builder(this) .setMessage("Dialog content") .setIcon(R.drawable.ic_settings_x) @@ -53,7 +54,7 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() { .show() } - debugShowBottomSheet.setOnClickListener { + views.debugShowBottomSheet.setOnClickListener { BottomSheetDialogFragment().show(supportFragmentManager, "TAG") } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt index ce1deef104..b61c5bb195 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt @@ -22,15 +22,16 @@ import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import im.vector.app.R - +import im.vector.app.databinding.ActivityTestLinkifyBinding +import im.vector.app.databinding.ActivityTestMaterialThemeBinding class TestLinkifyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_test_linkify) - - test_linkify_content_view.removeAllViews() + val views = ActivityTestLinkifyBinding.inflate(layoutInflater) + setContentView(views.root) + views.testLinkifyContentView.removeAllViews() listOf( "https://www.html5rocks.com/en/tutorials/webrtc/basics/ |", @@ -79,7 +80,7 @@ class TestLinkifyActivity : AppCompatActivity() { ) .forEach { textContent -> val item = LayoutInflater.from(this) - .inflate(R.layout.item_test_linkify, test_linkify_content_view, false) + .inflate(R.layout.item_test_linkify, views.testLinkifyContentView, false) item.findViewById<TextView>(R.id.test_linkify_auto_text) ?.apply { @@ -115,7 +116,7 @@ class TestLinkifyActivity : AppCompatActivity() { // TODO Call VectorLinkify.addLinks(text) } - test_linkify_content_view.addView(item, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + views.testLinkifyContentView.addView(item, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) } } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt index 7871fa0971..a502ebc75c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt @@ -21,14 +21,18 @@ import androidx.appcompat.app.AppCompatActivity import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith +import im.vector.app.databinding.FragmentGenericRecyclerBinding import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis class DebugSasEmojiActivity : AppCompatActivity() { + private lateinit var views: FragmentGenericRecyclerBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.fragment_generic_recycler) + views = FragmentGenericRecyclerBinding.inflate(layoutInflater) + setContentView(views.root) val controller = SasEmojiController() views.genericRecyclerView.configureWith(controller) controller.setData(SasState(getAllVerificationEmojis())) diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt b/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt index ca1e9a2a2d..ed429b30c6 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt @@ -21,7 +21,7 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import im.vector.app.R - +import im.vector.app.databinding.DialogConfirmationWithReasonBinding object ConfirmationDialogBuilder { @@ -33,25 +33,26 @@ object ConfirmationDialogBuilder { @StringRes reasonHintRes: Int, confirmation: (String?) -> Unit) { val layout = activity.layoutInflater.inflate(R.layout.dialog_confirmation_with_reason, null) - layout.dialogConfirmationText.setText(confirmationRes) + val views = DialogConfirmationWithReasonBinding.bind(layout) + views.dialogConfirmationText.setText(confirmationRes) - layout.dialogReasonCheck.isVisible = askForReason - layout.dialogReasonTextInputLayout.isVisible = askForReason + views.dialogReasonCheck.isVisible = askForReason + views.dialogReasonTextInputLayout.isVisible = askForReason - layout.dialogReasonCheck.setOnCheckedChangeListener { _, isChecked -> - layout.dialogReasonTextInputLayout.isEnabled = isChecked + views.dialogReasonCheck.setOnCheckedChangeListener { _, isChecked -> + views.dialogReasonTextInputLayout.isEnabled = isChecked } if (askForReason && reasonHintRes != 0) { - layout.dialogReasonInput.setHint(reasonHintRes) + views.dialogReasonInput.setHint(reasonHintRes) } AlertDialog.Builder(activity) .setTitle(titleRes) .setView(layout) .setPositiveButton(positiveRes) { _, _ -> - val reason = layout.dialogReasonInput.text.toString() + val reason = views.dialogReasonInput.text.toString() .takeIf { askForReason } - ?.takeIf { layout.dialogReasonCheck.isChecked } + ?.takeIf { views.dialogReasonCheck.isChecked } ?.takeIf { it.isNotBlank() } confirmation(reason) } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt index b4087d5ce1..a285e8458f 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt @@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout import im.vector.app.R import im.vector.app.core.extensions.showPassword import im.vector.app.core.platform.SimpleTextWatcher +import im.vector.app.databinding.DialogExportE2eKeysBinding class ExportKeysDialog { @@ -33,48 +34,45 @@ class ExportKeysDialog { fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) + val views = DialogExportE2eKeysBinding.bind(dialogLayout) val builder = AlertDialog.Builder(activity) .setTitle(R.string.encryption_export_room_keys) .setView(dialogLayout) - val passPhrase1EditText = dialogLayout.findViewById<TextInputEditText>(R.id.exportDialogEt) - val passPhrase2EditText = dialogLayout.findViewById<TextInputEditText>(R.id.exportDialogEtConfirm) - val passPhrase2Til = dialogLayout.findViewById<TextInputLayout>(R.id.exportDialogTilConfirm) - val exportButton = dialogLayout.findViewById<Button>(R.id.exportDialogSubmit) val textWatcher = object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { when { - passPhrase1EditText.text.isNullOrEmpty() -> { - exportButton.isEnabled = false - passPhrase2Til.error = null + views.exportDialogEt.text.isNullOrEmpty() -> { + views.exportDialogSubmit.isEnabled = false + views.exportDialogTilConfirm.error = null } - passPhrase1EditText.text.toString() == passPhrase2EditText.text.toString() -> { - exportButton.isEnabled = true - passPhrase2Til.error = null + views.exportDialogEt.text.toString() == views.exportDialogEtConfirm.text.toString() -> { + views.exportDialogSubmit.isEnabled = true + views.exportDialogTilConfirm.error = null } else -> { - exportButton.isEnabled = false - passPhrase2Til.error = activity.getString(R.string.passphrase_passphrase_does_not_match) + views.exportDialogSubmit.isEnabled = false + views.exportDialogTilConfirm.error = activity.getString(R.string.passphrase_passphrase_does_not_match) } } } } - passPhrase1EditText.addTextChangedListener(textWatcher) - passPhrase2EditText.addTextChangedListener(textWatcher) + views.exportDialogEt.addTextChangedListener(textWatcher) + views.exportDialogEtConfirm.addTextChangedListener(textWatcher) val showPassword = dialogLayout.findViewById<ImageView>(R.id.exportDialogShowPassword) showPassword.setOnClickListener { passwordVisible = !passwordVisible - passPhrase1EditText.showPassword(passwordVisible) - passPhrase2EditText.showPassword(passwordVisible) + views.exportDialogEt.showPassword(passwordVisible) + views.exportDialogEtConfirm.showPassword(passwordVisible) showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) } val exportDialog = builder.show() - exportButton.setOnClickListener { - exportKeyDialogListener.onPassphrase(passPhrase1EditText.text.toString()) + views.exportDialogSubmit.setOnClickListener { + exportKeyDialogListener.onPassphrase(views.exportDialogEt.text.toString()) exportDialog.dismiss() } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt index f57212d16e..91f16f371c 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt @@ -17,9 +17,9 @@ package im.vector.app.core.dialogs import android.app.Activity -import android.widget.TextView import androidx.appcompat.app.AlertDialog import im.vector.app.R +import im.vector.app.databinding.DialogDeviceVerifyBinding import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo @@ -27,6 +27,7 @@ object ManuallyVerifyDialog { fun show(activity: Activity, cryptoDeviceInfo: CryptoDeviceInfo, onVerified: (() -> Unit)) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_device_verify, null) + val views = DialogDeviceVerifyBinding.bind(dialogLayout) val builder = AlertDialog.Builder(activity) .setTitle(R.string.cross_signing_verify_by_text) .setView(dialogLayout) @@ -35,17 +36,9 @@ object ManuallyVerifyDialog { } .setNegativeButton(R.string.cancel, null) - dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_name)?.let { - it.text = cryptoDeviceInfo.displayName() - } - - dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_id)?.let { - it.text = cryptoDeviceInfo.deviceId - } - - dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_key)?.let { - it.text = cryptoDeviceInfo.getFingerprintHumanReadable() - } + views.encryptedDeviceInfoDeviceName.text = cryptoDeviceInfo.displayName() + views.encryptedDeviceInfoDeviceId.text = cryptoDeviceInfo.deviceId + views.encryptedDeviceInfoDeviceKey.text = cryptoDeviceInfo.getFingerprintHumanReadable() builder.show() } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/PromptPasswordDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/PromptPasswordDialog.kt index 76cdf1d329..6353ab10e9 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/PromptPasswordDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/PromptPasswordDialog.kt @@ -28,6 +28,7 @@ import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.showPassword import im.vector.app.core.platform.SimpleTextWatcher +import im.vector.app.databinding.DialogPromptPasswordBinding class PromptPasswordDialog { @@ -35,20 +36,18 @@ class PromptPasswordDialog { fun show(activity: Activity, listener: (String) -> Unit) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null) - - val passwordTil = dialogLayout.findViewById<TextInputLayout>(R.id.promptPasswordTil) - val passwordEditText = dialogLayout.findViewById<TextInputEditText>(R.id.promptPassword) + val views = DialogPromptPasswordBinding.bind(dialogLayout) val textWatcher = object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { - passwordTil.error = null + views.promptPasswordTil.error = null } } - passwordEditText.addTextChangedListener(textWatcher) + views.promptPassword.addTextChangedListener(textWatcher) val showPassword = dialogLayout.findViewById<ImageView>(R.id.promptPasswordPasswordReveal) showPassword.setOnClickListener { passwordVisible = !passwordVisible - passwordEditText.showPassword(passwordVisible) + views.promptPassword.showPassword(passwordVisible) showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) } @@ -73,10 +72,10 @@ class PromptPasswordDialog { setOnShowListener { getButton(AlertDialog.BUTTON_POSITIVE) .setOnClickListener { - if (passwordEditText.text.toString().isEmpty()) { - passwordTil.error = activity.getString(R.string.error_empty_field_your_password) + if (views.promptPassword.text.toString().isEmpty()) { + views.promptPasswordTil.error = activity.getString(R.string.error_empty_field_your_password) } else { - listener.invoke(passwordEditText.text.toString()) + listener.invoke(views.promptPassword.text.toString()) dismiss() } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/KeepItSafeDialog.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/KeepItSafeDialog.kt index 31fb9d01e3..40c9001982 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/KeepItSafeDialog.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/KeepItSafeDialog.kt @@ -23,6 +23,7 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import im.vector.app.R +import im.vector.app.databinding.DialogRecoveryKeySavedInfoBinding import me.gujun.android.span.image import me.gujun.android.span.span @@ -30,10 +31,9 @@ class KeepItSafeDialog { fun show(activity: Activity) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_recovery_key_saved_info, null) + val views = DialogRecoveryKeySavedInfoBinding.bind(dialogLayout) - val descriptionText = dialogLayout.findViewById<TextView>(R.id.keepItSafeText) - - descriptionText.text = span { + views.keepItSafeText.text = span { span { image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!) +" " diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 03db33d5ac..163b98c7bd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -35,6 +35,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.ImageView import android.widget.TextView @@ -113,6 +114,7 @@ import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.shareMedia import im.vector.app.core.utils.shareText import im.vector.app.core.utils.toast +import im.vector.app.databinding.FragmentRoomDetailBinding import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.ContactAttachment @@ -232,7 +234,7 @@ class RoomDetailFragment @Inject constructor( private val roomDetailPendingActionStore: RoomDetailPendingActionStore, private val pillsPostProcessorFactory: PillsPostProcessor.Factory ) : - VectorBaseFragment(), + VectorBaseFragment<FragmentRoomDetailBinding>(), TimelineEventController.Callback, VectorInviteView.Callback, JumpToReadMarkerView.Callback, @@ -278,7 +280,9 @@ class RoomDetailFragment @Inject constructor( private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback - override fun getLayoutResId() = R.layout.fragment_room_detail + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomDetailBinding { + return FragmentRoomDetailBinding.inflate(inflater, container, false) + } override fun getMenuRes() = R.menu.menu_timeline @@ -303,7 +307,7 @@ class RoomDetailFragment @Inject constructor( sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java) attachmentsHelper = AttachmentsHelper(requireContext(), this).register() keyboardStateUtils = KeyboardStateUtils(requireActivity()) - setupToolbar(roomToolbar) + setupToolbar(views.roomToolbar) setupRecyclerView() setupComposer() setupInviteView() @@ -314,7 +318,7 @@ class RoomDetailFragment @Inject constructor( setupConfBannerView() setupEmojiPopup() - roomToolbarContentView.debouncedClicks { + views.roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) } @@ -349,7 +353,7 @@ class RoomDetailFragment @Inject constructor( } roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState -> - syncStateView.render(syncState) + views.syncStateView.render(syncState) } roomDetailViewModel.observeViewEvents { @@ -396,8 +400,8 @@ class RoomDetailFragment @Inject constructor( private fun handleChatEffect(chatEffect: ChatEffect) { when (chatEffect) { ChatEffect.CONFETTI -> { - viewKonfetti.isVisible = true - viewKonfetti.build() + views.viewKonfetti.isVisible = true + views.viewKonfetti.build() .addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA) .setDirection(0.0, 359.0) .setSpeed(2f, 5f) @@ -405,20 +409,20 @@ class RoomDetailFragment @Inject constructor( .setTimeToLive(2000L) .addShapes(Shape.Square, Shape.Circle) .addSizes(Size(12)) - .setPosition(-50f, viewKonfetti.width + 50f, -50f, -50f) + .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f) .streamFor(150, 3000L) } ChatEffect.SNOW -> { - viewSnowFall.isVisible = true - viewSnowFall.restartFalling() + views.viewSnowFall.isVisible = true + views.viewSnowFall.restartFalling() } } } private fun handleStopChatEffects() { - TransitionManager.beginDelayedTransition(rootConstraintLayout) - viewSnowFall.isVisible = false + TransitionManager.beginDelayedTransition(views.rootConstraintLayout) + views.viewSnowFall.isVisible = false // when gone the effect is a bit buggy - viewKonfetti.isInvisible = true + views.viewKonfetti.isInvisible = true } override fun onImageReady(uri: Uri?) { @@ -486,7 +490,7 @@ class RoomDetailFragment @Inject constructor( } private fun setupConfBannerView() { - activeConferenceView.callback = object : ActiveConferenceView.Callback { + views.activeConferenceView.callback = object : ActiveConferenceView.Callback { override fun onTapJoinAudio(jitsiWidget: Widget) { // need to check if allowed first roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed( @@ -513,13 +517,13 @@ class RoomDetailFragment @Inject constructor( private fun setupEmojiPopup() { val emojiPopup = EmojiPopup .Builder - .fromRootView(rootConstraintLayout) + .fromRootView(views.rootConstraintLayout) .setKeyboardAnimationStyle(R.style.emoji_fade_animation_style) - .setOnEmojiPopupShownListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_keyboard) } - .setOnEmojiPopupDismissListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_insert_emoji) } - .build(composerLayout.composerEditText) + .setOnEmojiPopupShownListener { views.composerLayout.views.composerEmojiButton.setImageResource(R.drawable.ic_keyboard) } + .setOnEmojiPopupDismissListener { views.composerLayout.views.composerEmojiButton.setImageResource(R.drawable.ic_insert_emoji) } + .build(views.composerLayout.views.composerEditText) - composerLayout.composerEmojiButton.debouncedClicks { + views.composerLayout.views.composerEmojiButton.debouncedClicks { emojiPopup.toggle() } } @@ -585,11 +589,11 @@ class RoomDetailFragment @Inject constructor( override fun onDestroyView() { timelineEventController.callback = null timelineEventController.removeModelBuildListener(modelBuildListener) - activeCallView.callback = null + views.activeCallView.callback = null modelBuildListener = null autoCompleter.clear() debouncer.cancelAll() - timelineRecyclerView.cleanup() + views.timelineRecyclerView.cleanup() super.onDestroyView() } @@ -601,10 +605,10 @@ class RoomDetailFragment @Inject constructor( } private fun setupJumpToBottomView() { - jumpToBottomView.visibility = View.INVISIBLE - jumpToBottomView.debouncedClicks { + views.jumpToBottomView.visibility = View.INVISIBLE + views.jumpToBottomView.debouncedClicks { roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) - jumpToBottomView.visibility = View.INVISIBLE + views.jumpToBottomView.visibility = View.INVISIBLE if (!roomDetailViewModel.timeline.isLive) { scrollOnNewMessageCallback.forceScrollOnNextUpdate() roomDetailViewModel.timeline.restartWithEventId(null) @@ -614,22 +618,22 @@ class RoomDetailFragment @Inject constructor( } jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager( - jumpToBottomView, + views.jumpToBottomView, debouncer, - timelineRecyclerView, + views.timelineRecyclerView, layoutManager ) } private fun setupJumpToReadMarkerView() { - jumpToReadMarkerView.callback = this + views.jumpToReadMarkerView.callback = this } private fun setupActiveCallView() { activeCallViewHolder.bind( - activeCallPiP, - activeCallView, - activeCallPiPWrap, + views.activeCallPiP, + views.activeCallView, + views.activeCallPiPWrap, this ) } @@ -639,7 +643,7 @@ class RoomDetailFragment @Inject constructor( if (scrollPosition == null) { scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId) } else { - timelineRecyclerView.stopScroll() + views.timelineRecyclerView.stopScroll() layoutManager.scrollToPosition(scrollPosition) } } @@ -679,7 +683,7 @@ class RoomDetailFragment @Inject constructor( } private fun setupNotificationView() { - notificationAreaView.delegate = object : NotificationAreaView.Delegate { + views.notificationAreaView.delegate = object : NotificationAreaView.Delegate { override fun onTombstoneEventClicked(tombstoneEvent: Event) { roomDetailViewModel.handle(RoomDetailAction.HandleTombstoneEvent(tombstoneEvent)) } @@ -906,10 +910,10 @@ class RoomDetailFragment @Inject constructor( private fun renderRegularMode(text: String) { autoCompleter.exitSpecialMode() - composerLayout.collapse() + views.composerLayout.collapse() updateComposerText(text) - composerLayout.sendButton.contentDescription = getString(R.string.send) + views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) } private fun renderSpecialMode(event: TimelineEvent, @@ -918,7 +922,7 @@ class RoomDetailFragment @Inject constructor( defaultContent: String) { autoCompleter.enterSpecialMode() // switch to expanded bar - composerLayout.composerRelatedMessageTitle.apply { + views.composerLayout.views.composerRelatedMessageTitle.apply { text = event.senderInfo.disambiguatedDisplayName setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) } @@ -931,16 +935,16 @@ class RoomDetailFragment @Inject constructor( val document = parser.parse(messageContent.formattedBody ?: messageContent.body) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) } - composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) + views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) updateComposerText(defaultContent) - composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) - composerLayout.sendButton.contentDescription = getString(descriptionRes) + views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) + views.composerLayout.views.sendButton.contentDescription = getString(descriptionRes) - avatarRenderer.render(event.senderInfo.toMatrixItem(), composerLayout.composerRelatedMessageAvatar) + avatarRenderer.render(event.senderInfo.toMatrixItem(), views.composerLayout.views.composerRelatedMessageAvatar) - composerLayout.expand { + views.composerLayout.expand { if (isAdded) { // need to do it here also when not using quick reply focusComposerAndShowKeyboard() @@ -951,11 +955,10 @@ class RoomDetailFragment @Inject constructor( private fun updateComposerText(text: String) { // Do not update if this is the same text to avoid the cursor to move - if (text != composerLayout.composerEditText.text.toString()) { + if (text != views.composerLayout.text.toString()) { // Ignore update to avoid saving a draft - composerLayout.composerEditText.setText(text) - composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length - ?: 0) + views.composerLayout.views.composerEditText.setText(text) + views.composerLayout.views.composerEditText.setSelection(views.composerLayout.text?.length ?: 0) } } @@ -982,7 +985,7 @@ class RoomDetailFragment @Inject constructor( notificationDrawerManager.setCurrentRoom(null) - roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.SaveDraft(views.composerLayout.text.toString())) } private val attachmentFileActivityResultLauncher = registerStartForActivityResult { @@ -1050,14 +1053,14 @@ class RoomDetailFragment @Inject constructor( timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline - timelineRecyclerView.trackItemsVisibilityChange() + views.timelineRecyclerView.trackItemsVisibilityChange() layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) - scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(timelineRecyclerView, layoutManager, timelineEventController) - timelineRecyclerView.layoutManager = layoutManager - timelineRecyclerView.itemAnimator = null - timelineRecyclerView.setHasFixedSize(true) + scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController) + views.timelineRecyclerView.layoutManager = layoutManager + views.timelineRecyclerView.itemAnimator = null + views.timelineRecyclerView.setHasFixedSize(true) modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) it.dispatchTo(scrollOnNewMessageCallback) @@ -1066,14 +1069,14 @@ class RoomDetailFragment @Inject constructor( jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() } timelineEventController.addModelBuildListener(modelBuildListener) - timelineRecyclerView.adapter = timelineEventController.adapter + views.timelineRecyclerView.adapter = timelineEventController.adapter if (vectorPreferences.swipeToReplyIsEnabled()) { val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler { override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { (model as? AbsMessageItem)?.attributes?.informationData?.let { val eventId = it.eventId - roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(eventId, composerLayout.composerEditText.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(eventId, views.composerLayout.text.toString())) } } @@ -1096,9 +1099,9 @@ class RoomDetailFragment @Inject constructor( } val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler) val touchHelper = ItemTouchHelper(swipeCallback) - touchHelper.attachToRecyclerView(timelineRecyclerView) + touchHelper.attachToRecyclerView(views.timelineRecyclerView) } - timelineRecyclerView.addGlidePreloader( + views.timelineRecyclerView.addGlidePreloader( epoxyController = timelineEventController, requestManager = GlideApp.with(this), preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ -> @@ -1111,7 +1114,7 @@ class RoomDetailFragment @Inject constructor( } private fun updateJumpToReadMarkerViewVisibility() { - jumpToReadMarkerView?.post { + views.jumpToReadMarkerView?.post { withState(roomDetailViewModel) { val showJumpToUnreadBanner = when (it.unreadState) { UnreadState.Unknown, @@ -1131,13 +1134,13 @@ class RoomDetailFragment @Inject constructor( } } } - jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner + views.jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner } } } private fun setupComposer() { - val composerEditText = composerLayout.composerEditText + val composerEditText = views.composerLayout.views.composerEditText autoCompleter.setup(composerEditText) observerUserTyping() @@ -1164,12 +1167,12 @@ class RoomDetailFragment @Inject constructor( } else false } - composerLayout.callback = object : TextComposerView.Callback { + views.composerLayout.callback = object : TextComposerView.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) } - attachmentTypeSelector.show(composerLayout.attachmentButton, keyboardStateUtils.isKeyboardShowing) + attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing) } override fun onSendMessage(text: CharSequence) { @@ -1177,7 +1180,7 @@ class RoomDetailFragment @Inject constructor( } override fun onCloseRelatedMessage() { - roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false)) + roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(views.composerLayout.text.toString(), false)) } override fun onRichContentSelected(contentUri: Uri): Boolean { @@ -1193,14 +1196,14 @@ class RoomDetailFragment @Inject constructor( } if (text.isNotBlank()) { // We collapse ASAP, if not there will be a slight anoying delay - composerLayout.collapse(true) + views.composerLayout.collapse(true) lockSendButton = true roomDetailViewModel.handle(RoomDetailAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) } } private fun observerUserTyping() { - composerLayout.composerEditText.textChanges() + views.composerLayout.views.composerEditText.textChanges() .skipInitialValue() .debounce(300, TimeUnit.MILLISECONDS) .map { it.isNotEmpty() } @@ -1221,39 +1224,39 @@ class RoomDetailFragment @Inject constructor( } private fun setupInviteView() { - inviteView.callback = this + views.inviteView.callback = this } override fun invalidate() = withState(roomDetailViewModel) { state -> invalidateOptionsMenu() val summary = state.asyncRoomSummary() renderToolbar(summary, state.typingMessage) - activeConferenceView.render(state) + views.activeConferenceView.render(state) val inviter = state.asyncInviter() if (summary?.membership == Membership.JOIN) { - jumpToBottomView.count = summary.notificationCount - jumpToBottomView.drawBadge = summary.hasUnreadMessages + views.jumpToBottomView.count = summary.notificationCount + views.jumpToBottomView.drawBadge = summary.hasUnreadMessages scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) - inviteView.visibility = View.GONE + views.inviteView.visibility = View.GONE if (state.tombstoneEvent == null) { if (state.canSendMessage) { - composerLayout.visibility = View.VISIBLE - composerLayout.setRoomEncrypted(summary.isEncrypted, summary.roomEncryptionTrustLevel) - notificationAreaView.render(NotificationAreaView.State.Hidden) + views.composerLayout.visibility = View.VISIBLE + views.composerLayout.setRoomEncrypted(summary.isEncrypted, summary.roomEncryptionTrustLevel) + views.notificationAreaView.render(NotificationAreaView.State.Hidden) } else { - composerLayout.visibility = View.GONE - notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost) + views.composerLayout.visibility = View.GONE + views.notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost) } } else { - composerLayout.visibility = View.GONE - notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent)) + views.composerLayout.visibility = View.GONE + views.notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent)) } } else if (summary?.membership == Membership.INVITE && inviter != null) { - inviteView.visibility = View.VISIBLE - inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState) + views.inviteView.visibility = View.VISIBLE + views.inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState) // Intercept click event - inviteView.setOnClickListener { } + views.inviteView.setOnClickListener { } } else if (state.asyncInviter.complete) { vectorBaseActivity.finish() } @@ -1261,14 +1264,14 @@ class RoomDetailFragment @Inject constructor( private fun renderToolbar(roomSummary: RoomSummary?, typingMessage: String?) { if (roomSummary == null) { - roomToolbarContentView.isClickable = false + views.roomToolbarContentView.isClickable = false } else { - roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN - roomToolbarTitleView.text = roomSummary.displayName - avatarRenderer.render(roomSummary.toMatrixItem(), roomToolbarAvatarImageView) + views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN + views.roomToolbarTitleView.text = roomSummary.displayName + avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView) renderSubTitle(typingMessage, roomSummary.topic) - roomToolbarDecorationImageView.let { + views.roomToolbarDecorationImageView.let { it.setImageResource(roomSummary.roomEncryptionTrustLevel.toImageRes()) it.isVisible = roomSummary.roomEncryptionTrustLevel != null } @@ -1278,7 +1281,7 @@ class RoomDetailFragment @Inject constructor( private fun renderSubTitle(typingMessage: String?, topic: String) { // TODO Temporary place to put typing data val subtitle = typingMessage?.takeIf { it.isNotBlank() } ?: topic - roomToolbarSubtitleView.apply { + views.roomToolbarSubtitleView.apply { setTextOrHide(subtitle) if (typingMessage.isNullOrBlank()) { setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_toolbar_secondary_text_color)) @@ -1294,9 +1297,11 @@ class RoomDetailFragment @Inject constructor( when (async) { is Loading -> { // TODO Better handling progress + /* TODO BMA Yes, improve that vectorBaseActivity.showWaitingView() vectorBaseActivity.waitingStatusText.visibility = View.VISIBLE vectorBaseActivity.waitingStatusText.text = getString(R.string.joining_room) + */ } is Success -> { navigator.openRoom(vectorBaseActivity, async()) @@ -1544,8 +1549,8 @@ class RoomDetailFragment @Inject constructor( mediaData = mediaData, view = view ) { pairs -> - pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) - pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) + pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: "")) + pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: "")) } } @@ -1556,8 +1561,8 @@ class RoomDetailFragment @Inject constructor( mediaData = mediaData, view = view ) { pairs -> - pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) - pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) + pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: "")) + pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: "")) } } @@ -1785,13 +1790,13 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } is EventSharedAction.Edit -> { - roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) } is EventSharedAction.Quote -> { - roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) } is EventSharedAction.Reply -> { - roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) } is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) @@ -1857,13 +1862,13 @@ class RoomDetailFragment @Inject constructor( */ @SuppressLint("SetTextI18n") private fun insertUserDisplayNameInTextEditor(userId: String) { - val startToCompose = composerLayout.composerEditText.text.isNullOrBlank() + val startToCompose = views.composerLayout.text.isNullOrBlank() if (startToCompose && userId == session.myUserId) { // Empty composer, current user: start an emote - composerLayout.composerEditText.setText(Command.EMOTE.command + " ") - composerLayout.composerEditText.setSelection(Command.EMOTE.length) + views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ") + views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length) } else { val roomMember = roomDetailViewModel.getMember(userId) // TODO move logic outside of fragment @@ -1879,7 +1884,7 @@ class RoomDetailFragment @Inject constructor( requireContext(), MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) ) - .also { it.bind(composerLayout.composerEditText) }, + .also { it.bind(views.composerLayout.views.composerEditText) }, 0, displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE @@ -1889,11 +1894,11 @@ class RoomDetailFragment @Inject constructor( if (startToCompose) { if (displayName.startsWith("/")) { // Ensure displayName will not be interpreted as a Slash command - composerLayout.composerEditText.append("\\") + views.composerLayout.views.composerEditText.append("\\") } - composerLayout.composerEditText.append(pill) + views.composerLayout.views.composerEditText.append(pill) } else { - composerLayout.composerEditText.text?.insert(composerLayout.composerEditText.selectionStart, pill) + views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) } } } @@ -1902,8 +1907,8 @@ class RoomDetailFragment @Inject constructor( } private fun focusComposerAndShowKeyboard() { - if (composerLayout.isVisible) { - composerLayout.composerEditText.showKeyboard(andRequestFocus = true) + if (views.composerLayout.isVisible) { + views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) } } @@ -1933,7 +1938,7 @@ class RoomDetailFragment @Inject constructor( // JumpToReadMarkerView.Callback override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) { - jumpToReadMarkerView.isVisible = false + views.jumpToReadMarkerView.isVisible = false if (it.unreadState is UnreadState.HasUnread) { roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt index 427f30fc01..ed5e124250 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt @@ -20,7 +20,12 @@ import android.content.Context import android.net.Uri import android.text.Editable import android.util.AttributeSet +import android.view.View import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.ImageView +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.text.toSpannable @@ -31,6 +36,7 @@ import androidx.transition.Transition import androidx.transition.TransitionManager import androidx.transition.TransitionSet import im.vector.app.R +import im.vector.app.databinding.ComposerLayoutBinding import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel @@ -38,7 +44,9 @@ import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel * Encapsulate the timeline composer UX. * */ -class TextComposerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, +class TextComposerView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { interface Callback : ComposerEditText.Callback { @@ -47,6 +55,8 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib fun onAddAttachment() } + val views: ComposerLayoutBinding + var callback: Callback? = null private var currentConstraintSetId: Int = -1 @@ -54,27 +64,30 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib private val animationDuration = 100L val text: Editable? - get() = composerEditText.text + get() = views.composerEditText.text init { inflate(context, R.layout.composer_layout, this) + views = ComposerLayoutBinding.bind(this) + collapse(false) - composerEditText.callback = object : ComposerEditText.Callback { + + views.composerEditText.callback = object : ComposerEditText.Callback { override fun onRichContentSelected(contentUri: Uri): Boolean { return callback?.onRichContentSelected(contentUri) ?: false } } - composerRelatedMessageCloseButton.setOnClickListener { + views.composerRelatedMessageCloseButton.setOnClickListener { collapse() callback?.onCloseRelatedMessage() } - sendButton.setOnClickListener { + views.sendButton.setOnClickListener { val textMessage = text?.toSpannable() ?: "" callback?.onSendMessage(textMessage) } - attachmentButton.setOnClickListener { + views.attachmentButton.setOnClickListener { callback?.onAddAttachment() } } @@ -104,7 +117,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib ConstraintSet().also { it.clone(context, currentConstraintSetId) // in case shield is hidden, we will have glitch without this - it.getConstraint(R.id.composerShieldImageView).propertySet.visibility = composerShieldImageView.visibility + it.getConstraint(R.id.composerShieldImageView).propertySet.visibility = views.composerShieldImageView.visibility it.applyTo(this) } } @@ -134,17 +147,17 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) { if (isEncrypted) { - composerEditText.setHint(R.string.room_message_placeholder) - composerShieldImageView.isVisible = true + views.composerEditText.setHint(R.string.room_message_placeholder) + views.composerShieldImageView.isVisible = true val shieldRes = when (roomEncryptionTrustLevel) { RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning else -> R.drawable.ic_shield_black } - composerShieldImageView.setImageResource(shieldRes) + views.composerShieldImageView.setImageResource(shieldRes) } else { - composerEditText.setHint(R.string.room_message_placeholder) - composerShieldImageView.isVisible = false + views.composerEditText.setHint(R.string.room_message_placeholder) + views.composerShieldImageView.isVisible = false } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 29a8fb31cd..ead2b8c165 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -23,6 +23,8 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.Fail @@ -43,6 +45,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startSharePlainTextIntent +import im.vector.app.databinding.DialogShareQrCodeBinding import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.features.crypto.verification.VerificationBottomSheet @@ -72,6 +75,14 @@ class RoomMemberProfileFragment @Inject constructor( ) : VectorBaseFragment<FragmentMatrixProfileBinding>(), RoomMemberProfileController.Callback { + private lateinit var memberProfileStateView: StateView + private lateinit var memberProfileInfoContainer: View + private lateinit var memberProfileDecorationImageView: ImageView + private lateinit var memberProfileAvatarView: ImageView + private lateinit var memberProfileNameView: TextView + private lateinit var memberProfileIdView: TextView + private lateinit var memberProfilePowerLevelView: TextView + private val fragmentArgs: RoomMemberProfileArgs by args() private val viewModel: RoomMemberProfileViewModel by fragmentViewModel() @@ -85,27 +96,28 @@ class RoomMemberProfileFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupToolbar(matrixProfileToolbar) - val headerView = matrixProfileHeaderView.let { + setupToolbar(views.matrixProfileToolbar) + val headerView = views.matrixProfileHeaderView.let { it.layoutResource = R.layout.view_stub_room_member_profile_header it.inflate() } + findHeaderSubViews(headerView) memberProfileStateView.eventCallback = object : StateView.EventCallback { override fun onRetryClicked() { viewModel.handle(RoomMemberProfileAction.RetryFetchingInfo) } } memberProfileStateView.contentView = memberProfileInfoContainer - matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true, disableItemAnimation = true) + views.matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true, disableItemAnimation = true) roomMemberProfileController.callback = this appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf( - matrixProfileToolbarAvatarImageView, - matrixProfileToolbarTitleView, - matrixProfileDecorationToolbarAvatarImageView + views.matrixProfileToolbarAvatarImageView, + views.matrixProfileToolbarTitleView, + views.matrixProfileDecorationToolbarAvatarImageView ) ) - matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) + views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) viewModel.observeViewEvents { when (it) { is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) @@ -124,6 +136,16 @@ class RoomMemberProfileFragment @Inject constructor( setupLongClicks() } + private fun findHeaderSubViews(headerView: View) { + memberProfileStateView = headerView.findViewById(R.id.memberProfileStateView) + memberProfileInfoContainer = headerView.findViewById(R.id.memberProfileInfoContainer) + memberProfileNameView = headerView.findViewById(R.id.roomProfileNameView) + memberProfileIdView = headerView.findViewById(R.id.memberProfileIdView) + memberProfileAvatarView = headerView.findViewById(R.id.roomProfileAvatarView) + memberProfilePowerLevelView = headerView.findViewById(R.id.memberProfilePowerLevelView) + memberProfileDecorationImageView = headerView.findViewById(R.id.roomProfileDecorationImageView) + } + private fun setupLongClicks() { memberProfileNameView.copyOnLongClick() memberProfileIdView.copyOnLongClick() @@ -171,23 +193,23 @@ class RoomMemberProfileFragment @Inject constructor( } override fun onDestroyView() { - matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) + views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) roomMemberProfileController.callback = null appBarStateChangeListener = null - matrixProfileRecyclerView.cleanup() + views.matrixProfileRecyclerView.cleanup() super.onDestroyView() } override fun invalidate() = withState(viewModel) { state -> when (val asyncUserMatrixItem = state.userMatrixItem) { is Incomplete -> { - matrixProfileToolbarTitleView.text = state.userId - avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) + views.matrixProfileToolbarTitleView.text = state.userId + avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView) memberProfileStateView.state = StateView.State.Loading } is Fail -> { - avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) - matrixProfileToolbarTitleView.text = state.userId + avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView) + views.matrixProfileToolbarTitleView.text = state.userId val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error) memberProfileStateView.state = StateView.State.Error(failureMessage) } @@ -197,9 +219,9 @@ class RoomMemberProfileFragment @Inject constructor( memberProfileIdView.text = userMatrixItem.id val bestName = userMatrixItem.getBestName() memberProfileNameView.text = bestName - matrixProfileToolbarTitleView.text = bestName + views.matrixProfileToolbarTitleView.text = bestName avatarRenderer.render(userMatrixItem, memberProfileAvatarView) - avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView) + avatarRenderer.render(userMatrixItem, views.matrixProfileToolbarAvatarImageView) if (state.isRoomEncrypted) { memberProfileDecorationImageView.isVisible = true @@ -217,15 +239,15 @@ class RoomMemberProfileFragment @Inject constructor( } memberProfileDecorationImageView.setImageResource(icon) - matrixProfileDecorationToolbarAvatarImageView.setImageResource(icon) + views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(icon) } else { // Legacy if (state.allDevicesAreTrusted) { memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_trusted) - matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) + views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) } else { memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_warning) - matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_warning) + views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_warning) } } } else { @@ -235,7 +257,7 @@ class RoomMemberProfileFragment @Inject constructor( memberProfileAvatarView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } - matrixProfileToolbarAvatarImageView.setOnClickListener { view -> + views.matrixProfileToolbarAvatarImageView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } } @@ -302,8 +324,8 @@ class RoomMemberProfileFragment @Inject constructor( private fun handleShareRoomMemberProfile(permalink: String) { val view = layoutInflater.inflate(R.layout.dialog_share_qr_code, null) - val qrCode = view.findViewById<im.vector.app.core.ui.views.QrCodeImageView>(R.id.itemShareQrCodeImage) - qrCode.setData(permalink) + val views = DialogShareQrCodeBinding.bind(view) + views.itemShareQrCodeImage.setData(permalink) AlertDialog.Builder(requireContext()) .setView(view) .setNeutralButton(R.string.ok, null) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index b0dbdacd49..4316a4bd0d 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -23,6 +23,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.databinding.DialogEditPowerLevelBinding import org.matrix.android.sdk.api.session.room.powerlevels.Role @@ -30,28 +31,29 @@ object EditPowerLevelDialogs { fun showChoice(activity: Activity, currentRole: Role, listener: (Int) -> Unit) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null) - dialogLayout.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> - dialogLayout.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio + val views = DialogEditPowerLevelBinding.bind(dialogLayout) + views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> + views.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio } - dialogLayout.powerLevelCustomEdit.setText(currentRole.value.toString()) + views.powerLevelCustomEdit.setText(currentRole.value.toString()) when (currentRole) { - Role.Admin -> dialogLayout.powerLevelAdminRadio.isChecked = true - Role.Moderator -> dialogLayout.powerLevelModeratorRadio.isChecked = true - Role.Default -> dialogLayout.powerLevelDefaultRadio.isChecked = true - else -> dialogLayout.powerLevelCustomRadio.isChecked = true + Role.Admin -> views.powerLevelAdminRadio.isChecked = true + Role.Moderator -> views.powerLevelModeratorRadio.isChecked = true + Role.Default -> views.powerLevelDefaultRadio.isChecked = true + else -> views.powerLevelCustomRadio.isChecked = true } AlertDialog.Builder(activity) .setTitle(R.string.power_level_edit_title) .setView(dialogLayout) .setPositiveButton(R.string.edit) { _, _ -> - val newValue = when (dialogLayout.powerLevelRadioGroup.checkedRadioButtonId) { + val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) { R.id.powerLevelAdminRadio -> Role.Admin.value R.id.powerLevelModeratorRadio -> Role.Moderator.value R.id.powerLevelDefaultRadio -> Role.Default.value else -> { - dialogLayout.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value + views.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value } } listener(newValue) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 696725d001..76649d53b3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -29,6 +29,7 @@ import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewModel @@ -41,7 +42,7 @@ import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import javax.inject.Inject class RoomProfileActivity : - VectorBaseActivity(), + VectorBaseActivity<ActivitySimpleBinding>(), ToolbarConfigurable, RequireActiveMembershipViewModel.Factory { @@ -81,7 +82,9 @@ class RoomProfileActivity : injector.inject(this) } - override fun getLayoutRes() = R.layout.activity_simple + override fun getBinding(): ActivitySimpleBinding { + return ActivitySimpleBinding.inflate(layoutInflater) + } override fun initUiAndData() { sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 2199cb7211..6e41bd398e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -23,6 +23,8 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityOptionsCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -42,7 +44,6 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.startSharePlainTextIntent -import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.features.crypto.util.toImageRes import im.vector.app.features.home.AvatarRenderer @@ -70,9 +71,15 @@ class RoomProfileFragment @Inject constructor( private val roomProfileController: RoomProfileController, private val avatarRenderer: AvatarRenderer, val roomProfileViewModelFactory: RoomProfileViewModel.Factory -) : VectorBaseFragment<FragmentMatrixProfileBinding>(), +) : + VectorBaseFragment<FragmentMatrixProfileBinding>(), RoomProfileController.Callback { + private lateinit var roomProfileDecorationImageView: ImageView + private lateinit var roomProfileAvatarView: ImageView + private lateinit var roomProfileAliasView: TextView + private lateinit var roomProfileNameView: TextView + private val roomProfileArgs: RoomProfileArgs by args() private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel @@ -90,20 +97,21 @@ class RoomProfileFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) - val headerView = matrixProfileHeaderView.let { + val headerView = views.matrixProfileHeaderView.let { it.layoutResource = R.layout.view_stub_room_profile_header it.inflate() } + findHeaderSubViews(headerView) setupWaitingView() - setupToolbar(matrixProfileToolbar) + setupToolbar(views.matrixProfileToolbar) setupRecyclerView() appBarStateChangeListener = MatrixItemAppBarStateChangeListener( headerView, - listOf(matrixProfileToolbarAvatarImageView, - matrixProfileToolbarTitleView, - matrixProfileDecorationToolbarAvatarImageView) + listOf(views.matrixProfileToolbarAvatarImageView, + views. matrixProfileToolbarTitleView, + views. matrixProfileDecorationToolbarAvatarImageView) ) - matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) + views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) roomProfileViewModel.observeViewEvents { when (it) { is RoomProfileViewEvents.Loading -> showLoading(it.message) @@ -119,9 +127,16 @@ class RoomProfileFragment @Inject constructor( setupLongClicks() } + private fun findHeaderSubViews(headerView: View) { + roomProfileNameView = headerView.findViewById(R.id.roomProfileNameView) + roomProfileAliasView = headerView.findViewById(R.id.roomProfileAliasView) + roomProfileAvatarView = headerView.findViewById(R.id.roomProfileAvatarView) + roomProfileDecorationImageView = headerView.findViewById(R.id.roomProfileDecorationImageView) + } + private fun setupWaitingView() { - waitingStatusText.setText(R.string.please_wait) - waitingStatusText.isVisible = true + views.waitingView.waitingStatusText.setText(R.string.please_wait) + views.waitingView.waitingStatusText.isVisible = true } private fun setupLongClicks() { @@ -157,18 +172,18 @@ class RoomProfileFragment @Inject constructor( private fun setupRecyclerView() { roomProfileController.callback = this - matrixProfileRecyclerView.configureWith(roomProfileController, hasFixedSize = true, disableItemAnimation = true) + views.matrixProfileRecyclerView.configureWith(roomProfileController, hasFixedSize = true, disableItemAnimation = true) } override fun onDestroyView() { super.onDestroyView() - matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) - matrixProfileRecyclerView.cleanup() + views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) + views.matrixProfileRecyclerView.cleanup() appBarStateChangeListener = null } override fun invalidate() = withState(roomProfileViewModel) { state -> - waiting_view.isVisible = state.isLoading + views.waitingView.root.isVisible = state.isLoading state.roomSummary()?.also { if (it.membership.isLeft()) { @@ -176,19 +191,19 @@ class RoomProfileFragment @Inject constructor( activity?.finish() } else { roomProfileNameView.text = it.displayName - matrixProfileToolbarTitleView.text = it.displayName + views.matrixProfileToolbarTitleView.text = it.displayName roomProfileAliasView.setTextOrHide(it.canonicalAlias) val matrixItem = it.toMatrixItem() avatarRenderer.render(matrixItem, roomProfileAvatarView) - avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView) + avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView) roomProfileDecorationImageView.isVisible = it.roomEncryptionTrustLevel != null roomProfileDecorationImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes()) - matrixProfileDecorationToolbarAvatarImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes()) + views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes()) roomProfileAvatarView.setOnClickListener { view -> onAvatarClicked(view, matrixItem) } - matrixProfileToolbarAvatarImageView.setOnClickListener { view -> + views.matrixProfileToolbarAvatarImageView.setOnClickListener { view -> onAvatarClicked(view, matrixItem) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 62fb9e2b02..0230bf925e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -61,29 +61,29 @@ class RoomMemberListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomMemberListController.callback = this - setupToolbar(roomSettingsToolbar) + setupToolbar(views.roomSettingGeneric.roomSettingsToolbar) setupSearchView() setupInviteUsersButton() - views.roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) + views.roomSettingGeneric.roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) } private fun setupInviteUsersButton() { - inviteUsersButton.debouncedClicks { + views.inviteUsersButton.debouncedClicks { navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId) } // Hide FAB when list is scrolling - views.roomSettingsRecyclerView.addOnScrollListener( + views.roomSettingGeneric.roomSettingsRecyclerView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { if (withState(viewModel) { it.actionsPermissions.canInvite }) { - inviteUsersButton.show() + views.inviteUsersButton.show() } } RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_SETTLING -> { - inviteUsersButton.hide() + views.inviteUsersButton.hide() } } } @@ -92,8 +92,8 @@ class RoomMemberListFragment @Inject constructor( } private fun setupSearchView() { - searchView.queryHint = getString(R.string.search_members_hint) - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + views.roomSettingGeneric.searchView.queryHint = getString(R.string.search_members_hint) + views.roomSettingGeneric.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { return true } @@ -106,16 +106,16 @@ class RoomMemberListFragment @Inject constructor( } override fun onDestroyView() { - views.roomSettingsRecyclerView.cleanup() + views.roomSettingGeneric.roomSettingsRecyclerView.cleanup() super.onDestroyView() } override fun invalidate() = withState(viewModel) { viewState -> roomMemberListController.setData(viewState) renderRoomSummary(viewState) - inviteUsersButton.isVisible = viewState.actionsPermissions.canInvite + views.inviteUsersButton.isVisible = viewState.actionsPermissions.canInvite // Display filter only if there are more than 2 members in this room - searchViewAppBarLayout.isVisible = viewState.roomSummary()?.otherMemberIds.orEmpty().size > 1 + views.roomSettingGeneric.searchViewAppBarLayout.isVisible = viewState.roomSummary()?.otherMemberIds.orEmpty().size > 1 } override fun onRoomMemberClicked(roomMember: RoomMemberSummary) { @@ -140,8 +140,8 @@ class RoomMemberListFragment @Inject constructor( private fun renderRoomSummary(state: RoomMemberListViewState) { state.roomSummary()?.let { - roomSettingsToolbarTitleView.text = it.displayName - avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) + views.roomSettingGeneric.roomSettingsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), views.roomSettingGeneric.roomSettingsToolbarAvatarImageView) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt index 59b8569c1e..84aa80ee5a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import im.vector.app.R +import im.vector.app.databinding.DialogBackgroundSyncModeBinding class BackgroundSyncModeChooserDialog : DialogFragment() { @@ -31,25 +32,26 @@ class BackgroundSyncModeChooserDialog : DialogFragment() { val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) val view = requireActivity().layoutInflater.inflate(R.layout.dialog_background_sync_mode, null) + val views = DialogBackgroundSyncModeBinding.bind(view) val dialog = AlertDialog.Builder(requireActivity()) .setTitle(R.string.settings_background_fdroid_sync_mode) .setView(view) .setPositiveButton(R.string.cancel, null) .create() - view.findViewById<View>(R.id.backgroundSyncModeBattery).setOnClickListener { + views.backgroundSyncModeBattery.setOnClickListener { interactionListener ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY } ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY) dialog.dismiss() } - view.findViewById<View>(R.id.backgroundSyncModeReal).setOnClickListener { + views.backgroundSyncModeReal.setOnClickListener { interactionListener ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME } ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) dialog.dismiss() } - view.findViewById<View>(R.id.backgroundSyncModeOff).setOnClickListener { + views.backgroundSyncModeOff.setOnClickListener { interactionListener ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED } ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 5a7ceb4084..0409e0bf11 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -49,6 +49,7 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.toast +import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.workers.signout.SignOutUiWorker @@ -352,25 +353,18 @@ class VectorSettingsGeneralFragment @Inject constructor( private fun onPasswordUpdateClick() { activity?.let { activity -> val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup - - val showPassword: ImageView = view.findViewById(R.id.change_password_show_passwords) - val oldPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_old_pwd_til) - val oldPasswordText: TextInputEditText = view.findViewById(R.id.change_password_old_pwd_text) - val newPasswordText: TextInputEditText = view.findViewById(R.id.change_password_new_pwd_text) - val confirmNewPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_confirm_new_pwd_til) - val confirmNewPasswordText: TextInputEditText = view.findViewById(R.id.change_password_confirm_new_pwd_text) - val changePasswordLoader: View = view.findViewById(R.id.change_password_loader) + val views = DialogChangePasswordBinding.bind(view) var passwordShown = false - showPassword.setOnClickListener { + views.changePasswordShowPasswords.setOnClickListener { passwordShown = !passwordShown - oldPasswordText.showPassword(passwordShown) - newPasswordText.showPassword(passwordShown) - confirmNewPasswordText.showPassword(passwordShown) + views.changePasswordOldPwdText.showPassword(passwordShown) + views.changePasswordNewPwdText.showPassword(passwordShown) + views.changePasswordConfirmNewPwdText.showPassword(passwordShown) - showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye) + views.changePasswordShowPasswords.setImageResource(if (passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye) } val dialog = AlertDialog.Builder(activity) @@ -389,53 +383,53 @@ class VectorSettingsGeneralFragment @Inject constructor( updateButton.isEnabled = false fun updateUi() { - val oldPwd = oldPasswordText.text.toString() - val newPwd = newPasswordText.text.toString() - val newConfirmPwd = confirmNewPasswordText.text.toString() + val oldPwd = views.changePasswordOldPwdText.text.toString() + val newPwd = views.changePasswordNewPwdText.text.toString() + val newConfirmPwd = views.changePasswordConfirmNewPwdText.text.toString() updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && newPwd != newConfirmPwd) { - confirmNewPasswordTil.error = getString(R.string.passwords_do_not_match) + views.changePasswordConfirmNewPwdTil.error = getString(R.string.passwords_do_not_match) } } - oldPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + views.changePasswordOldPwdText.addTextChangedListener(object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { - oldPasswordTil.error = null + views.changePasswordOldPwdTil.error = null updateUi() } }) - newPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + views.changePasswordNewPwdText.addTextChangedListener(object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { - confirmNewPasswordTil.error = null + views.changePasswordConfirmNewPwdTil.error = null updateUi() } }) - confirmNewPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + views.changePasswordConfirmNewPwdText.addTextChangedListener(object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { - confirmNewPasswordTil.error = null + views.changePasswordConfirmNewPwdTil.error = null updateUi() } }) fun showPasswordLoadingView(toShow: Boolean) { if (toShow) { - showPassword.isEnabled = false - oldPasswordText.isEnabled = false - newPasswordText.isEnabled = false - confirmNewPasswordText.isEnabled = false - changePasswordLoader.isVisible = true + views.changePasswordShowPasswords.isEnabled = false + views.changePasswordOldPwdText.isEnabled = false + views.changePasswordNewPwdText.isEnabled = false + views.changePasswordConfirmNewPwdText.isEnabled = false + views.changePasswordLoader.isVisible = true updateButton.isEnabled = false cancelButton.isEnabled = false } else { - showPassword.isEnabled = true - oldPasswordText.isEnabled = true - newPasswordText.isEnabled = true - confirmNewPasswordText.isEnabled = true - changePasswordLoader.isVisible = false + views.changePasswordShowPasswords.isEnabled = true + views.changePasswordOldPwdText.isEnabled = true + views.changePasswordNewPwdText.isEnabled = true + views.changePasswordConfirmNewPwdText.isEnabled = true + views.changePasswordLoader.isVisible = false updateButton.isEnabled = true cancelButton.isEnabled = true } @@ -444,13 +438,13 @@ class VectorSettingsGeneralFragment @Inject constructor( updateButton.setOnClickListener { if (passwordShown) { // Hide passwords during processing - showPassword.performClick() + views.changePasswordShowPasswords.performClick() } view.hideKeyboard() - val oldPwd = oldPasswordText.text.toString() - val newPwd = newPasswordText.text.toString() + val oldPwd = views.changePasswordOldPwdText.text.toString() + val newPwd = views.changePasswordNewPwdText.text.toString() showPasswordLoadingView(true) lifecycleScope.launch { @@ -466,9 +460,9 @@ class VectorSettingsGeneralFragment @Inject constructor( activity.toast(R.string.settings_password_updated) }, { failure -> if (failure.isInvalidPassword()) { - oldPasswordTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password) + views.changePasswordOldPwdTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password) } else { - oldPasswordTil.error = getString(R.string.settings_fail_to_update_password) + views.changePasswordOldPwdTil.error = getString(R.string.settings_fail_to_update_password) } }) } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt index e6f92e1eea..028f9d6ff0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt @@ -24,6 +24,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -63,71 +64,70 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) val layoutManager = LinearLayoutManager(context) - troubleshoot_test_recycler_view.layoutManager = layoutManager + views.troubleshootTestRecyclerView.layoutManager = layoutManager - val dividerItemDecoration = DividerItemDecoration(troubleshoot_test_recycler_view.context, - layoutManager.orientation) - troubleshoot_test_recycler_view.addItemDecoration(dividerItemDecoration) + val dividerItemDecoration = DividerItemDecoration(view.context, layoutManager.orientation) + views.troubleshootTestRecyclerView.addItemDecoration(dividerItemDecoration) - troubleshoot_summ_button.debouncedClicks { + views.troubleshootSummButton.debouncedClicks { bugReporter.openBugReportScreen(requireActivity()) } - troubleshoot_run_button.debouncedClicks { + views.troubleshootRunButton.debouncedClicks { testManager?.retry(testStartForActivityResult) } startUI() } private fun startUI() { - toubleshoot_summ_description.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, 0, 0) + views.toubleshootSummDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, 0, 0) testManager = testManagerFactory.create(this) testManager?.statusListener = { troubleshootTestManager -> if (isAdded) { - TransitionManager.beginDelayedTransition(troubleshoot_bottom_view) + TransitionManager.beginDelayedTransition(views.troubleshootBottomView) when (troubleshootTestManager.diagStatus) { TroubleshootTest.TestStatus.NOT_STARTED -> { - toubleshoot_summ_description.text = "" - troubleshoot_summ_button.visibility = View.GONE - troubleshoot_run_button.visibility = View.VISIBLE + views.toubleshootSummDescription.text = "" + views.troubleshootSummButton.visibility = View.GONE + views.troubleshootRunButton.visibility = View.VISIBLE } TroubleshootTest.TestStatus.RUNNING, TroubleshootTest.TestStatus.WAITING_FOR_USER -> { val size = troubleshootTestManager.testListSize val currentTestIndex = troubleshootTestManager.currentTestIndex - toubleshoot_summ_description.text = getString( + views.toubleshootSummDescription.text = getString( R.string.settings_troubleshoot_diagnostic_running_status, currentTestIndex, size ) - troubleshoot_summ_button.visibility = View.GONE - troubleshoot_run_button.visibility = View.GONE + views.troubleshootSummButton.visibility = View.GONE + views.troubleshootRunButton.visibility = View.GONE } TroubleshootTest.TestStatus.FAILED -> { // check if there are quick fixes val hasQuickFix = testManager?.hasQuickFix().orFalse() if (hasQuickFix) { - toubleshoot_summ_description.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix) + views.toubleshootSummDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix) } else { - toubleshoot_summ_description.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_no_quickfix) + views.toubleshootSummDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_no_quickfix) } - troubleshoot_summ_button.visibility = View.VISIBLE - troubleshoot_run_button.visibility = View.VISIBLE + views.troubleshootSummButton.visibility = View.VISIBLE + views.troubleshootRunButton.visibility = View.VISIBLE } TroubleshootTest.TestStatus.SUCCESS -> { - toubleshoot_summ_description.text = getString(R.string.settings_troubleshoot_diagnostic_success_status) - troubleshoot_summ_button.visibility = View.VISIBLE - troubleshoot_run_button.visibility = View.VISIBLE + views.toubleshootSummDescription.text = getString(R.string.settings_troubleshoot_diagnostic_success_status) + views.troubleshootSummButton.visibility = View.VISIBLE + views.troubleshootRunButton.visibility = View.VISIBLE } } } } - troubleshoot_test_recycler_view.adapter = testManager?.adapter + views.troubleshootTestRecyclerView.adapter = testManager?.adapter testManager?.runDiagnostic(testStartForActivityResult) } override fun onDestroyView() { - troubleshoot_test_recycler_view.cleanup() + views.troubleshootTestRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 6dd35a7d1d..c1e441e0ee 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -50,6 +50,7 @@ import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.openFileSelection import im.vector.app.core.utils.toast +import im.vector.app.databinding.DialogImportE2eKeysBinding import im.vector.app.features.crypto.keys.KeysExporter import im.vector.app.features.crypto.keys.KeysImporter import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity @@ -447,42 +448,37 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( val filename = getFilenameFromUri(appContext, uri) val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null) - - val textView = dialogLayout.findViewById<TextView>(R.id.dialog_e2e_keys_passphrase_filename) + val views = DialogImportE2eKeysBinding.bind(dialogLayout) if (filename.isNullOrBlank()) { - textView.isVisible = false + views.dialogE2eKeysPassphraseFilename.isVisible = false } else { - textView.isVisible = true - textView.text = getString(R.string.import_e2e_keys_from_file, filename) + views.dialogE2eKeysPassphraseFilename.isVisible = true + views.dialogE2eKeysPassphraseFilename.text = getString(R.string.import_e2e_keys_from_file, filename) } val builder = AlertDialog.Builder(thisActivity) .setTitle(R.string.encryption_import_room_keys) .setView(dialogLayout) - val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text) - val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button) - - val showPassword = dialogLayout.findViewById<ImageView>(R.id.importDialogShowPassword) var passwordVisible = false - showPassword.setOnClickListener { + views.importDialogShowPassword.setOnClickListener { passwordVisible = !passwordVisible - passPhraseEditText.showPassword(passwordVisible) - showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) + views.dialogE2eKeysPassphraseEditText.showPassword(passwordVisible) + views.importDialogShowPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) } - passPhraseEditText.addTextChangedListener(object : SimpleTextWatcher() { + views.dialogE2eKeysPassphraseEditText.addTextChangedListener(object : SimpleTextWatcher() { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - importButton.isEnabled = !passPhraseEditText.text.isNullOrEmpty() + views.dialogE2eKeysImportButton.isEnabled = !views.dialogE2eKeysPassphraseEditText.text.isNullOrEmpty() } }) val importDialog = builder.show() - importButton.setOnClickListener { - val password = passPhraseEditText.text.toString() + views.dialogE2eKeysImportButton.setOnClickListener { + val password = views.dialogE2eKeysPassphraseEditText.text.toString() displayLoadingView() diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt index 8449215a09..4491a40f64 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.textChanges @@ -73,23 +74,23 @@ class DeactivateAccountFragment @Inject constructor( } private fun setupUi() { - deactivateAccountPassword.textChanges() + views.deactivateAccountPassword.textChanges() .subscribe { - deactivateAccountPasswordTil.error = null - deactivateAccountSubmit.isEnabled = it.isNotEmpty() + views.deactivateAccountPasswordTil.error = null + views.deactivateAccountSubmit.isEnabled = it.isNotEmpty() } .disposeOnDestroyView() } private fun setupViewListeners() { - deactivateAccountPasswordReveal.setOnClickListener { + views.deactivateAccountPasswordReveal.setOnClickListener { viewModel.handle(DeactivateAccountAction.TogglePassword) } - deactivateAccountSubmit.debouncedClicks { + views.deactivateAccountSubmit.debouncedClicks { viewModel.handle(DeactivateAccountAction.DeactivateAccount( - deactivateAccountPassword.text.toString(), - deactivateAccountEraseCheckbox.isChecked)) + views.deactivateAccountPassword.text.toString(), + views.deactivateAccountEraseCheckbox.isChecked)) } } @@ -102,11 +103,11 @@ class DeactivateAccountFragment @Inject constructor( } DeactivateAccountViewEvents.EmptyPassword -> { settingsActivity?.ignoreInvalidTokenError = false - deactivateAccountPasswordTil.error = getString(R.string.error_empty_field_your_password) + views.deactivateAccountPasswordTil.error = getString(R.string.error_empty_field_your_password) } DeactivateAccountViewEvents.InvalidPassword -> { settingsActivity?.ignoreInvalidTokenError = false - deactivateAccountPasswordTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password) + views.deactivateAccountPasswordTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password) } is DeactivateAccountViewEvents.OtherFailure -> { settingsActivity?.ignoreInvalidTokenError = false @@ -119,7 +120,7 @@ class DeactivateAccountFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { state -> - deactivateAccountPassword.showPassword(state.passwordShown) - deactivateAccountPasswordReveal.setImageResource(if (state.passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye) + views.deactivateAccountPassword.showPassword(state.passwordShown) + views.deactivateAccountPasswordReveal.setImageResource(if (state.passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt index 65f1a05f29..028f167f95 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt @@ -18,6 +18,10 @@ package im.vector.app.features.settings.troubleshoot import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import im.vector.app.R @@ -43,68 +47,73 @@ class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList<Troublesh override fun getItemCount(): Int = tests.size class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val troubleshootProgressBar = itemView.findViewById<ProgressBar>(R.id.troubleshootProgressBar) + private val troubleshootTestTitle = itemView.findViewById<TextView>(R.id.troubleshootTestTitle) + private val troubleshootTestDescription = itemView.findViewById<TextView>(R.id.troubleshootTestDescription) + private val troubleshootStatusIcon = itemView.findViewById<ImageView>(R.id.troubleshootStatusIcon) + private val troubleshootTestButton = itemView.findViewById<Button>(R.id.troubleshootTestButton) fun bind(test: TroubleshootTest) { val context = itemView.context - itemView.troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary)) - itemView.troubleshootTestDescription.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) + troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary)) + troubleshootTestDescription.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) when (test.status) { TroubleshootTest.TestStatus.NOT_STARTED -> { - itemView.troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) + troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) - itemView.troubleshootProgressBar.visibility = View.INVISIBLE - itemView.troubleshootStatusIcon.visibility = View.VISIBLE - itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test) + troubleshootProgressBar.visibility = View.INVISIBLE + troubleshootStatusIcon.visibility = View.VISIBLE + troubleshootStatusIcon.setImageResource(R.drawable.unit_test) } TroubleshootTest.TestStatus.WAITING_FOR_USER -> { - itemView.troubleshootProgressBar.visibility = View.INVISIBLE - itemView.troubleshootStatusIcon.visibility = View.VISIBLE + troubleshootProgressBar.visibility = View.INVISIBLE + troubleshootStatusIcon.visibility = View.VISIBLE val infoColor = ContextCompat.getColor(context, R.color.vector_info_color) val drawable = ContextCompat.getDrawable(itemView.context, R.drawable.ic_notification_privacy_warning)?.apply { ThemeUtils.tintDrawableWithColor(this, infoColor) } - itemView.troubleshootStatusIcon.setImageDrawable(drawable) - itemView.troubleshootTestDescription.setTextColor(infoColor) + troubleshootStatusIcon.setImageDrawable(drawable) + troubleshootTestDescription.setTextColor(infoColor) } TroubleshootTest.TestStatus.RUNNING -> { - itemView.troubleshootProgressBar.visibility = View.VISIBLE - itemView.troubleshootStatusIcon.visibility = View.INVISIBLE + troubleshootProgressBar.visibility = View.VISIBLE + troubleshootStatusIcon.visibility = View.INVISIBLE } TroubleshootTest.TestStatus.FAILED -> { - itemView.troubleshootProgressBar.visibility = View.INVISIBLE - itemView.troubleshootStatusIcon.visibility = View.VISIBLE - itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ko) + troubleshootProgressBar.visibility = View.INVISIBLE + troubleshootStatusIcon.visibility = View.VISIBLE + troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ko) - itemView.troubleshootStatusIcon.imageTintList = null + troubleshootStatusIcon.imageTintList = null - itemView.troubleshootTestDescription.setTextColor(ContextCompat.getColor(context, R.color.riotx_notice)) + troubleshootTestDescription.setTextColor(ContextCompat.getColor(context, R.color.riotx_notice)) } TroubleshootTest.TestStatus.SUCCESS -> { - itemView.troubleshootProgressBar.visibility = View.INVISIBLE - itemView.troubleshootStatusIcon.visibility = View.VISIBLE - itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ok) + troubleshootProgressBar.visibility = View.INVISIBLE + troubleshootStatusIcon.visibility = View.VISIBLE + troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ok) } } val quickFix = test.quickFix if (quickFix != null) { - itemView.troubleshootTestButton.setText(test.quickFix!!.title) - itemView.troubleshootTestButton.setOnClickListener { _ -> + troubleshootTestButton.setText(test.quickFix!!.title) + troubleshootTestButton.setOnClickListener { _ -> test.quickFix!!.doFix() } - itemView.troubleshootTestButton.visibility = View.VISIBLE + troubleshootTestButton.visibility = View.VISIBLE } else { - itemView.troubleshootTestButton.visibility = View.GONE + troubleshootTestButton.visibility = View.GONE } - itemView.troubleshootTestTitle.setText(test.titleResId) + troubleshootTestTitle.setText(test.titleResId) val description = test.description if (description == null) { - itemView.troubleshootTestDescription.visibility = View.GONE + troubleshootTestDescription.visibility = View.GONE } else { - itemView.troubleshootTestDescription.visibility = View.VISIBLE - itemView.troubleshootTestDescription.text = description + troubleshootTestDescription.visibility = View.VISIBLE + troubleshootTestDescription.text = description } } } diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt index 97fa4710e0..a9c0dc6505 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt @@ -19,6 +19,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success @@ -58,10 +59,10 @@ class ReviewTermsFragment @Inject constructor( } termsController.listener = this - reviewTermsRecyclerView.configureWith(termsController) + views.reviewTermsRecyclerView.configureWith(termsController) - reviewTermsAccept.onClick { reviewTermsViewModel.handle(ReviewTermsAction.Accept) } - reviewTermsDecline.onClick { activity?.finish() } + views.reviewTermsAccept.onClick { reviewTermsViewModel.handle(ReviewTermsAction.Accept) } + views.reviewTermsDecline.onClick { activity?.finish() } reviewTermsViewModel.observeViewEvents { when (it) { @@ -79,7 +80,7 @@ class ReviewTermsFragment @Inject constructor( } override fun onDestroyView() { - reviewTermsRecyclerView.cleanup() + views.reviewTermsRecyclerView.cleanup() termsController.listener = null super.onDestroyView() } @@ -94,11 +95,11 @@ class ReviewTermsFragment @Inject constructor( when (state.termsList) { is Loading -> { - reviewTermsBottomBar.isVisible = false + views.reviewTermsBottomBar.isVisible = false } is Success -> { - reviewTermsBottomBar.isVisible = true - reviewTermsAccept.isEnabled = state.termsList.invoke().all { it.accepted } + views.reviewTermsBottomBar.isVisible = true + views.reviewTermsAccept.isEnabled = state.termsList.invoke().all { it.accepted } } else -> Unit } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt index 4a060c0fcc..5d850a583d 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt @@ -57,11 +57,11 @@ class ScanUserCodeFragment @Inject constructor() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - userCodeMyCodeButton.debouncedClicks { + views.userCodeMyCodeButton.debouncedClicks { sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) } - userCodeOpenGalleryButton.debouncedClicks { + views.userCodeOpenGalleryButton.debouncedClicks { MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher) } } @@ -94,11 +94,11 @@ class ScanUserCodeFragment @Inject constructor() } private fun startCamera() { - userCodeScannerView.startCamera() - userCodeScannerView.setAutoFocus(autoFocus) - userCodeScannerView.debouncedClicks { + views.userCodeScannerView.startCamera() + views.userCodeScannerView.setAutoFocus(autoFocus) + views.userCodeScannerView.debouncedClicks { this.autoFocus = !autoFocus - userCodeScannerView.setAutoFocus(autoFocus) + views.userCodeScannerView.setAutoFocus(autoFocus) } } @@ -112,7 +112,7 @@ class ScanUserCodeFragment @Inject constructor() override fun onResume() { super.onResume() // Register ourselves as a handler for scan results. - userCodeScannerView.setResultHandler(this) + views.userCodeScannerView.setResultHandler(this) if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) { startCamera() } @@ -120,9 +120,9 @@ class ScanUserCodeFragment @Inject constructor() override fun onPause() { super.onPause() - userCodeScannerView.setResultHandler(null) + views.userCodeScannerView.setResultHandler(null) // Stop camera on pause - userCodeScannerView.stopCamera() + views.userCodeScannerView.stopCamera() } override fun handleResult(result: Result?) { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index 45e682b1ce..b4e9baac7f 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -78,9 +78,9 @@ class WidgetFragment @Inject constructor() : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) - widgetWebView.setupForWidget(this) + views.widgetWebView.setupForWidget(this) if (fragmentArgs.kind.isAdmin()) { - viewModel.getPostAPIMediator().setWebView(widgetWebView) + viewModel.getPostAPIMediator().setWebView(views.widgetWebView) } viewModel.observeViewEvents { Timber.v("Observed view events: $it") @@ -114,12 +114,12 @@ class WidgetFragment @Inject constructor() : if (fragmentArgs.kind.isAdmin()) { viewModel.getPostAPIMediator().clearWebView() } - widgetWebView.clearAfterWidget() + views.widgetWebView.clearAfterWidget() } override fun onResume() { super.onResume() - widgetWebView?.let { + views.widgetWebView?.let { it.resumeTimers() it.onResume() } @@ -127,7 +127,7 @@ class WidgetFragment @Inject constructor() : override fun onPause() { super.onPause() - widgetWebView?.let { + views.widgetWebView?.let { it.pauseTimers() it.onPause() } @@ -166,7 +166,7 @@ class WidgetFragment @Inject constructor() : return@withState true } R.id.action_refresh -> if (state.formattedURL.complete) { - widgetWebView.reload() + views.widgetWebView.reload() return@withState true } R.id.action_widget_open_ext -> if (state.formattedURL.complete) { @@ -183,8 +183,8 @@ class WidgetFragment @Inject constructor() : override fun onBackPressed(toolbarButton: Boolean): Boolean = withState(viewModel) { state -> if (state.formattedURL.complete) { - if (widgetWebView.canGoBack()) { - widgetWebView.goBack() + if (views.widgetWebView.canGoBack()) { + views.widgetWebView.goBack() return@withState true } } @@ -196,37 +196,37 @@ class WidgetFragment @Inject constructor() : when (state.formattedURL) { is Incomplete -> { setStateError(null) - widgetWebView.isInvisible = true - widgetProgressBar.isIndeterminate = true - widgetProgressBar.isVisible = true + views.widgetWebView.isInvisible = true + views.widgetProgressBar.isIndeterminate = true + views.widgetProgressBar.isVisible = true } is Success -> { setStateError(null) when (state.webviewLoadedUrl) { Uninitialized -> { - widgetWebView.isInvisible = true + views.widgetWebView.isInvisible = true } is Loading -> { setStateError(null) - widgetWebView.isInvisible = false - widgetProgressBar.isIndeterminate = true - widgetProgressBar.isVisible = true + views.widgetWebView.isInvisible = false + views.widgetProgressBar.isIndeterminate = true + views.widgetProgressBar.isVisible = true } is Success -> { - widgetWebView.isInvisible = false - widgetProgressBar.isVisible = false + views.widgetWebView.isInvisible = false + views.widgetProgressBar.isVisible = false setStateError(null) } is Fail -> { - widgetProgressBar.isInvisible = true + views.widgetProgressBar.isInvisible = true setStateError(state.webviewLoadedUrl.error.message) } } } is Fail -> { // we need to show Error - widgetWebView.isInvisible = true - widgetProgressBar.isVisible = false + views.widgetWebView.isInvisible = true + views.widgetProgressBar.isVisible = false setStateError(state.formattedURL.error.message) } } @@ -282,19 +282,19 @@ class WidgetFragment @Inject constructor() : } private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) { - widgetWebView.clearHistory() - widgetWebView.loadUrl(event.formattedURL) + views.widgetWebView.clearHistory() + views.widgetWebView.loadUrl(event.formattedURL) } private fun setStateError(message: String?) { if (message == null) { - widgetErrorLayout.isVisible = false - widgetErrorText.text = null + views.widgetErrorLayout.isVisible = false + views.widgetErrorText.text = null } else { - widgetProgressBar.isVisible = false - widgetErrorLayout.isVisible = true - widgetWebView.isInvisible = true - widgetErrorText.text = getString(R.string.room_widget_failed_to_load, message) + views.widgetProgressBar.isVisible = false + views.widgetErrorLayout.isVisible = true + views.widgetWebView.isInvisible = true + views.widgetErrorText.text = getString(R.string.room_widget_failed_to_load, message) } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt index 0c646d5d5e..9feeef3f2a 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt @@ -20,7 +20,9 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.util.AttributeSet +import android.widget.ImageView import android.widget.LinearLayout +import android.widget.TextView import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide @@ -31,6 +33,9 @@ class SignOutBottomSheetActionButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { + private val actionIconImageView: ImageView + private val actionTitleText: TextView + var action: (() -> Unit)? = null var title: String? = null @@ -72,9 +77,12 @@ class SignOutBottomSheetActionButton @JvmOverloads constructor( tint = typedArray.getColor(R.styleable.SignOutBottomSheetActionButton_iconTint, ThemeUtils.getColor(context, android.R.attr.textColor)) textColor = typedArray.getColor(R.styleable.SignOutBottomSheetActionButton_textColor, ThemeUtils.getColor(context, android.R.attr.textColor)) + actionIconImageView = findViewById(R.id.actionIconImageView) + actionTitleText = findViewById(R.id.actionTitleText) + typedArray.recycle() - signedOutActionClickable.setOnClickListener { + setOnClickListener { action?.invoke() } } diff --git a/vector/src/main/res/layout/fragment_room_member_list.xml b/vector/src/main/res/layout/fragment_room_member_list.xml index e144ddb6e3..3e70c44dda 100644 --- a/vector/src/main/res/layout/fragment_room_member_list.xml +++ b/vector/src/main/res/layout/fragment_room_member_list.xml @@ -4,7 +4,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <include layout="@layout/fragment_room_setting_generic" /> + <include + android:id="@+id/room_setting_generic" + layout="@layout/fragment_room_setting_generic" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/inviteUsersButton"