Migrate to ViewBindings (#1072) - WIP

This commit is contained in:
Benoit Marty 2020-12-16 02:05:38 +01:00
parent 706736273c
commit dba65dcd22
27 changed files with 478 additions and 405 deletions

View File

@ -172,3 +172,6 @@ getSystemService\(Context
### Use DefaultSharedPreferences.getInstance() instead for better performance ### Use DefaultSharedPreferences.getInstance() instead for better performance
PreferenceManager\.getDefaultSharedPreferences==2 PreferenceManager\.getDefaultSharedPreferences==2
### Use ViewBindings
findViewById

View File

@ -24,26 +24,27 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityTestMaterialThemeBinding
// Rendering is not the same with VectorBaseActivity // Rendering is not the same with VectorBaseActivity
abstract class DebugMaterialThemeActivity : AppCompatActivity() { abstract class DebugMaterialThemeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_material_theme) val views = ActivityTestMaterialThemeBinding.inflate(layoutInflater)
setContentView(views.root)
debugShowSnackbar.setOnClickListener { views.debugShowSnackbar.setOnClickListener {
Snackbar.make(debugMaterialCoordinator, "Snackbar!", Snackbar.LENGTH_SHORT) Snackbar.make(views.debugMaterialCoordinator, "Snackbar!", Snackbar.LENGTH_SHORT)
.setAction("Action") { } .setAction("Action") { }
.show() .show()
} }
debugShowToast.setOnClickListener { views.debugShowToast.setOnClickListener {
toast("Toast") toast("Toast")
} }
debugShowDialog.setOnClickListener { views.debugShowDialog.setOnClickListener {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setMessage("Dialog content") .setMessage("Dialog content")
.setIcon(R.drawable.ic_settings_x) .setIcon(R.drawable.ic_settings_x)
@ -53,7 +54,7 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() {
.show() .show()
} }
debugShowBottomSheet.setOnClickListener { views.debugShowBottomSheet.setOnClickListener {
BottomSheetDialogFragment().show(supportFragmentManager, "TAG") BottomSheetDialogFragment().show(supportFragmentManager, "TAG")
} }
} }

View File

@ -22,15 +22,16 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.ActivityTestLinkifyBinding
import im.vector.app.databinding.ActivityTestMaterialThemeBinding
class TestLinkifyActivity : AppCompatActivity() { class TestLinkifyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_linkify) val views = ActivityTestLinkifyBinding.inflate(layoutInflater)
setContentView(views.root)
test_linkify_content_view.removeAllViews() views.testLinkifyContentView.removeAllViews()
listOf( listOf(
"https://www.html5rocks.com/en/tutorials/webrtc/basics/ |", "https://www.html5rocks.com/en/tutorials/webrtc/basics/ |",
@ -79,7 +80,7 @@ class TestLinkifyActivity : AppCompatActivity() {
) )
.forEach { textContent -> .forEach { textContent ->
val item = LayoutInflater.from(this) 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) item.findViewById<TextView>(R.id.test_linkify_auto_text)
?.apply { ?.apply {
@ -115,7 +116,7 @@ class TestLinkifyActivity : AppCompatActivity() {
// TODO Call VectorLinkify.addLinks(text) // 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))
} }
} }
} }

View File

@ -21,14 +21,18 @@ import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis
class DebugSasEmojiActivity : AppCompatActivity() { class DebugSasEmojiActivity : AppCompatActivity() {
private lateinit var views: FragmentGenericRecyclerBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_generic_recycler) views = FragmentGenericRecyclerBinding.inflate(layoutInflater)
setContentView(views.root)
val controller = SasEmojiController() val controller = SasEmojiController()
views.genericRecyclerView.configureWith(controller) views.genericRecyclerView.configureWith(controller)
controller.setData(SasState(getAllVerificationEmojis())) controller.setData(SasState(getAllVerificationEmojis()))

View File

@ -21,7 +21,7 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.DialogConfirmationWithReasonBinding
object ConfirmationDialogBuilder { object ConfirmationDialogBuilder {
@ -33,25 +33,26 @@ object ConfirmationDialogBuilder {
@StringRes reasonHintRes: Int, @StringRes reasonHintRes: Int,
confirmation: (String?) -> Unit) { confirmation: (String?) -> Unit) {
val layout = activity.layoutInflater.inflate(R.layout.dialog_confirmation_with_reason, null) 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 views.dialogReasonCheck.isVisible = askForReason
layout.dialogReasonTextInputLayout.isVisible = askForReason views.dialogReasonTextInputLayout.isVisible = askForReason
layout.dialogReasonCheck.setOnCheckedChangeListener { _, isChecked -> views.dialogReasonCheck.setOnCheckedChangeListener { _, isChecked ->
layout.dialogReasonTextInputLayout.isEnabled = isChecked views.dialogReasonTextInputLayout.isEnabled = isChecked
} }
if (askForReason && reasonHintRes != 0) { if (askForReason && reasonHintRes != 0) {
layout.dialogReasonInput.setHint(reasonHintRes) views.dialogReasonInput.setHint(reasonHintRes)
} }
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setTitle(titleRes) .setTitle(titleRes)
.setView(layout) .setView(layout)
.setPositiveButton(positiveRes) { _, _ -> .setPositiveButton(positiveRes) { _, _ ->
val reason = layout.dialogReasonInput.text.toString() val reason = views.dialogReasonInput.text.toString()
.takeIf { askForReason } .takeIf { askForReason }
?.takeIf { layout.dialogReasonCheck.isChecked } ?.takeIf { views.dialogReasonCheck.isChecked }
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
confirmation(reason) confirmation(reason)
} }

View File

@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.DialogExportE2eKeysBinding
class ExportKeysDialog { class ExportKeysDialog {
@ -33,48 +34,45 @@ class ExportKeysDialog {
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
val views = DialogExportE2eKeysBinding.bind(dialogLayout)
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
.setTitle(R.string.encryption_export_room_keys) .setTitle(R.string.encryption_export_room_keys)
.setView(dialogLayout) .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() { val textWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
when { when {
passPhrase1EditText.text.isNullOrEmpty() -> { views.exportDialogEt.text.isNullOrEmpty() -> {
exportButton.isEnabled = false views.exportDialogSubmit.isEnabled = false
passPhrase2Til.error = null views.exportDialogTilConfirm.error = null
} }
passPhrase1EditText.text.toString() == passPhrase2EditText.text.toString() -> { views.exportDialogEt.text.toString() == views.exportDialogEtConfirm.text.toString() -> {
exportButton.isEnabled = true views.exportDialogSubmit.isEnabled = true
passPhrase2Til.error = null views.exportDialogTilConfirm.error = null
} }
else -> { else -> {
exportButton.isEnabled = false views.exportDialogSubmit.isEnabled = false
passPhrase2Til.error = activity.getString(R.string.passphrase_passphrase_does_not_match) views.exportDialogTilConfirm.error = activity.getString(R.string.passphrase_passphrase_does_not_match)
} }
} }
} }
} }
passPhrase1EditText.addTextChangedListener(textWatcher) views.exportDialogEt.addTextChangedListener(textWatcher)
passPhrase2EditText.addTextChangedListener(textWatcher) views.exportDialogEtConfirm.addTextChangedListener(textWatcher)
val showPassword = dialogLayout.findViewById<ImageView>(R.id.exportDialogShowPassword) val showPassword = dialogLayout.findViewById<ImageView>(R.id.exportDialogShowPassword)
showPassword.setOnClickListener { showPassword.setOnClickListener {
passwordVisible = !passwordVisible passwordVisible = !passwordVisible
passPhrase1EditText.showPassword(passwordVisible) views.exportDialogEt.showPassword(passwordVisible)
passPhrase2EditText.showPassword(passwordVisible) views.exportDialogEtConfirm.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
val exportDialog = builder.show() val exportDialog = builder.show()
exportButton.setOnClickListener { views.exportDialogSubmit.setOnClickListener {
exportKeyDialogListener.onPassphrase(passPhrase1EditText.text.toString()) exportKeyDialogListener.onPassphrase(views.exportDialogEt.text.toString())
exportDialog.dismiss() exportDialog.dismiss()
} }

View File

@ -17,9 +17,9 @@
package im.vector.app.core.dialogs package im.vector.app.core.dialogs
import android.app.Activity import android.app.Activity
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.DialogDeviceVerifyBinding
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
@ -27,6 +27,7 @@ object ManuallyVerifyDialog {
fun show(activity: Activity, cryptoDeviceInfo: CryptoDeviceInfo, onVerified: (() -> Unit)) { fun show(activity: Activity, cryptoDeviceInfo: CryptoDeviceInfo, onVerified: (() -> Unit)) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_device_verify, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_device_verify, null)
val views = DialogDeviceVerifyBinding.bind(dialogLayout)
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
.setTitle(R.string.cross_signing_verify_by_text) .setTitle(R.string.cross_signing_verify_by_text)
.setView(dialogLayout) .setView(dialogLayout)
@ -35,17 +36,9 @@ object ManuallyVerifyDialog {
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_name)?.let { views.encryptedDeviceInfoDeviceName.text = cryptoDeviceInfo.displayName()
it.text = cryptoDeviceInfo.displayName() views.encryptedDeviceInfoDeviceId.text = cryptoDeviceInfo.deviceId
} views.encryptedDeviceInfoDeviceKey.text = cryptoDeviceInfo.getFingerprintHumanReadable()
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()
}
builder.show() builder.show()
} }

View File

@ -28,6 +28,7 @@ import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.showPassword import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.DialogPromptPasswordBinding
class PromptPasswordDialog { class PromptPasswordDialog {
@ -35,20 +36,18 @@ class PromptPasswordDialog {
fun show(activity: Activity, listener: (String) -> Unit) { fun show(activity: Activity, listener: (String) -> Unit) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null)
val views = DialogPromptPasswordBinding.bind(dialogLayout)
val passwordTil = dialogLayout.findViewById<TextInputLayout>(R.id.promptPasswordTil)
val passwordEditText = dialogLayout.findViewById<TextInputEditText>(R.id.promptPassword)
val textWatcher = object : SimpleTextWatcher() { val textWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { 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) val showPassword = dialogLayout.findViewById<ImageView>(R.id.promptPasswordPasswordReveal)
showPassword.setOnClickListener { showPassword.setOnClickListener {
passwordVisible = !passwordVisible passwordVisible = !passwordVisible
passwordEditText.showPassword(passwordVisible) views.promptPassword.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
@ -73,10 +72,10 @@ class PromptPasswordDialog {
setOnShowListener { setOnShowListener {
getButton(AlertDialog.BUTTON_POSITIVE) getButton(AlertDialog.BUTTON_POSITIVE)
.setOnClickListener { .setOnClickListener {
if (passwordEditText.text.toString().isEmpty()) { if (views.promptPassword.text.toString().isEmpty()) {
passwordTil.error = activity.getString(R.string.error_empty_field_your_password) views.promptPasswordTil.error = activity.getString(R.string.error_empty_field_your_password)
} else { } else {
listener.invoke(passwordEditText.text.toString()) listener.invoke(views.promptPassword.text.toString())
dismiss() dismiss()
} }
} }

View File

@ -23,6 +23,7 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.DialogRecoveryKeySavedInfoBinding
import me.gujun.android.span.image import me.gujun.android.span.image
import me.gujun.android.span.span import me.gujun.android.span.span
@ -30,10 +31,9 @@ class KeepItSafeDialog {
fun show(activity: Activity) { fun show(activity: Activity) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_recovery_key_saved_info, null) 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) views.keepItSafeText.text = span {
descriptionText.text = span {
span { span {
image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!) image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!)
+" " +" "

View File

@ -35,6 +35,7 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView 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.shareMedia
import im.vector.app.core.utils.shareText import im.vector.app.core.utils.shareText
import im.vector.app.core.utils.toast 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.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.AttachmentsHelper
import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.ContactAttachment
@ -232,7 +234,7 @@ class RoomDetailFragment @Inject constructor(
private val roomDetailPendingActionStore: RoomDetailPendingActionStore, private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
private val pillsPostProcessorFactory: PillsPostProcessor.Factory private val pillsPostProcessorFactory: PillsPostProcessor.Factory
) : ) :
VectorBaseFragment(), VectorBaseFragment<FragmentRoomDetailBinding>(),
TimelineEventController.Callback, TimelineEventController.Callback,
VectorInviteView.Callback, VectorInviteView.Callback,
JumpToReadMarkerView.Callback, JumpToReadMarkerView.Callback,
@ -278,7 +280,9 @@ class RoomDetailFragment @Inject constructor(
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback 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 override fun getMenuRes() = R.menu.menu_timeline
@ -303,7 +307,7 @@ class RoomDetailFragment @Inject constructor(
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java) sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
attachmentsHelper = AttachmentsHelper(requireContext(), this).register() attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity()) keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(roomToolbar) setupToolbar(views.roomToolbar)
setupRecyclerView() setupRecyclerView()
setupComposer() setupComposer()
setupInviteView() setupInviteView()
@ -314,7 +318,7 @@ class RoomDetailFragment @Inject constructor(
setupConfBannerView() setupConfBannerView()
setupEmojiPopup() setupEmojiPopup()
roomToolbarContentView.debouncedClicks { views.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
} }
@ -349,7 +353,7 @@ class RoomDetailFragment @Inject constructor(
} }
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState -> roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
syncStateView.render(syncState) views.syncStateView.render(syncState)
} }
roomDetailViewModel.observeViewEvents { roomDetailViewModel.observeViewEvents {
@ -396,8 +400,8 @@ class RoomDetailFragment @Inject constructor(
private fun handleChatEffect(chatEffect: ChatEffect) { private fun handleChatEffect(chatEffect: ChatEffect) {
when (chatEffect) { when (chatEffect) {
ChatEffect.CONFETTI -> { ChatEffect.CONFETTI -> {
viewKonfetti.isVisible = true views.viewKonfetti.isVisible = true
viewKonfetti.build() views.viewKonfetti.build()
.addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA) .addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA)
.setDirection(0.0, 359.0) .setDirection(0.0, 359.0)
.setSpeed(2f, 5f) .setSpeed(2f, 5f)
@ -405,20 +409,20 @@ class RoomDetailFragment @Inject constructor(
.setTimeToLive(2000L) .setTimeToLive(2000L)
.addShapes(Shape.Square, Shape.Circle) .addShapes(Shape.Square, Shape.Circle)
.addSizes(Size(12)) .addSizes(Size(12))
.setPosition(-50f, viewKonfetti.width + 50f, -50f, -50f) .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f)
.streamFor(150, 3000L) .streamFor(150, 3000L)
} }
ChatEffect.SNOW -> { ChatEffect.SNOW -> {
viewSnowFall.isVisible = true views.viewSnowFall.isVisible = true
viewSnowFall.restartFalling() views.viewSnowFall.restartFalling()
} }
} }
} }
private fun handleStopChatEffects() { private fun handleStopChatEffects() {
TransitionManager.beginDelayedTransition(rootConstraintLayout) TransitionManager.beginDelayedTransition(views.rootConstraintLayout)
viewSnowFall.isVisible = false views.viewSnowFall.isVisible = false
// when gone the effect is a bit buggy // when gone the effect is a bit buggy
viewKonfetti.isInvisible = true views.viewKonfetti.isInvisible = true
} }
override fun onImageReady(uri: Uri?) { override fun onImageReady(uri: Uri?) {
@ -486,7 +490,7 @@ class RoomDetailFragment @Inject constructor(
} }
private fun setupConfBannerView() { private fun setupConfBannerView() {
activeConferenceView.callback = object : ActiveConferenceView.Callback { views.activeConferenceView.callback = object : ActiveConferenceView.Callback {
override fun onTapJoinAudio(jitsiWidget: Widget) { override fun onTapJoinAudio(jitsiWidget: Widget) {
// need to check if allowed first // need to check if allowed first
roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed( roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed(
@ -513,13 +517,13 @@ class RoomDetailFragment @Inject constructor(
private fun setupEmojiPopup() { private fun setupEmojiPopup() {
val emojiPopup = EmojiPopup val emojiPopup = EmojiPopup
.Builder .Builder
.fromRootView(rootConstraintLayout) .fromRootView(views.rootConstraintLayout)
.setKeyboardAnimationStyle(R.style.emoji_fade_animation_style) .setKeyboardAnimationStyle(R.style.emoji_fade_animation_style)
.setOnEmojiPopupShownListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_keyboard) } .setOnEmojiPopupShownListener { views.composerLayout.views.composerEmojiButton.setImageResource(R.drawable.ic_keyboard) }
.setOnEmojiPopupDismissListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_insert_emoji) } .setOnEmojiPopupDismissListener { views.composerLayout.views.composerEmojiButton.setImageResource(R.drawable.ic_insert_emoji) }
.build(composerLayout.composerEditText) .build(views.composerLayout.views.composerEditText)
composerLayout.composerEmojiButton.debouncedClicks { views.composerLayout.views.composerEmojiButton.debouncedClicks {
emojiPopup.toggle() emojiPopup.toggle()
} }
} }
@ -585,11 +589,11 @@ class RoomDetailFragment @Inject constructor(
override fun onDestroyView() { override fun onDestroyView() {
timelineEventController.callback = null timelineEventController.callback = null
timelineEventController.removeModelBuildListener(modelBuildListener) timelineEventController.removeModelBuildListener(modelBuildListener)
activeCallView.callback = null views.activeCallView.callback = null
modelBuildListener = null modelBuildListener = null
autoCompleter.clear() autoCompleter.clear()
debouncer.cancelAll() debouncer.cancelAll()
timelineRecyclerView.cleanup() views.timelineRecyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
} }
@ -601,10 +605,10 @@ class RoomDetailFragment @Inject constructor(
} }
private fun setupJumpToBottomView() { private fun setupJumpToBottomView() {
jumpToBottomView.visibility = View.INVISIBLE views.jumpToBottomView.visibility = View.INVISIBLE
jumpToBottomView.debouncedClicks { views.jumpToBottomView.debouncedClicks {
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
jumpToBottomView.visibility = View.INVISIBLE views.jumpToBottomView.visibility = View.INVISIBLE
if (!roomDetailViewModel.timeline.isLive) { if (!roomDetailViewModel.timeline.isLive) {
scrollOnNewMessageCallback.forceScrollOnNextUpdate() scrollOnNewMessageCallback.forceScrollOnNextUpdate()
roomDetailViewModel.timeline.restartWithEventId(null) roomDetailViewModel.timeline.restartWithEventId(null)
@ -614,22 +618,22 @@ class RoomDetailFragment @Inject constructor(
} }
jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager( jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager(
jumpToBottomView, views.jumpToBottomView,
debouncer, debouncer,
timelineRecyclerView, views.timelineRecyclerView,
layoutManager layoutManager
) )
} }
private fun setupJumpToReadMarkerView() { private fun setupJumpToReadMarkerView() {
jumpToReadMarkerView.callback = this views.jumpToReadMarkerView.callback = this
} }
private fun setupActiveCallView() { private fun setupActiveCallView() {
activeCallViewHolder.bind( activeCallViewHolder.bind(
activeCallPiP, views.activeCallPiP,
activeCallView, views.activeCallView,
activeCallPiPWrap, views.activeCallPiPWrap,
this this
) )
} }
@ -639,7 +643,7 @@ class RoomDetailFragment @Inject constructor(
if (scrollPosition == null) { if (scrollPosition == null) {
scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId) scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId)
} else { } else {
timelineRecyclerView.stopScroll() views.timelineRecyclerView.stopScroll()
layoutManager.scrollToPosition(scrollPosition) layoutManager.scrollToPosition(scrollPosition)
} }
} }
@ -679,7 +683,7 @@ class RoomDetailFragment @Inject constructor(
} }
private fun setupNotificationView() { private fun setupNotificationView() {
notificationAreaView.delegate = object : NotificationAreaView.Delegate { views.notificationAreaView.delegate = object : NotificationAreaView.Delegate {
override fun onTombstoneEventClicked(tombstoneEvent: Event) { override fun onTombstoneEventClicked(tombstoneEvent: Event) {
roomDetailViewModel.handle(RoomDetailAction.HandleTombstoneEvent(tombstoneEvent)) roomDetailViewModel.handle(RoomDetailAction.HandleTombstoneEvent(tombstoneEvent))
} }
@ -906,10 +910,10 @@ class RoomDetailFragment @Inject constructor(
private fun renderRegularMode(text: String) { private fun renderRegularMode(text: String) {
autoCompleter.exitSpecialMode() autoCompleter.exitSpecialMode()
composerLayout.collapse() views.composerLayout.collapse()
updateComposerText(text) updateComposerText(text)
composerLayout.sendButton.contentDescription = getString(R.string.send) views.composerLayout.views.sendButton.contentDescription = getString(R.string.send)
} }
private fun renderSpecialMode(event: TimelineEvent, private fun renderSpecialMode(event: TimelineEvent,
@ -918,7 +922,7 @@ class RoomDetailFragment @Inject constructor(
defaultContent: String) { defaultContent: String) {
autoCompleter.enterSpecialMode() autoCompleter.enterSpecialMode()
// switch to expanded bar // switch to expanded bar
composerLayout.composerRelatedMessageTitle.apply { views.composerLayout.views.composerRelatedMessageTitle.apply {
text = event.senderInfo.disambiguatedDisplayName text = event.senderInfo.disambiguatedDisplayName
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
} }
@ -931,16 +935,16 @@ class RoomDetailFragment @Inject constructor(
val document = parser.parse(messageContent.formattedBody ?: messageContent.body) val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
} }
composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
updateComposerText(defaultContent) updateComposerText(defaultContent)
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
composerLayout.sendButton.contentDescription = getString(descriptionRes) 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) { if (isAdded) {
// need to do it here also when not using quick reply // need to do it here also when not using quick reply
focusComposerAndShowKeyboard() focusComposerAndShowKeyboard()
@ -951,11 +955,10 @@ class RoomDetailFragment @Inject constructor(
private fun updateComposerText(text: String) { private fun updateComposerText(text: String) {
// Do not update if this is the same text to avoid the cursor to move // 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 // Ignore update to avoid saving a draft
composerLayout.composerEditText.setText(text) views.composerLayout.views.composerEditText.setText(text)
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length views.composerLayout.views.composerEditText.setSelection(views.composerLayout.text?.length ?: 0)
?: 0)
} }
} }
@ -982,7 +985,7 @@ class RoomDetailFragment @Inject constructor(
notificationDrawerManager.setCurrentRoom(null) notificationDrawerManager.setCurrentRoom(null)
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString())) roomDetailViewModel.handle(RoomDetailAction.SaveDraft(views.composerLayout.text.toString()))
} }
private val attachmentFileActivityResultLauncher = registerStartForActivityResult { private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
@ -1050,14 +1053,14 @@ class RoomDetailFragment @Inject constructor(
timelineEventController.callback = this timelineEventController.callback = this
timelineEventController.timeline = roomDetailViewModel.timeline timelineEventController.timeline = roomDetailViewModel.timeline
timelineRecyclerView.trackItemsVisibilityChange() views.timelineRecyclerView.trackItemsVisibilityChange()
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(timelineRecyclerView, layoutManager, timelineEventController) scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
timelineRecyclerView.layoutManager = layoutManager views.timelineRecyclerView.layoutManager = layoutManager
timelineRecyclerView.itemAnimator = null views.timelineRecyclerView.itemAnimator = null
timelineRecyclerView.setHasFixedSize(true) views.timelineRecyclerView.setHasFixedSize(true)
modelBuildListener = OnModelBuildFinishedListener { modelBuildListener = OnModelBuildFinishedListener {
it.dispatchTo(stateRestorer) it.dispatchTo(stateRestorer)
it.dispatchTo(scrollOnNewMessageCallback) it.dispatchTo(scrollOnNewMessageCallback)
@ -1066,14 +1069,14 @@ class RoomDetailFragment @Inject constructor(
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} }
timelineEventController.addModelBuildListener(modelBuildListener) timelineEventController.addModelBuildListener(modelBuildListener)
timelineRecyclerView.adapter = timelineEventController.adapter views.timelineRecyclerView.adapter = timelineEventController.adapter
if (vectorPreferences.swipeToReplyIsEnabled()) { if (vectorPreferences.swipeToReplyIsEnabled()) {
val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler { val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
(model as? AbsMessageItem)?.attributes?.informationData?.let { (model as? AbsMessageItem)?.attributes?.informationData?.let {
val eventId = it.eventId 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 swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler)
val touchHelper = ItemTouchHelper(swipeCallback) val touchHelper = ItemTouchHelper(swipeCallback)
touchHelper.attachToRecyclerView(timelineRecyclerView) touchHelper.attachToRecyclerView(views.timelineRecyclerView)
} }
timelineRecyclerView.addGlidePreloader( views.timelineRecyclerView.addGlidePreloader(
epoxyController = timelineEventController, epoxyController = timelineEventController,
requestManager = GlideApp.with(this), requestManager = GlideApp.with(this),
preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ -> preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ ->
@ -1111,7 +1114,7 @@ class RoomDetailFragment @Inject constructor(
} }
private fun updateJumpToReadMarkerViewVisibility() { private fun updateJumpToReadMarkerViewVisibility() {
jumpToReadMarkerView?.post { views.jumpToReadMarkerView?.post {
withState(roomDetailViewModel) { withState(roomDetailViewModel) {
val showJumpToUnreadBanner = when (it.unreadState) { val showJumpToUnreadBanner = when (it.unreadState) {
UnreadState.Unknown, UnreadState.Unknown,
@ -1131,13 +1134,13 @@ class RoomDetailFragment @Inject constructor(
} }
} }
} }
jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner views.jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner
} }
} }
} }
private fun setupComposer() { private fun setupComposer() {
val composerEditText = composerLayout.composerEditText val composerEditText = views.composerLayout.views.composerEditText
autoCompleter.setup(composerEditText) autoCompleter.setup(composerEditText)
observerUserTyping() observerUserTyping()
@ -1164,12 +1167,12 @@ class RoomDetailFragment @Inject constructor(
} else false } else false
} }
composerLayout.callback = object : TextComposerView.Callback { views.composerLayout.callback = object : TextComposerView.Callback {
override fun onAddAttachment() { override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) { if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) 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) { override fun onSendMessage(text: CharSequence) {
@ -1177,7 +1180,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onCloseRelatedMessage() { 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 { override fun onRichContentSelected(contentUri: Uri): Boolean {
@ -1193,14 +1196,14 @@ class RoomDetailFragment @Inject constructor(
} }
if (text.isNotBlank()) { if (text.isNotBlank()) {
// We collapse ASAP, if not there will be a slight anoying delay // We collapse ASAP, if not there will be a slight anoying delay
composerLayout.collapse(true) views.composerLayout.collapse(true)
lockSendButton = true lockSendButton = true
roomDetailViewModel.handle(RoomDetailAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) roomDetailViewModel.handle(RoomDetailAction.SendMessage(text, vectorPreferences.isMarkdownEnabled()))
} }
} }
private fun observerUserTyping() { private fun observerUserTyping() {
composerLayout.composerEditText.textChanges() views.composerLayout.views.composerEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300, TimeUnit.MILLISECONDS)
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
@ -1221,39 +1224,39 @@ class RoomDetailFragment @Inject constructor(
} }
private fun setupInviteView() { private fun setupInviteView() {
inviteView.callback = this views.inviteView.callback = this
} }
override fun invalidate() = withState(roomDetailViewModel) { state -> override fun invalidate() = withState(roomDetailViewModel) { state ->
invalidateOptionsMenu() invalidateOptionsMenu()
val summary = state.asyncRoomSummary() val summary = state.asyncRoomSummary()
renderToolbar(summary, state.typingMessage) renderToolbar(summary, state.typingMessage)
activeConferenceView.render(state) views.activeConferenceView.render(state)
val inviter = state.asyncInviter() val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) { if (summary?.membership == Membership.JOIN) {
jumpToBottomView.count = summary.notificationCount views.jumpToBottomView.count = summary.notificationCount
jumpToBottomView.drawBadge = summary.hasUnreadMessages views.jumpToBottomView.drawBadge = summary.hasUnreadMessages
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
timelineEventController.update(state) timelineEventController.update(state)
inviteView.visibility = View.GONE views.inviteView.visibility = View.GONE
if (state.tombstoneEvent == null) { if (state.tombstoneEvent == null) {
if (state.canSendMessage) { if (state.canSendMessage) {
composerLayout.visibility = View.VISIBLE views.composerLayout.visibility = View.VISIBLE
composerLayout.setRoomEncrypted(summary.isEncrypted, summary.roomEncryptionTrustLevel) views.composerLayout.setRoomEncrypted(summary.isEncrypted, summary.roomEncryptionTrustLevel)
notificationAreaView.render(NotificationAreaView.State.Hidden) views.notificationAreaView.render(NotificationAreaView.State.Hidden)
} else { } else {
composerLayout.visibility = View.GONE views.composerLayout.visibility = View.GONE
notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost) views.notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost)
} }
} else { } else {
composerLayout.visibility = View.GONE views.composerLayout.visibility = View.GONE
notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent)) views.notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent))
} }
} else if (summary?.membership == Membership.INVITE && inviter != null) { } else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE views.inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState) views.inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState)
// Intercept click event // Intercept click event
inviteView.setOnClickListener { } views.inviteView.setOnClickListener { }
} else if (state.asyncInviter.complete) { } else if (state.asyncInviter.complete) {
vectorBaseActivity.finish() vectorBaseActivity.finish()
} }
@ -1261,14 +1264,14 @@ class RoomDetailFragment @Inject constructor(
private fun renderToolbar(roomSummary: RoomSummary?, typingMessage: String?) { private fun renderToolbar(roomSummary: RoomSummary?, typingMessage: String?) {
if (roomSummary == null) { if (roomSummary == null) {
roomToolbarContentView.isClickable = false views.roomToolbarContentView.isClickable = false
} else { } else {
roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN
roomToolbarTitleView.text = roomSummary.displayName views.roomToolbarTitleView.text = roomSummary.displayName
avatarRenderer.render(roomSummary.toMatrixItem(), roomToolbarAvatarImageView) avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView)
renderSubTitle(typingMessage, roomSummary.topic) renderSubTitle(typingMessage, roomSummary.topic)
roomToolbarDecorationImageView.let { views.roomToolbarDecorationImageView.let {
it.setImageResource(roomSummary.roomEncryptionTrustLevel.toImageRes()) it.setImageResource(roomSummary.roomEncryptionTrustLevel.toImageRes())
it.isVisible = roomSummary.roomEncryptionTrustLevel != null it.isVisible = roomSummary.roomEncryptionTrustLevel != null
} }
@ -1278,7 +1281,7 @@ class RoomDetailFragment @Inject constructor(
private fun renderSubTitle(typingMessage: String?, topic: String) { private fun renderSubTitle(typingMessage: String?, topic: String) {
// TODO Temporary place to put typing data // TODO Temporary place to put typing data
val subtitle = typingMessage?.takeIf { it.isNotBlank() } ?: topic val subtitle = typingMessage?.takeIf { it.isNotBlank() } ?: topic
roomToolbarSubtitleView.apply { views.roomToolbarSubtitleView.apply {
setTextOrHide(subtitle) setTextOrHide(subtitle)
if (typingMessage.isNullOrBlank()) { if (typingMessage.isNullOrBlank()) {
setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_toolbar_secondary_text_color)) setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_toolbar_secondary_text_color))
@ -1294,9 +1297,11 @@ class RoomDetailFragment @Inject constructor(
when (async) { when (async) {
is Loading -> { is Loading -> {
// TODO Better handling progress // TODO Better handling progress
/* TODO BMA Yes, improve that
vectorBaseActivity.showWaitingView() vectorBaseActivity.showWaitingView()
vectorBaseActivity.waitingStatusText.visibility = View.VISIBLE vectorBaseActivity.waitingStatusText.visibility = View.VISIBLE
vectorBaseActivity.waitingStatusText.text = getString(R.string.joining_room) vectorBaseActivity.waitingStatusText.text = getString(R.string.joining_room)
*/
} }
is Success -> { is Success -> {
navigator.openRoom(vectorBaseActivity, async()) navigator.openRoom(vectorBaseActivity, async())
@ -1544,8 +1549,8 @@ class RoomDetailFragment @Inject constructor(
mediaData = mediaData, mediaData = mediaData,
view = view view = view
) { pairs -> ) { pairs ->
pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: ""))
pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: ""))
} }
} }
@ -1556,8 +1561,8 @@ class RoomDetailFragment @Inject constructor(
mediaData = mediaData, mediaData = mediaData,
view = view view = view
) { pairs -> ) { pairs ->
pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: ""))
pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) 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)) roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
} }
is EventSharedAction.Edit -> { 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 -> { 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 -> { 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 -> { is EventSharedAction.CopyPermalink -> {
val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId)
@ -1857,13 +1862,13 @@ class RoomDetailFragment @Inject constructor(
*/ */
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun insertUserDisplayNameInTextEditor(userId: String) { private fun insertUserDisplayNameInTextEditor(userId: String) {
val startToCompose = composerLayout.composerEditText.text.isNullOrBlank() val startToCompose = views.composerLayout.text.isNullOrBlank()
if (startToCompose if (startToCompose
&& userId == session.myUserId) { && userId == session.myUserId) {
// Empty composer, current user: start an emote // Empty composer, current user: start an emote
composerLayout.composerEditText.setText(Command.EMOTE.command + " ") views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
composerLayout.composerEditText.setSelection(Command.EMOTE.length) views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length)
} else { } else {
val roomMember = roomDetailViewModel.getMember(userId) val roomMember = roomDetailViewModel.getMember(userId)
// TODO move logic outside of fragment // TODO move logic outside of fragment
@ -1879,7 +1884,7 @@ class RoomDetailFragment @Inject constructor(
requireContext(), requireContext(),
MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl)
) )
.also { it.bind(composerLayout.composerEditText) }, .also { it.bind(views.composerLayout.views.composerEditText) },
0, 0,
displayName.length, displayName.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
@ -1889,11 +1894,11 @@ class RoomDetailFragment @Inject constructor(
if (startToCompose) { if (startToCompose) {
if (displayName.startsWith("/")) { if (displayName.startsWith("/")) {
// Ensure displayName will not be interpreted as a Slash command // 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 { } 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() { private fun focusComposerAndShowKeyboard() {
if (composerLayout.isVisible) { if (views.composerLayout.isVisible) {
composerLayout.composerEditText.showKeyboard(andRequestFocus = true) views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true)
} }
} }
@ -1933,7 +1938,7 @@ class RoomDetailFragment @Inject constructor(
// JumpToReadMarkerView.Callback // JumpToReadMarkerView.Callback
override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) { override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
jumpToReadMarkerView.isVisible = false views.jumpToReadMarkerView.isVisible = false
if (it.unreadState is UnreadState.HasUnread) { if (it.unreadState is UnreadState.HasUnread) {
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false)) roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
} }

View File

@ -20,7 +20,12 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.text.Editable import android.text.Editable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup 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.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
@ -31,6 +36,7 @@ import androidx.transition.Transition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import androidx.transition.TransitionSet import androidx.transition.TransitionSet
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.ComposerLayoutBinding
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel 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. * 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) { defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
interface Callback : ComposerEditText.Callback { interface Callback : ComposerEditText.Callback {
@ -47,6 +55,8 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
fun onAddAttachment() fun onAddAttachment()
} }
val views: ComposerLayoutBinding
var callback: Callback? = null var callback: Callback? = null
private var currentConstraintSetId: Int = -1 private var currentConstraintSetId: Int = -1
@ -54,27 +64,30 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
private val animationDuration = 100L private val animationDuration = 100L
val text: Editable? val text: Editable?
get() = composerEditText.text get() = views.composerEditText.text
init { init {
inflate(context, R.layout.composer_layout, this) inflate(context, R.layout.composer_layout, this)
views = ComposerLayoutBinding.bind(this)
collapse(false) collapse(false)
composerEditText.callback = object : ComposerEditText.Callback {
views.composerEditText.callback = object : ComposerEditText.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean { override fun onRichContentSelected(contentUri: Uri): Boolean {
return callback?.onRichContentSelected(contentUri) ?: false return callback?.onRichContentSelected(contentUri) ?: false
} }
} }
composerRelatedMessageCloseButton.setOnClickListener { views.composerRelatedMessageCloseButton.setOnClickListener {
collapse() collapse()
callback?.onCloseRelatedMessage() callback?.onCloseRelatedMessage()
} }
sendButton.setOnClickListener { views.sendButton.setOnClickListener {
val textMessage = text?.toSpannable() ?: "" val textMessage = text?.toSpannable() ?: ""
callback?.onSendMessage(textMessage) callback?.onSendMessage(textMessage)
} }
attachmentButton.setOnClickListener { views.attachmentButton.setOnClickListener {
callback?.onAddAttachment() callback?.onAddAttachment()
} }
} }
@ -104,7 +117,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
ConstraintSet().also { ConstraintSet().also {
it.clone(context, currentConstraintSetId) it.clone(context, currentConstraintSetId)
// in case shield is hidden, we will have glitch without this // 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) it.applyTo(this)
} }
} }
@ -134,17 +147,17 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) { fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) {
if (isEncrypted) { if (isEncrypted) {
composerEditText.setHint(R.string.room_message_placeholder) views.composerEditText.setHint(R.string.room_message_placeholder)
composerShieldImageView.isVisible = true views.composerShieldImageView.isVisible = true
val shieldRes = when (roomEncryptionTrustLevel) { val shieldRes = when (roomEncryptionTrustLevel) {
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
else -> R.drawable.ic_shield_black else -> R.drawable.ic_shield_black
} }
composerShieldImageView.setImageResource(shieldRes) views.composerShieldImageView.setImageResource(shieldRes)
} else { } else {
composerEditText.setHint(R.string.room_message_placeholder) views.composerEditText.setHint(R.string.room_message_placeholder)
composerShieldImageView.isVisible = false views.composerShieldImageView.isVisible = false
} }
} }
} }

View File

@ -23,6 +23,8 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail 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.StateView
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.DialogShareQrCodeBinding
import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.databinding.FragmentGenericRecyclerBinding
import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.FragmentMatrixProfileBinding
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
@ -72,6 +75,14 @@ class RoomMemberProfileFragment @Inject constructor(
) : VectorBaseFragment<FragmentMatrixProfileBinding>(), ) : VectorBaseFragment<FragmentMatrixProfileBinding>(),
RoomMemberProfileController.Callback { 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 fragmentArgs: RoomMemberProfileArgs by args()
private val viewModel: RoomMemberProfileViewModel by fragmentViewModel() private val viewModel: RoomMemberProfileViewModel by fragmentViewModel()
@ -85,27 +96,28 @@ class RoomMemberProfileFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(matrixProfileToolbar) setupToolbar(views.matrixProfileToolbar)
val headerView = matrixProfileHeaderView.let { val headerView = views.matrixProfileHeaderView.let {
it.layoutResource = R.layout.view_stub_room_member_profile_header it.layoutResource = R.layout.view_stub_room_member_profile_header
it.inflate() it.inflate()
} }
findHeaderSubViews(headerView)
memberProfileStateView.eventCallback = object : StateView.EventCallback { memberProfileStateView.eventCallback = object : StateView.EventCallback {
override fun onRetryClicked() { override fun onRetryClicked() {
viewModel.handle(RoomMemberProfileAction.RetryFetchingInfo) viewModel.handle(RoomMemberProfileAction.RetryFetchingInfo)
} }
} }
memberProfileStateView.contentView = memberProfileInfoContainer memberProfileStateView.contentView = memberProfileInfoContainer
matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true, disableItemAnimation = true) views.matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true, disableItemAnimation = true)
roomMemberProfileController.callback = this roomMemberProfileController.callback = this
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView,
listOf( listOf(
matrixProfileToolbarAvatarImageView, views.matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView, views.matrixProfileToolbarTitleView,
matrixProfileDecorationToolbarAvatarImageView views.matrixProfileDecorationToolbarAvatarImageView
) )
) )
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
@ -124,6 +136,16 @@ class RoomMemberProfileFragment @Inject constructor(
setupLongClicks() 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() { private fun setupLongClicks() {
memberProfileNameView.copyOnLongClick() memberProfileNameView.copyOnLongClick()
memberProfileIdView.copyOnLongClick() memberProfileIdView.copyOnLongClick()
@ -171,23 +193,23 @@ class RoomMemberProfileFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
roomMemberProfileController.callback = null roomMemberProfileController.callback = null
appBarStateChangeListener = null appBarStateChangeListener = null
matrixProfileRecyclerView.cleanup() views.matrixProfileRecyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
when (val asyncUserMatrixItem = state.userMatrixItem) { when (val asyncUserMatrixItem = state.userMatrixItem) {
is Incomplete -> { is Incomplete -> {
matrixProfileToolbarTitleView.text = state.userId views.matrixProfileToolbarTitleView.text = state.userId
avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView)
memberProfileStateView.state = StateView.State.Loading memberProfileStateView.state = StateView.State.Loading
} }
is Fail -> { is Fail -> {
avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView)
matrixProfileToolbarTitleView.text = state.userId views.matrixProfileToolbarTitleView.text = state.userId
val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error) val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error)
memberProfileStateView.state = StateView.State.Error(failureMessage) memberProfileStateView.state = StateView.State.Error(failureMessage)
} }
@ -197,9 +219,9 @@ class RoomMemberProfileFragment @Inject constructor(
memberProfileIdView.text = userMatrixItem.id memberProfileIdView.text = userMatrixItem.id
val bestName = userMatrixItem.getBestName() val bestName = userMatrixItem.getBestName()
memberProfileNameView.text = bestName memberProfileNameView.text = bestName
matrixProfileToolbarTitleView.text = bestName views.matrixProfileToolbarTitleView.text = bestName
avatarRenderer.render(userMatrixItem, memberProfileAvatarView) avatarRenderer.render(userMatrixItem, memberProfileAvatarView)
avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView) avatarRenderer.render(userMatrixItem, views.matrixProfileToolbarAvatarImageView)
if (state.isRoomEncrypted) { if (state.isRoomEncrypted) {
memberProfileDecorationImageView.isVisible = true memberProfileDecorationImageView.isVisible = true
@ -217,15 +239,15 @@ class RoomMemberProfileFragment @Inject constructor(
} }
memberProfileDecorationImageView.setImageResource(icon) memberProfileDecorationImageView.setImageResource(icon)
matrixProfileDecorationToolbarAvatarImageView.setImageResource(icon) views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(icon)
} else { } else {
// Legacy // Legacy
if (state.allDevicesAreTrusted) { if (state.allDevicesAreTrusted) {
memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_trusted) memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_trusted)
matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_trusted)
} else { } else {
memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_warning) memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_warning)
matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_warning) views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_warning)
} }
} }
} else { } else {
@ -235,7 +257,7 @@ class RoomMemberProfileFragment @Inject constructor(
memberProfileAvatarView.setOnClickListener { view -> memberProfileAvatarView.setOnClickListener { view ->
onAvatarClicked(view, userMatrixItem) onAvatarClicked(view, userMatrixItem)
} }
matrixProfileToolbarAvatarImageView.setOnClickListener { view -> views.matrixProfileToolbarAvatarImageView.setOnClickListener { view ->
onAvatarClicked(view, userMatrixItem) onAvatarClicked(view, userMatrixItem)
} }
} }
@ -302,8 +324,8 @@ class RoomMemberProfileFragment @Inject constructor(
private fun handleShareRoomMemberProfile(permalink: String) { private fun handleShareRoomMemberProfile(permalink: String) {
val view = layoutInflater.inflate(R.layout.dialog_share_qr_code, null) 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) val views = DialogShareQrCodeBinding.bind(view)
qrCode.setData(permalink) views.itemShareQrCodeImage.setData(permalink)
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setView(view) .setView(view)
.setNeutralButton(R.string.ok, null) .setNeutralButton(R.string.ok, null)

View File

@ -23,6 +23,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.databinding.DialogEditPowerLevelBinding
import org.matrix.android.sdk.api.session.room.powerlevels.Role 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) { fun showChoice(activity: Activity, currentRole: Role, listener: (Int) -> Unit) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null)
dialogLayout.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> val views = DialogEditPowerLevelBinding.bind(dialogLayout)
dialogLayout.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio 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) { when (currentRole) {
Role.Admin -> dialogLayout.powerLevelAdminRadio.isChecked = true Role.Admin -> views.powerLevelAdminRadio.isChecked = true
Role.Moderator -> dialogLayout.powerLevelModeratorRadio.isChecked = true Role.Moderator -> views.powerLevelModeratorRadio.isChecked = true
Role.Default -> dialogLayout.powerLevelDefaultRadio.isChecked = true Role.Default -> views.powerLevelDefaultRadio.isChecked = true
else -> dialogLayout.powerLevelCustomRadio.isChecked = true else -> views.powerLevelCustomRadio.isChecked = true
} }
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setTitle(R.string.power_level_edit_title) .setTitle(R.string.power_level_edit_title)
.setView(dialogLayout) .setView(dialogLayout)
.setPositiveButton(R.string.edit) { _, _ -> .setPositiveButton(R.string.edit) { _, _ ->
val newValue = when (dialogLayout.powerLevelRadioGroup.checkedRadioButtonId) { val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) {
R.id.powerLevelAdminRadio -> Role.Admin.value R.id.powerLevelAdminRadio -> Role.Admin.value
R.id.powerLevelModeratorRadio -> Role.Moderator.value R.id.powerLevelModeratorRadio -> Role.Moderator.value
R.id.powerLevelDefaultRadio -> Role.Default.value R.id.powerLevelDefaultRadio -> Role.Default.value
else -> { else -> {
dialogLayout.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value views.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value
} }
} }
listener(newValue) listener(newValue)

View File

@ -29,6 +29,7 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity 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.home.room.detail.RoomDetailPendingActionStore
import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel import im.vector.app.features.room.RequireActiveMembershipViewModel
@ -41,7 +42,7 @@ import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import javax.inject.Inject import javax.inject.Inject
class RoomProfileActivity : class RoomProfileActivity :
VectorBaseActivity(), VectorBaseActivity<ActivitySimpleBinding>(),
ToolbarConfigurable, ToolbarConfigurable,
RequireActiveMembershipViewModel.Factory { RequireActiveMembershipViewModel.Factory {
@ -81,7 +82,9 @@ class RoomProfileActivity :
injector.inject(this) injector.inject(this)
} }
override fun getLayoutRes() = R.layout.activity_simple override fun getBinding(): ActivitySimpleBinding {
return ActivitySimpleBinding.inflate(layoutInflater)
}
override fun initUiAndData() { override fun initUiAndData() {
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)

View File

@ -23,6 +23,8 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.pm.ShortcutManagerCompat 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.platform.VectorBaseFragment
import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.FragmentMatrixProfileBinding
import im.vector.app.features.crypto.util.toImageRes import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
@ -70,9 +71,15 @@ class RoomProfileFragment @Inject constructor(
private val roomProfileController: RoomProfileController, private val roomProfileController: RoomProfileController,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
val roomProfileViewModelFactory: RoomProfileViewModel.Factory val roomProfileViewModelFactory: RoomProfileViewModel.Factory
) : VectorBaseFragment<FragmentMatrixProfileBinding>(), ) :
VectorBaseFragment<FragmentMatrixProfileBinding>(),
RoomProfileController.Callback { 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 val roomProfileArgs: RoomProfileArgs by args()
private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel
private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel
@ -90,20 +97,21 @@ class RoomProfileFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::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.layoutResource = R.layout.view_stub_room_profile_header
it.inflate() it.inflate()
} }
findHeaderSubViews(headerView)
setupWaitingView() setupWaitingView()
setupToolbar(matrixProfileToolbar) setupToolbar(views.matrixProfileToolbar)
setupRecyclerView() setupRecyclerView()
appBarStateChangeListener = MatrixItemAppBarStateChangeListener( appBarStateChangeListener = MatrixItemAppBarStateChangeListener(
headerView, headerView,
listOf(matrixProfileToolbarAvatarImageView, listOf(views.matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView, views. matrixProfileToolbarTitleView,
matrixProfileDecorationToolbarAvatarImageView) views. matrixProfileDecorationToolbarAvatarImageView)
) )
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
roomProfileViewModel.observeViewEvents { roomProfileViewModel.observeViewEvents {
when (it) { when (it) {
is RoomProfileViewEvents.Loading -> showLoading(it.message) is RoomProfileViewEvents.Loading -> showLoading(it.message)
@ -119,9 +127,16 @@ class RoomProfileFragment @Inject constructor(
setupLongClicks() 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() { private fun setupWaitingView() {
waitingStatusText.setText(R.string.please_wait) views.waitingView.waitingStatusText.setText(R.string.please_wait)
waitingStatusText.isVisible = true views.waitingView.waitingStatusText.isVisible = true
} }
private fun setupLongClicks() { private fun setupLongClicks() {
@ -157,18 +172,18 @@ class RoomProfileFragment @Inject constructor(
private fun setupRecyclerView() { private fun setupRecyclerView() {
roomProfileController.callback = this roomProfileController.callback = this
matrixProfileRecyclerView.configureWith(roomProfileController, hasFixedSize = true, disableItemAnimation = true) views.matrixProfileRecyclerView.configureWith(roomProfileController, hasFixedSize = true, disableItemAnimation = true)
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
matrixProfileRecyclerView.cleanup() views.matrixProfileRecyclerView.cleanup()
appBarStateChangeListener = null appBarStateChangeListener = null
} }
override fun invalidate() = withState(roomProfileViewModel) { state -> override fun invalidate() = withState(roomProfileViewModel) { state ->
waiting_view.isVisible = state.isLoading views.waitingView.root.isVisible = state.isLoading
state.roomSummary()?.also { state.roomSummary()?.also {
if (it.membership.isLeft()) { if (it.membership.isLeft()) {
@ -176,19 +191,19 @@ class RoomProfileFragment @Inject constructor(
activity?.finish() activity?.finish()
} else { } else {
roomProfileNameView.text = it.displayName roomProfileNameView.text = it.displayName
matrixProfileToolbarTitleView.text = it.displayName views.matrixProfileToolbarTitleView.text = it.displayName
roomProfileAliasView.setTextOrHide(it.canonicalAlias) roomProfileAliasView.setTextOrHide(it.canonicalAlias)
val matrixItem = it.toMatrixItem() val matrixItem = it.toMatrixItem()
avatarRenderer.render(matrixItem, roomProfileAvatarView) avatarRenderer.render(matrixItem, roomProfileAvatarView)
avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView) avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView)
roomProfileDecorationImageView.isVisible = it.roomEncryptionTrustLevel != null roomProfileDecorationImageView.isVisible = it.roomEncryptionTrustLevel != null
roomProfileDecorationImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes()) roomProfileDecorationImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes())
matrixProfileDecorationToolbarAvatarImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes()) views.matrixProfileDecorationToolbarAvatarImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes())
roomProfileAvatarView.setOnClickListener { view -> roomProfileAvatarView.setOnClickListener { view ->
onAvatarClicked(view, matrixItem) onAvatarClicked(view, matrixItem)
} }
matrixProfileToolbarAvatarImageView.setOnClickListener { view -> views.matrixProfileToolbarAvatarImageView.setOnClickListener { view ->
onAvatarClicked(view, matrixItem) onAvatarClicked(view, matrixItem)
} }
} }

View File

@ -61,29 +61,29 @@ class RoomMemberListFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
roomMemberListController.callback = this roomMemberListController.callback = this
setupToolbar(roomSettingsToolbar) setupToolbar(views.roomSettingGeneric.roomSettingsToolbar)
setupSearchView() setupSearchView()
setupInviteUsersButton() setupInviteUsersButton()
views.roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) views.roomSettingGeneric.roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true)
} }
private fun setupInviteUsersButton() { private fun setupInviteUsersButton() {
inviteUsersButton.debouncedClicks { views.inviteUsersButton.debouncedClicks {
navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId) navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId)
} }
// Hide FAB when list is scrolling // Hide FAB when list is scrolling
views.roomSettingsRecyclerView.addOnScrollListener( views.roomSettingGeneric.roomSettingsRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() { object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
when (newState) { when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
if (withState(viewModel) { it.actionsPermissions.canInvite }) { if (withState(viewModel) { it.actionsPermissions.canInvite }) {
inviteUsersButton.show() views.inviteUsersButton.show()
} }
} }
RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> { RecyclerView.SCROLL_STATE_SETTLING -> {
inviteUsersButton.hide() views.inviteUsersButton.hide()
} }
} }
} }
@ -92,8 +92,8 @@ class RoomMemberListFragment @Inject constructor(
} }
private fun setupSearchView() { private fun setupSearchView() {
searchView.queryHint = getString(R.string.search_members_hint) views.roomSettingGeneric.searchView.queryHint = getString(R.string.search_members_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { views.roomSettingGeneric.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
return true return true
} }
@ -106,16 +106,16 @@ class RoomMemberListFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
views.roomSettingsRecyclerView.cleanup() views.roomSettingGeneric.roomSettingsRecyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
} }
override fun invalidate() = withState(viewModel) { viewState -> override fun invalidate() = withState(viewModel) { viewState ->
roomMemberListController.setData(viewState) roomMemberListController.setData(viewState)
renderRoomSummary(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 // 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) { override fun onRoomMemberClicked(roomMember: RoomMemberSummary) {
@ -140,8 +140,8 @@ class RoomMemberListFragment @Inject constructor(
private fun renderRoomSummary(state: RoomMemberListViewState) { private fun renderRoomSummary(state: RoomMemberListViewState) {
state.roomSummary()?.let { state.roomSummary()?.let {
roomSettingsToolbarTitleView.text = it.displayName views.roomSettingGeneric.roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) avatarRenderer.render(it.toMatrixItem(), views.roomSettingGeneric.roomSettingsToolbarAvatarImageView)
} }
} }
} }

View File

@ -22,6 +22,7 @@ import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.DialogBackgroundSyncModeBinding
class BackgroundSyncModeChooserDialog : DialogFragment() { class BackgroundSyncModeChooserDialog : DialogFragment() {
@ -31,25 +32,26 @@ class BackgroundSyncModeChooserDialog : DialogFragment() {
val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE))
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_background_sync_mode, null) val view = requireActivity().layoutInflater.inflate(R.layout.dialog_background_sync_mode, null)
val views = DialogBackgroundSyncModeBinding.bind(view)
val dialog = AlertDialog.Builder(requireActivity()) val dialog = AlertDialog.Builder(requireActivity())
.setTitle(R.string.settings_background_fdroid_sync_mode) .setTitle(R.string.settings_background_fdroid_sync_mode)
.setView(view) .setView(view)
.setPositiveButton(R.string.cancel, null) .setPositiveButton(R.string.cancel, null)
.create() .create()
view.findViewById<View>(R.id.backgroundSyncModeBattery).setOnClickListener { views.backgroundSyncModeBattery.setOnClickListener {
interactionListener interactionListener
?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY } ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY }
?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY) ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY)
dialog.dismiss() dialog.dismiss()
} }
view.findViewById<View>(R.id.backgroundSyncModeReal).setOnClickListener { views.backgroundSyncModeReal.setOnClickListener {
interactionListener interactionListener
?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME } ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME }
?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME)
dialog.dismiss() dialog.dismiss()
} }
view.findViewById<View>(R.id.backgroundSyncModeOff).setOnClickListener { views.backgroundSyncModeOff.setOnClickListener {
interactionListener interactionListener
?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED } ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED }
?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED) ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED)

View File

@ -49,6 +49,7 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.TextUtils
import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.getSizeOfFiles
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogChangePasswordBinding
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.workers.signout.SignOutUiWorker import im.vector.app.features.workers.signout.SignOutUiWorker
@ -352,25 +353,18 @@ class VectorSettingsGeneralFragment @Inject constructor(
private fun onPasswordUpdateClick() { private fun onPasswordUpdateClick() {
activity?.let { activity -> activity?.let { activity ->
val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup
val views = DialogChangePasswordBinding.bind(view)
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)
var passwordShown = false var passwordShown = false
showPassword.setOnClickListener { views.changePasswordShowPasswords.setOnClickListener {
passwordShown = !passwordShown passwordShown = !passwordShown
oldPasswordText.showPassword(passwordShown) views.changePasswordOldPwdText.showPassword(passwordShown)
newPasswordText.showPassword(passwordShown) views.changePasswordNewPwdText.showPassword(passwordShown)
confirmNewPasswordText.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) val dialog = AlertDialog.Builder(activity)
@ -389,53 +383,53 @@ class VectorSettingsGeneralFragment @Inject constructor(
updateButton.isEnabled = false updateButton.isEnabled = false
fun updateUi() { fun updateUi() {
val oldPwd = oldPasswordText.text.toString() val oldPwd = views.changePasswordOldPwdText.text.toString()
val newPwd = newPasswordText.text.toString() val newPwd = views.changePasswordNewPwdText.text.toString()
val newConfirmPwd = confirmNewPasswordText.text.toString() val newConfirmPwd = views.changePasswordConfirmNewPwdText.text.toString()
updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd
if (newPwd.isNotEmpty() && newConfirmPwd.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) { override fun afterTextChanged(s: Editable) {
oldPasswordTil.error = null views.changePasswordOldPwdTil.error = null
updateUi() updateUi()
} }
}) })
newPasswordText.addTextChangedListener(object : SimpleTextWatcher() { views.changePasswordNewPwdText.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
confirmNewPasswordTil.error = null views.changePasswordConfirmNewPwdTil.error = null
updateUi() updateUi()
} }
}) })
confirmNewPasswordText.addTextChangedListener(object : SimpleTextWatcher() { views.changePasswordConfirmNewPwdText.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
confirmNewPasswordTil.error = null views.changePasswordConfirmNewPwdTil.error = null
updateUi() updateUi()
} }
}) })
fun showPasswordLoadingView(toShow: Boolean) { fun showPasswordLoadingView(toShow: Boolean) {
if (toShow) { if (toShow) {
showPassword.isEnabled = false views.changePasswordShowPasswords.isEnabled = false
oldPasswordText.isEnabled = false views.changePasswordOldPwdText.isEnabled = false
newPasswordText.isEnabled = false views.changePasswordNewPwdText.isEnabled = false
confirmNewPasswordText.isEnabled = false views.changePasswordConfirmNewPwdText.isEnabled = false
changePasswordLoader.isVisible = true views.changePasswordLoader.isVisible = true
updateButton.isEnabled = false updateButton.isEnabled = false
cancelButton.isEnabled = false cancelButton.isEnabled = false
} else { } else {
showPassword.isEnabled = true views.changePasswordShowPasswords.isEnabled = true
oldPasswordText.isEnabled = true views.changePasswordOldPwdText.isEnabled = true
newPasswordText.isEnabled = true views.changePasswordNewPwdText.isEnabled = true
confirmNewPasswordText.isEnabled = true views.changePasswordConfirmNewPwdText.isEnabled = true
changePasswordLoader.isVisible = false views.changePasswordLoader.isVisible = false
updateButton.isEnabled = true updateButton.isEnabled = true
cancelButton.isEnabled = true cancelButton.isEnabled = true
} }
@ -444,13 +438,13 @@ class VectorSettingsGeneralFragment @Inject constructor(
updateButton.setOnClickListener { updateButton.setOnClickListener {
if (passwordShown) { if (passwordShown) {
// Hide passwords during processing // Hide passwords during processing
showPassword.performClick() views.changePasswordShowPasswords.performClick()
} }
view.hideKeyboard() view.hideKeyboard()
val oldPwd = oldPasswordText.text.toString() val oldPwd = views.changePasswordOldPwdText.text.toString()
val newPwd = newPasswordText.text.toString() val newPwd = views.changePasswordNewPwdText.text.toString()
showPasswordLoadingView(true) showPasswordLoadingView(true)
lifecycleScope.launch { lifecycleScope.launch {
@ -466,9 +460,9 @@ class VectorSettingsGeneralFragment @Inject constructor(
activity.toast(R.string.settings_password_updated) activity.toast(R.string.settings_password_updated)
}, { failure -> }, { failure ->
if (failure.isInvalidPassword()) { 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 { } else {
oldPasswordTil.error = getString(R.string.settings_fail_to_update_password) views.changePasswordOldPwdTil.error = getString(R.string.settings_fail_to_update_password)
} }
}) })
} }

View File

@ -24,6 +24,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -63,71 +64,70 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
troubleshoot_test_recycler_view.layoutManager = layoutManager views.troubleshootTestRecyclerView.layoutManager = layoutManager
val dividerItemDecoration = DividerItemDecoration(troubleshoot_test_recycler_view.context, val dividerItemDecoration = DividerItemDecoration(view.context, layoutManager.orientation)
layoutManager.orientation) views.troubleshootTestRecyclerView.addItemDecoration(dividerItemDecoration)
troubleshoot_test_recycler_view.addItemDecoration(dividerItemDecoration)
troubleshoot_summ_button.debouncedClicks { views.troubleshootSummButton.debouncedClicks {
bugReporter.openBugReportScreen(requireActivity()) bugReporter.openBugReportScreen(requireActivity())
} }
troubleshoot_run_button.debouncedClicks { views.troubleshootRunButton.debouncedClicks {
testManager?.retry(testStartForActivityResult) testManager?.retry(testStartForActivityResult)
} }
startUI() startUI()
} }
private fun 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 = testManagerFactory.create(this)
testManager?.statusListener = { troubleshootTestManager -> testManager?.statusListener = { troubleshootTestManager ->
if (isAdded) { if (isAdded) {
TransitionManager.beginDelayedTransition(troubleshoot_bottom_view) TransitionManager.beginDelayedTransition(views.troubleshootBottomView)
when (troubleshootTestManager.diagStatus) { when (troubleshootTestManager.diagStatus) {
TroubleshootTest.TestStatus.NOT_STARTED -> { TroubleshootTest.TestStatus.NOT_STARTED -> {
toubleshoot_summ_description.text = "" views.toubleshootSummDescription.text = ""
troubleshoot_summ_button.visibility = View.GONE views.troubleshootSummButton.visibility = View.GONE
troubleshoot_run_button.visibility = View.VISIBLE views.troubleshootRunButton.visibility = View.VISIBLE
} }
TroubleshootTest.TestStatus.RUNNING, TroubleshootTest.TestStatus.RUNNING,
TroubleshootTest.TestStatus.WAITING_FOR_USER -> { TroubleshootTest.TestStatus.WAITING_FOR_USER -> {
val size = troubleshootTestManager.testListSize val size = troubleshootTestManager.testListSize
val currentTestIndex = troubleshootTestManager.currentTestIndex val currentTestIndex = troubleshootTestManager.currentTestIndex
toubleshoot_summ_description.text = getString( views.toubleshootSummDescription.text = getString(
R.string.settings_troubleshoot_diagnostic_running_status, R.string.settings_troubleshoot_diagnostic_running_status,
currentTestIndex, currentTestIndex,
size size
) )
troubleshoot_summ_button.visibility = View.GONE views.troubleshootSummButton.visibility = View.GONE
troubleshoot_run_button.visibility = View.GONE views.troubleshootRunButton.visibility = View.GONE
} }
TroubleshootTest.TestStatus.FAILED -> { TroubleshootTest.TestStatus.FAILED -> {
// check if there are quick fixes // check if there are quick fixes
val hasQuickFix = testManager?.hasQuickFix().orFalse() val hasQuickFix = testManager?.hasQuickFix().orFalse()
if (hasQuickFix) { 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 { } 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 views.troubleshootSummButton.visibility = View.VISIBLE
troubleshoot_run_button.visibility = View.VISIBLE views.troubleshootRunButton.visibility = View.VISIBLE
} }
TroubleshootTest.TestStatus.SUCCESS -> { TroubleshootTest.TestStatus.SUCCESS -> {
toubleshoot_summ_description.text = getString(R.string.settings_troubleshoot_diagnostic_success_status) views.toubleshootSummDescription.text = getString(R.string.settings_troubleshoot_diagnostic_success_status)
troubleshoot_summ_button.visibility = View.VISIBLE views.troubleshootSummButton.visibility = View.VISIBLE
troubleshoot_run_button.visibility = View.VISIBLE views.troubleshootRunButton.visibility = View.VISIBLE
} }
} }
} }
} }
troubleshoot_test_recycler_view.adapter = testManager?.adapter views.troubleshootTestRecyclerView.adapter = testManager?.adapter
testManager?.runDiagnostic(testStartForActivityResult) testManager?.runDiagnostic(testStartForActivityResult)
} }
override fun onDestroyView() { override fun onDestroyView() {
troubleshoot_test_recycler_view.cleanup() views.troubleshootTestRecyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
} }

View File

@ -50,6 +50,7 @@ import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.openFileSelection import im.vector.app.core.utils.openFileSelection
import im.vector.app.core.utils.toast 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.KeysExporter
import im.vector.app.features.crypto.keys.KeysImporter import im.vector.app.features.crypto.keys.KeysImporter
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
@ -447,42 +448,37 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
val filename = getFilenameFromUri(appContext, uri) val filename = getFilenameFromUri(appContext, uri)
val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null) val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)
val views = DialogImportE2eKeysBinding.bind(dialogLayout)
val textView = dialogLayout.findViewById<TextView>(R.id.dialog_e2e_keys_passphrase_filename)
if (filename.isNullOrBlank()) { if (filename.isNullOrBlank()) {
textView.isVisible = false views.dialogE2eKeysPassphraseFilename.isVisible = false
} else { } else {
textView.isVisible = true views.dialogE2eKeysPassphraseFilename.isVisible = true
textView.text = getString(R.string.import_e2e_keys_from_file, filename) views.dialogE2eKeysPassphraseFilename.text = getString(R.string.import_e2e_keys_from_file, filename)
} }
val builder = AlertDialog.Builder(thisActivity) val builder = AlertDialog.Builder(thisActivity)
.setTitle(R.string.encryption_import_room_keys) .setTitle(R.string.encryption_import_room_keys)
.setView(dialogLayout) .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 var passwordVisible = false
showPassword.setOnClickListener { views.importDialogShowPassword.setOnClickListener {
passwordVisible = !passwordVisible passwordVisible = !passwordVisible
passPhraseEditText.showPassword(passwordVisible) views.dialogE2eKeysPassphraseEditText.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) 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) { 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() val importDialog = builder.show()
importButton.setOnClickListener { views.dialogE2eKeysImportButton.setOnClickListener {
val password = passPhraseEditText.text.toString() val password = views.dialogE2eKeysPassphraseEditText.text.toString()
displayLoadingView() displayLoadingView()

View File

@ -21,6 +21,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
@ -73,23 +74,23 @@ class DeactivateAccountFragment @Inject constructor(
} }
private fun setupUi() { private fun setupUi() {
deactivateAccountPassword.textChanges() views.deactivateAccountPassword.textChanges()
.subscribe { .subscribe {
deactivateAccountPasswordTil.error = null views.deactivateAccountPasswordTil.error = null
deactivateAccountSubmit.isEnabled = it.isNotEmpty() views.deactivateAccountSubmit.isEnabled = it.isNotEmpty()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
private fun setupViewListeners() { private fun setupViewListeners() {
deactivateAccountPasswordReveal.setOnClickListener { views.deactivateAccountPasswordReveal.setOnClickListener {
viewModel.handle(DeactivateAccountAction.TogglePassword) viewModel.handle(DeactivateAccountAction.TogglePassword)
} }
deactivateAccountSubmit.debouncedClicks { views.deactivateAccountSubmit.debouncedClicks {
viewModel.handle(DeactivateAccountAction.DeactivateAccount( viewModel.handle(DeactivateAccountAction.DeactivateAccount(
deactivateAccountPassword.text.toString(), views.deactivateAccountPassword.text.toString(),
deactivateAccountEraseCheckbox.isChecked)) views.deactivateAccountEraseCheckbox.isChecked))
} }
} }
@ -102,11 +103,11 @@ class DeactivateAccountFragment @Inject constructor(
} }
DeactivateAccountViewEvents.EmptyPassword -> { DeactivateAccountViewEvents.EmptyPassword -> {
settingsActivity?.ignoreInvalidTokenError = false 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 -> { DeactivateAccountViewEvents.InvalidPassword -> {
settingsActivity?.ignoreInvalidTokenError = false 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 -> { is DeactivateAccountViewEvents.OtherFailure -> {
settingsActivity?.ignoreInvalidTokenError = false settingsActivity?.ignoreInvalidTokenError = false
@ -119,7 +120,7 @@ class DeactivateAccountFragment @Inject constructor(
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
deactivateAccountPassword.showPassword(state.passwordShown) views.deactivateAccountPassword.showPassword(state.passwordShown)
deactivateAccountPasswordReveal.setImageResource(if (state.passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.deactivateAccountPasswordReveal.setImageResource(if (state.passwordShown) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
} }

View File

@ -18,6 +18,10 @@ package im.vector.app.features.settings.troubleshoot
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import im.vector.app.R import im.vector.app.R
@ -43,68 +47,73 @@ class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList<Troublesh
override fun getItemCount(): Int = tests.size override fun getItemCount(): Int = tests.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 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) { fun bind(test: TroubleshootTest) {
val context = itemView.context val context = itemView.context
itemView.troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary)) troubleshootTestTitle.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary))
itemView.troubleshootTestDescription.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) troubleshootTestDescription.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary))
when (test.status) { when (test.status) {
TroubleshootTest.TestStatus.NOT_STARTED -> { 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 troubleshootProgressBar.visibility = View.INVISIBLE
itemView.troubleshootStatusIcon.visibility = View.VISIBLE troubleshootStatusIcon.visibility = View.VISIBLE
itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test) troubleshootStatusIcon.setImageResource(R.drawable.unit_test)
} }
TroubleshootTest.TestStatus.WAITING_FOR_USER -> { TroubleshootTest.TestStatus.WAITING_FOR_USER -> {
itemView.troubleshootProgressBar.visibility = View.INVISIBLE troubleshootProgressBar.visibility = View.INVISIBLE
itemView.troubleshootStatusIcon.visibility = View.VISIBLE troubleshootStatusIcon.visibility = View.VISIBLE
val infoColor = ContextCompat.getColor(context, R.color.vector_info_color) val infoColor = ContextCompat.getColor(context, R.color.vector_info_color)
val drawable = ContextCompat.getDrawable(itemView.context, R.drawable.ic_notification_privacy_warning)?.apply { val drawable = ContextCompat.getDrawable(itemView.context, R.drawable.ic_notification_privacy_warning)?.apply {
ThemeUtils.tintDrawableWithColor(this, infoColor) ThemeUtils.tintDrawableWithColor(this, infoColor)
} }
itemView.troubleshootStatusIcon.setImageDrawable(drawable) troubleshootStatusIcon.setImageDrawable(drawable)
itemView.troubleshootTestDescription.setTextColor(infoColor) troubleshootTestDescription.setTextColor(infoColor)
} }
TroubleshootTest.TestStatus.RUNNING -> { TroubleshootTest.TestStatus.RUNNING -> {
itemView.troubleshootProgressBar.visibility = View.VISIBLE troubleshootProgressBar.visibility = View.VISIBLE
itemView.troubleshootStatusIcon.visibility = View.INVISIBLE troubleshootStatusIcon.visibility = View.INVISIBLE
} }
TroubleshootTest.TestStatus.FAILED -> { TroubleshootTest.TestStatus.FAILED -> {
itemView.troubleshootProgressBar.visibility = View.INVISIBLE troubleshootProgressBar.visibility = View.INVISIBLE
itemView.troubleshootStatusIcon.visibility = View.VISIBLE troubleshootStatusIcon.visibility = View.VISIBLE
itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ko) 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 -> { TroubleshootTest.TestStatus.SUCCESS -> {
itemView.troubleshootProgressBar.visibility = View.INVISIBLE troubleshootProgressBar.visibility = View.INVISIBLE
itemView.troubleshootStatusIcon.visibility = View.VISIBLE troubleshootStatusIcon.visibility = View.VISIBLE
itemView.troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ok) troubleshootStatusIcon.setImageResource(R.drawable.unit_test_ok)
} }
} }
val quickFix = test.quickFix val quickFix = test.quickFix
if (quickFix != null) { if (quickFix != null) {
itemView.troubleshootTestButton.setText(test.quickFix!!.title) troubleshootTestButton.setText(test.quickFix!!.title)
itemView.troubleshootTestButton.setOnClickListener { _ -> troubleshootTestButton.setOnClickListener { _ ->
test.quickFix!!.doFix() test.quickFix!!.doFix()
} }
itemView.troubleshootTestButton.visibility = View.VISIBLE troubleshootTestButton.visibility = View.VISIBLE
} else { } else {
itemView.troubleshootTestButton.visibility = View.GONE troubleshootTestButton.visibility = View.GONE
} }
itemView.troubleshootTestTitle.setText(test.titleResId) troubleshootTestTitle.setText(test.titleResId)
val description = test.description val description = test.description
if (description == null) { if (description == null) {
itemView.troubleshootTestDescription.visibility = View.GONE troubleshootTestDescription.visibility = View.GONE
} else { } else {
itemView.troubleshootTestDescription.visibility = View.VISIBLE troubleshootTestDescription.visibility = View.VISIBLE
itemView.troubleshootTestDescription.text = description troubleshootTestDescription.text = description
} }
} }
} }

View File

@ -19,6 +19,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
@ -58,10 +59,10 @@ class ReviewTermsFragment @Inject constructor(
} }
termsController.listener = this termsController.listener = this
reviewTermsRecyclerView.configureWith(termsController) views.reviewTermsRecyclerView.configureWith(termsController)
reviewTermsAccept.onClick { reviewTermsViewModel.handle(ReviewTermsAction.Accept) } views.reviewTermsAccept.onClick { reviewTermsViewModel.handle(ReviewTermsAction.Accept) }
reviewTermsDecline.onClick { activity?.finish() } views.reviewTermsDecline.onClick { activity?.finish() }
reviewTermsViewModel.observeViewEvents { reviewTermsViewModel.observeViewEvents {
when (it) { when (it) {
@ -79,7 +80,7 @@ class ReviewTermsFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
reviewTermsRecyclerView.cleanup() views.reviewTermsRecyclerView.cleanup()
termsController.listener = null termsController.listener = null
super.onDestroyView() super.onDestroyView()
} }
@ -94,11 +95,11 @@ class ReviewTermsFragment @Inject constructor(
when (state.termsList) { when (state.termsList) {
is Loading -> { is Loading -> {
reviewTermsBottomBar.isVisible = false views.reviewTermsBottomBar.isVisible = false
} }
is Success -> { is Success -> {
reviewTermsBottomBar.isVisible = true views.reviewTermsBottomBar.isVisible = true
reviewTermsAccept.isEnabled = state.termsList.invoke().all { it.accepted } views.reviewTermsAccept.isEnabled = state.termsList.invoke().all { it.accepted }
} }
else -> Unit else -> Unit
} }

View File

@ -57,11 +57,11 @@ class ScanUserCodeFragment @Inject constructor()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
userCodeMyCodeButton.debouncedClicks { views.userCodeMyCodeButton.debouncedClicks {
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
} }
userCodeOpenGalleryButton.debouncedClicks { views.userCodeOpenGalleryButton.debouncedClicks {
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher) MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
} }
} }
@ -94,11 +94,11 @@ class ScanUserCodeFragment @Inject constructor()
} }
private fun startCamera() { private fun startCamera() {
userCodeScannerView.startCamera() views.userCodeScannerView.startCamera()
userCodeScannerView.setAutoFocus(autoFocus) views.userCodeScannerView.setAutoFocus(autoFocus)
userCodeScannerView.debouncedClicks { views.userCodeScannerView.debouncedClicks {
this.autoFocus = !autoFocus this.autoFocus = !autoFocus
userCodeScannerView.setAutoFocus(autoFocus) views.userCodeScannerView.setAutoFocus(autoFocus)
} }
} }
@ -112,7 +112,7 @@ class ScanUserCodeFragment @Inject constructor()
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// Register ourselves as a handler for scan results. // 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)) { if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) {
startCamera() startCamera()
} }
@ -120,9 +120,9 @@ class ScanUserCodeFragment @Inject constructor()
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
userCodeScannerView.setResultHandler(null) views.userCodeScannerView.setResultHandler(null)
// Stop camera on pause // Stop camera on pause
userCodeScannerView.stopCamera() views.userCodeScannerView.stopCamera()
} }
override fun handleResult(result: Result?) { override fun handleResult(result: Result?) {

View File

@ -78,9 +78,9 @@ class WidgetFragment @Inject constructor() :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
widgetWebView.setupForWidget(this) views.widgetWebView.setupForWidget(this)
if (fragmentArgs.kind.isAdmin()) { if (fragmentArgs.kind.isAdmin()) {
viewModel.getPostAPIMediator().setWebView(widgetWebView) viewModel.getPostAPIMediator().setWebView(views.widgetWebView)
} }
viewModel.observeViewEvents { viewModel.observeViewEvents {
Timber.v("Observed view events: $it") Timber.v("Observed view events: $it")
@ -114,12 +114,12 @@ class WidgetFragment @Inject constructor() :
if (fragmentArgs.kind.isAdmin()) { if (fragmentArgs.kind.isAdmin()) {
viewModel.getPostAPIMediator().clearWebView() viewModel.getPostAPIMediator().clearWebView()
} }
widgetWebView.clearAfterWidget() views.widgetWebView.clearAfterWidget()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
widgetWebView?.let { views.widgetWebView?.let {
it.resumeTimers() it.resumeTimers()
it.onResume() it.onResume()
} }
@ -127,7 +127,7 @@ class WidgetFragment @Inject constructor() :
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
widgetWebView?.let { views.widgetWebView?.let {
it.pauseTimers() it.pauseTimers()
it.onPause() it.onPause()
} }
@ -166,7 +166,7 @@ class WidgetFragment @Inject constructor() :
return@withState true return@withState true
} }
R.id.action_refresh -> if (state.formattedURL.complete) { R.id.action_refresh -> if (state.formattedURL.complete) {
widgetWebView.reload() views.widgetWebView.reload()
return@withState true return@withState true
} }
R.id.action_widget_open_ext -> if (state.formattedURL.complete) { 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 -> override fun onBackPressed(toolbarButton: Boolean): Boolean = withState(viewModel) { state ->
if (state.formattedURL.complete) { if (state.formattedURL.complete) {
if (widgetWebView.canGoBack()) { if (views.widgetWebView.canGoBack()) {
widgetWebView.goBack() views.widgetWebView.goBack()
return@withState true return@withState true
} }
} }
@ -196,37 +196,37 @@ class WidgetFragment @Inject constructor() :
when (state.formattedURL) { when (state.formattedURL) {
is Incomplete -> { is Incomplete -> {
setStateError(null) setStateError(null)
widgetWebView.isInvisible = true views.widgetWebView.isInvisible = true
widgetProgressBar.isIndeterminate = true views.widgetProgressBar.isIndeterminate = true
widgetProgressBar.isVisible = true views.widgetProgressBar.isVisible = true
} }
is Success -> { is Success -> {
setStateError(null) setStateError(null)
when (state.webviewLoadedUrl) { when (state.webviewLoadedUrl) {
Uninitialized -> { Uninitialized -> {
widgetWebView.isInvisible = true views.widgetWebView.isInvisible = true
} }
is Loading -> { is Loading -> {
setStateError(null) setStateError(null)
widgetWebView.isInvisible = false views.widgetWebView.isInvisible = false
widgetProgressBar.isIndeterminate = true views.widgetProgressBar.isIndeterminate = true
widgetProgressBar.isVisible = true views.widgetProgressBar.isVisible = true
} }
is Success -> { is Success -> {
widgetWebView.isInvisible = false views.widgetWebView.isInvisible = false
widgetProgressBar.isVisible = false views.widgetProgressBar.isVisible = false
setStateError(null) setStateError(null)
} }
is Fail -> { is Fail -> {
widgetProgressBar.isInvisible = true views.widgetProgressBar.isInvisible = true
setStateError(state.webviewLoadedUrl.error.message) setStateError(state.webviewLoadedUrl.error.message)
} }
} }
} }
is Fail -> { is Fail -> {
// we need to show Error // we need to show Error
widgetWebView.isInvisible = true views.widgetWebView.isInvisible = true
widgetProgressBar.isVisible = false views.widgetProgressBar.isVisible = false
setStateError(state.formattedURL.error.message) setStateError(state.formattedURL.error.message)
} }
} }
@ -282,19 +282,19 @@ class WidgetFragment @Inject constructor() :
} }
private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) { private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) {
widgetWebView.clearHistory() views.widgetWebView.clearHistory()
widgetWebView.loadUrl(event.formattedURL) views.widgetWebView.loadUrl(event.formattedURL)
} }
private fun setStateError(message: String?) { private fun setStateError(message: String?) {
if (message == null) { if (message == null) {
widgetErrorLayout.isVisible = false views.widgetErrorLayout.isVisible = false
widgetErrorText.text = null views.widgetErrorText.text = null
} else { } else {
widgetProgressBar.isVisible = false views.widgetProgressBar.isVisible = false
widgetErrorLayout.isVisible = true views.widgetErrorLayout.isVisible = true
widgetWebView.isInvisible = true views.widgetWebView.isInvisible = true
widgetErrorText.text = getString(R.string.room_widget_failed_to_load, message) views.widgetErrorText.text = getString(R.string.room_widget_failed_to_load, message)
} }
} }

View File

@ -20,7 +20,9 @@ import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
@ -31,6 +33,9 @@ class SignOutBottomSheetActionButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
private val actionIconImageView: ImageView
private val actionTitleText: TextView
var action: (() -> Unit)? = null var action: (() -> Unit)? = null
var title: String? = 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)) 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)) 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() typedArray.recycle()
signedOutActionClickable.setOnClickListener { setOnClickListener {
action?.invoke() action?.invoke()
} }
} }

View File

@ -4,7 +4,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/inviteUsersButton" android:id="@+id/inviteUsersButton"