Migrate to ViewBindings (#1072) - WIP

This commit is contained in:
Benoit Marty 2020-12-16 03:25:17 +01:00
parent 409d7e50bb
commit 7de2494af2
28 changed files with 344 additions and 320 deletions

View File

@ -39,7 +39,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) { ) : FrameLayout(context, attrs, defStyleAttr) {
private val views : ItemVerificationActionBinding val views : ItemVerificationActionBinding
var title: String? = null var title: String? = null
set(value) { set(value) {

View File

@ -27,6 +27,7 @@ import androidx.core.text.italic
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ResourceLimitErrorFormatter import im.vector.app.core.error.ResourceLimitErrorFormatter
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewNotificationAreaBinding
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import me.gujun.android.span.span import me.gujun.android.span.span
@ -48,6 +49,8 @@ class NotificationAreaView @JvmOverloads constructor(
var delegate: Delegate? = null var delegate: Delegate? = null
private var state: State = State.Initial private var state: State = State.Initial
private lateinit var views : ViewNotificationAreaBinding
init { init {
setupView() setupView()
} }
@ -78,27 +81,28 @@ class NotificationAreaView @JvmOverloads constructor(
private fun setupView() { private fun setupView() {
inflate(context, R.layout.view_notification_area, this) inflate(context, R.layout.view_notification_area, this)
views = ViewNotificationAreaBinding.bind(this)
minimumHeight = DimensionConverter(resources).dpToPx(48) minimumHeight = DimensionConverter(resources).dpToPx(48)
} }
private fun cleanUp() { private fun cleanUp() {
roomNotificationMessage.setOnClickListener(null) views.roomNotificationMessage.setOnClickListener(null)
roomNotificationIcon.setOnClickListener(null) views.roomNotificationIcon.setOnClickListener(null)
setBackgroundColor(Color.TRANSPARENT) setBackgroundColor(Color.TRANSPARENT)
roomNotificationMessage.text = null views.roomNotificationMessage.text = null
roomNotificationIcon.setImageResource(0) views.roomNotificationIcon.setImageResource(0)
} }
private fun renderNoPermissionToPost() { private fun renderNoPermissionToPost() {
visibility = View.VISIBLE visibility = View.VISIBLE
roomNotificationIcon.setImageDrawable(null) views.roomNotificationIcon.setImageDrawable(null)
val message = span { val message = span {
italic { italic {
+resources.getString(R.string.room_do_not_have_permission_to_post) +resources.getString(R.string.room_do_not_have_permission_to_post)
} }
} }
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) views.roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary))
} }
private fun renderResourceLimitExceededError(state: State.ResourceLimitExceededError) { private fun renderResourceLimitExceededError(state: State.ResourceLimitExceededError) {
@ -114,16 +118,16 @@ class NotificationAreaView @JvmOverloads constructor(
formatterMode = ResourceLimitErrorFormatter.Mode.Hard formatterMode = ResourceLimitErrorFormatter.Mode.Hard
} }
val message = resourceLimitErrorFormatter.format(state.matrixError, formatterMode, clickable = true) val message = resourceLimitErrorFormatter.format(state.matrixError, formatterMode, clickable = true)
roomNotificationMessage.setTextColor(Color.WHITE) views.roomNotificationMessage.setTextColor(Color.WHITE)
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
roomNotificationMessage.movementMethod = LinkMovementMethod.getInstance() views.roomNotificationMessage.movementMethod = LinkMovementMethod.getInstance()
roomNotificationMessage.setLinkTextColor(Color.WHITE) views.roomNotificationMessage.setLinkTextColor(Color.WHITE)
setBackgroundColor(ContextCompat.getColor(context, backgroundColor)) setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
} }
private fun renderTombstone(state: State.Tombstone) { private fun renderTombstone(state: State.Tombstone) {
visibility = View.VISIBLE visibility = View.VISIBLE
roomNotificationIcon.setImageResource(R.drawable.error) views.roomNotificationIcon.setImageResource(R.drawable.error)
val message = span { val message = span {
+resources.getString(R.string.room_tombstone_versioned_description) +resources.getString(R.string.room_tombstone_versioned_description)
+"\n" +"\n"
@ -132,8 +136,8 @@ class NotificationAreaView @JvmOverloads constructor(
onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) } onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) }
} }
} }
roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance() views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance()
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
} }
private fun renderDefault() { private fun renderDefault() {

View File

@ -22,7 +22,7 @@ import android.widget.LinearLayout
import androidx.annotation.IntRange import androidx.annotation.IntRange
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.ViewPasswordStrengthBarBinding
/** /**
* A password strength bar custom widget * A password strength bar custom widget
@ -39,6 +39,8 @@ class PasswordStrengthBar @JvmOverloads constructor(
defStyleAttr: Int = 0) defStyleAttr: Int = 0)
: LinearLayout(context, attrs, defStyleAttr) { : LinearLayout(context, attrs, defStyleAttr) {
private val views: ViewPasswordStrengthBarBinding
private val colorBackground = ContextCompat.getColor(context, R.color.password_strength_bar_undefined) private val colorBackground = ContextCompat.getColor(context, R.color.password_strength_bar_undefined)
private val colorWeak = ContextCompat.getColor(context, R.color.password_strength_bar_weak) private val colorWeak = ContextCompat.getColor(context, R.color.password_strength_bar_weak)
private val colorLow = ContextCompat.getColor(context, R.color.password_strength_bar_low) private val colorLow = ContextCompat.getColor(context, R.color.password_strength_bar_low)
@ -52,34 +54,34 @@ class PasswordStrengthBar @JvmOverloads constructor(
when (newValue) { when (newValue) {
0 -> { 0 -> {
password_strength_bar_1.setBackgroundColor(colorBackground) views.passwordStrengthBar1.setBackgroundColor(colorBackground)
password_strength_bar_2.setBackgroundColor(colorBackground) views.passwordStrengthBar2.setBackgroundColor(colorBackground)
password_strength_bar_3.setBackgroundColor(colorBackground) views.passwordStrengthBar3.setBackgroundColor(colorBackground)
password_strength_bar_4.setBackgroundColor(colorBackground) views.passwordStrengthBar4.setBackgroundColor(colorBackground)
} }
1 -> { 1 -> {
password_strength_bar_1.setBackgroundColor(colorWeak) views.passwordStrengthBar1.setBackgroundColor(colorWeak)
password_strength_bar_2.setBackgroundColor(colorBackground) views.passwordStrengthBar2.setBackgroundColor(colorBackground)
password_strength_bar_3.setBackgroundColor(colorBackground) views.passwordStrengthBar3.setBackgroundColor(colorBackground)
password_strength_bar_4.setBackgroundColor(colorBackground) views.passwordStrengthBar4.setBackgroundColor(colorBackground)
} }
2 -> { 2 -> {
password_strength_bar_1.setBackgroundColor(colorLow) views.passwordStrengthBar1.setBackgroundColor(colorLow)
password_strength_bar_2.setBackgroundColor(colorLow) views.passwordStrengthBar2.setBackgroundColor(colorLow)
password_strength_bar_3.setBackgroundColor(colorBackground) views.passwordStrengthBar3.setBackgroundColor(colorBackground)
password_strength_bar_4.setBackgroundColor(colorBackground) views.passwordStrengthBar4.setBackgroundColor(colorBackground)
} }
3 -> { 3 -> {
password_strength_bar_1.setBackgroundColor(colorOk) views.passwordStrengthBar1.setBackgroundColor(colorOk)
password_strength_bar_2.setBackgroundColor(colorOk) views.passwordStrengthBar2.setBackgroundColor(colorOk)
password_strength_bar_3.setBackgroundColor(colorOk) views.passwordStrengthBar3.setBackgroundColor(colorOk)
password_strength_bar_4.setBackgroundColor(colorBackground) views.passwordStrengthBar4.setBackgroundColor(colorBackground)
} }
4 -> { 4 -> {
password_strength_bar_1.setBackgroundColor(colorStrong) views.passwordStrengthBar1.setBackgroundColor(colorStrong)
password_strength_bar_2.setBackgroundColor(colorStrong) views.passwordStrengthBar2.setBackgroundColor(colorStrong)
password_strength_bar_3.setBackgroundColor(colorStrong) views.passwordStrengthBar3.setBackgroundColor(colorStrong)
password_strength_bar_4.setBackgroundColor(colorStrong) views.passwordStrengthBar4.setBackgroundColor(colorStrong)
} }
} }
} }
@ -87,6 +89,7 @@ class PasswordStrengthBar @JvmOverloads constructor(
init { init {
LayoutInflater.from(context) LayoutInflater.from(context)
.inflate(R.layout.view_password_strength_bar, this, true) .inflate(R.layout.view_password_strength_bar, this, true)
views = ViewPasswordStrengthBarBinding.bind(this)
orientation = HORIZONTAL orientation = HORIZONTAL
strength = 0 strength = 0
} }

View File

@ -23,6 +23,7 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
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.ViewReadReceiptsBinding
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.toMatrixItem import im.vector.app.features.home.room.detail.timeline.item.toMatrixItem
@ -37,12 +38,21 @@ class ReadReceiptsView @JvmOverloads constructor(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
private val receiptAvatars: List<ImageView> by lazy { private val views : ViewReadReceiptsBinding
listOf(receiptAvatar1, receiptAvatar2, receiptAvatar3, receiptAvatar4, receiptAvatar5)
}
init { init {
setupView() setupView()
views = ViewReadReceiptsBinding.bind(this)
}
private val receiptAvatars: List<ImageView> by lazy {
listOf(
views.receiptAvatar1,
views.receiptAvatar2,
views.receiptAvatar3,
views.receiptAvatar4,
views.receiptAvatar5
)
} }
private fun setupView() { private fun setupView() {
@ -69,12 +79,12 @@ class ReadReceiptsView @JvmOverloads constructor(
.take(MAX_RECEIPT_DESCRIBED) .take(MAX_RECEIPT_DESCRIBED)
if (readReceipts.size > MAX_RECEIPT_DISPLAYED) { if (readReceipts.size > MAX_RECEIPT_DISPLAYED) {
receiptMore.visibility = View.VISIBLE views.receiptMore.visibility = View.VISIBLE
receiptMore.text = context.getString( views.receiptMore.text = context.getString(
R.string.x_plus, readReceipts.size - MAX_RECEIPT_DISPLAYED R.string.x_plus, readReceipts.size - MAX_RECEIPT_DISPLAYED
) )
} else { } else {
receiptMore.visibility = View.GONE views.receiptMore.visibility = View.GONE
} }
contentDescription = when (readReceipts.size) { contentDescription = when (readReceipts.size) {
1 -> 1 ->

View File

@ -285,7 +285,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
return isPermissionGranted return isPermissionGranted
} }
fun VectorBaseActivity.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) { fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
showSnackbar(getString(rationaleMessage), R.string.settings) { showSnackbar(getString(rationaleMessage), R.string.settings) {
openAppSettingsPage(this) openAppSettingsPage(this)
} }

View File

@ -45,16 +45,16 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
renderState(it) renderState(it)
} }
views.callControlsSoundDevice.clickableView.debouncedClicks { views.callControlsSoundDevice.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.SwitchSoundDevice) callViewModel.handle(VectorCallViewActions.SwitchSoundDevice)
} }
views.callControlsSwitchCamera.clickableView.debouncedClicks { views.callControlsSwitchCamera.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleCamera) callViewModel.handle(VectorCallViewActions.ToggleCamera)
dismiss() dismiss()
} }
views.callControlsToggleSDHD.clickableView.debouncedClicks { views.callControlsToggleSDHD.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleHDSD) callViewModel.handle(VectorCallViewActions.ToggleHDSD)
dismiss() dismiss()
} }

View File

@ -77,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE views.toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel

View File

@ -45,12 +45,12 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
private val zxcvbn = Zxcvbn() private val zxcvbn = Zxcvbn()
private fun onPassphraseChanged() { private fun onPassphraseChanged() {
viewModel.passphrase.value = keys_backup_passphrase_enter_edittext.text.toString() viewModel.passphrase.value = views.keysBackupPassphraseEnterEdittext.text.toString()
viewModel.confirmPassphraseError.value = null viewModel.confirmPassphraseError.value = null
} }
private fun onConfirmPassphraseChanged() { private fun onConfirmPassphraseChanged() {
viewModel.confirmPassphrase.value = mPassphraseConfirmTextEdit.text.toString() viewModel.confirmPassphrase.value = views.mPassphraseConfirmTextEdit.text.toString()
} }
private lateinit var viewModel: KeysBackupSetupSharedViewModel private lateinit var viewModel: KeysBackupSetupSharedViewModel
@ -72,24 +72,24 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
private fun bindViewToViewModel() { private fun bindViewToViewModel() {
viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength -> viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength ->
if (strength == null) { if (strength == null) {
mPassphraseProgressLevel.strength = 0 views.mPassphraseProgressLevel.strength = 0
keys_backup_passphrase_enter_til.error = null views.keysBackupPassphraseEnterTil.error = null
} else { } else {
val score = strength.score val score = strength.score
mPassphraseProgressLevel.strength = score views.mPassphraseProgressLevel.strength = score
if (score in 1..3) { if (score in 1..3) {
val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale) val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale)
if (warning != null) { if (warning != null) {
keys_backup_passphrase_enter_til.error = warning views.keysBackupPassphraseEnterTil.error = warning
} }
val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale) val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale)
if (suggestions != null) { if (suggestions != null) {
keys_backup_passphrase_enter_til.error = suggestions.firstOrNull() views.keysBackupPassphraseEnterTil.error = suggestions.firstOrNull()
} }
} else { } else {
keys_backup_passphrase_enter_til.error = null views.keysBackupPassphraseEnterTil.error = null
} }
} }
}) })
@ -107,28 +107,28 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
} }
}) })
keys_backup_passphrase_enter_edittext.setText(viewModel.passphrase.value) views.keysBackupPassphraseEnterEdittext.setText(viewModel.passphrase.value)
viewModel.passphraseError.observe(viewLifecycleOwner, Observer { viewModel.passphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(keys_backup_root) TransitionManager.beginDelayedTransition(views.keysBackupRoot)
keys_backup_passphrase_enter_til.error = it views.keysBackupPassphraseEnterTil.error = it
}) })
mPassphraseConfirmTextEdit.setText(viewModel.confirmPassphrase.value) views.mPassphraseConfirmTextEdit.setText(viewModel.confirmPassphrase.value)
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer { viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
val shouldBeVisible = it ?: false val shouldBeVisible = it ?: false
keys_backup_passphrase_enter_edittext.showPassword(shouldBeVisible) views.keysBackupPassphraseEnterEdittext.showPassword(shouldBeVisible)
mPassphraseConfirmTextEdit.showPassword(shouldBeVisible) views.mPassphraseConfirmTextEdit.showPassword(shouldBeVisible)
keys_backup_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.keysBackupViewShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}) })
viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer { viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(keys_backup_root) TransitionManager.beginDelayedTransition(views.keysBackupRoot)
mPassphraseConfirmInputLayout.error = it views.mPassphraseConfirmInputLayout.error = it
}) })
mPassphraseConfirmTextEdit.setOnEditorActionListener { _, actionId, _ -> views.mPassphraseConfirmTextEdit.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
doNext() doNext()
return@setOnEditorActionListener true return@setOnEditorActionListener true
@ -138,12 +138,12 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
} }
private fun setupViews() { private fun setupViews() {
keys_backup_view_show_password.setOnClickListener { toggleVisibilityMode() } views.keysBackupViewShowPassword.setOnClickListener { toggleVisibilityMode() }
keys_backup_setup_step2_button.setOnClickListener { doNext() } views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
keys_backup_setup_step2_skip_button.setOnClickListener { skipPassphrase() } views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
keys_backup_passphrase_enter_edittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() } views.keysBackupPassphraseEnterEdittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() }
mPassphraseConfirmTextEdit.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() } views.mPassphraseConfirmTextEdit.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
} }
private fun toggleVisibilityMode() { private fun toggleVisibilityMode() {

View File

@ -65,7 +65,7 @@ class SharedSecureStorageActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.addFragmentOnAttachListener(this) supportFragmentManager.addFragmentOnAttachListener(this)
toolbar.visibility = View.GONE views.toolbar.visibility = View.GONE
viewModel.observeViewEvents { observeViewEvents(it) } viewModel.observeViewEvents { observeViewEvents(it) }
@ -132,7 +132,7 @@ class SharedSecureStorageActivity :
} }
override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) { override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) {
if (fragment is VectorBaseBottomSheetDialogFragment) { if (fragment is VectorBaseBottomSheetDialogFragment<*>) {
fragment.resultListener = this fragment.resultListener = this
} }
} }

View File

@ -47,9 +47,9 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ssss_restore_with_key_text.text = getString(R.string.enter_secret_storage_input_key) views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key)
ssss_key_enter_edittext.editorActionEvents() views.ssssKeyEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -59,35 +59,35 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_key_enter_edittext.textChanges() views.ssssKeyEnterEdittext.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .subscribe {
ssss_key_enter_til.error = null views.ssssKeyEnterTil.error = null
ssss_key_submit.isEnabled = it.isNotBlank() views.ssssKeySubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
ssss_key_reset.clickableView.debouncedClicks { views.ssssKeyReset.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
} }
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
when (it) { when (it) {
is SharedSecureStorageViewEvent.KeyInlineError -> { is SharedSecureStorageViewEvent.KeyInlineError -> {
ssss_key_enter_til.error = it.message views.ssssKeyEnterTil.error = it.message
} }
} }
} }
ssss_key_submit.debouncedClicks { submit() } views.ssssKeySubmit.debouncedClicks { submit() }
} }
fun submit() { fun submit() {
val text = ssss_key_enter_edittext.text.toString() val text = views.ssssKeyEnterEdittext.text.toString()
if (text.isBlank()) return // Should not reach this point as button disabled if (text.isBlank()) return // Should not reach this point as button disabled
ssss_key_submit.isEnabled = false views.ssssKeySubmit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text)) sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
} }
@ -99,7 +99,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
?.bufferedReader() ?.bufferedReader()
?.use { it.readText() } ?.use { it.readText() }
?.let { ?.let {
ssss_key_enter_edittext.setText(it) views.ssssKeyEnterEdittext.setText(it)
} }
} }
} }

View File

@ -80,7 +80,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
views.ssssPassphraseReset.clickableView.debouncedClicks { views.ssssPassphraseReset.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
} }

View File

@ -55,13 +55,13 @@ class BootstrapAccountPasswordFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val recPassPhrase = getString(R.string.account_password) val recPassPhrase = getString(R.string.account_password)
bootstrapDescriptionText.text = getString(R.string.enter_account_password, recPassPhrase) views.bootstrapDescriptionText.text = getString(R.string.enter_account_password, recPassPhrase)
.toSpannable() .toSpannable()
.colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
bootstrapAccountPasswordEditText.hint = getString(R.string.account_password) views.bootstrapAccountPasswordEditText.hint = getString(R.string.account_password)
bootstrapAccountPasswordEditText.editorActionEvents() views.bootstrapAccountPasswordEditText.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -71,21 +71,21 @@ class BootstrapAccountPasswordFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
bootstrapAccountPasswordEditText.textChanges() views.bootstrapAccountPasswordEditText.textChanges()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .subscribe {
if (!it.isNullOrBlank()) { if (!it.isNullOrBlank()) {
bootstrapAccountPasswordTil.error = null views.bootstrapAccountPasswordTil.error = null
} }
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapPasswordButton.debouncedClicks { submit() } views.bootstrapPasswordButton.debouncedClicks { submit() }
withState(sharedViewModel) { state -> withState(sharedViewModel) { state ->
(state.step as? BootstrapStep.AccountPassword)?.failure?.let { (state.step as? BootstrapStep.AccountPassword)?.failure?.let {
bootstrapAccountPasswordTil.error = it views.bootstrapAccountPasswordTil.error = it
} }
} }
} }
@ -94,9 +94,9 @@ class BootstrapAccountPasswordFragment @Inject constructor(
if (state.step !is BootstrapStep.AccountPassword) { if (state.step !is BootstrapStep.AccountPassword) {
return@withState return@withState
} }
val accountPassword = bootstrapAccountPasswordEditText.text?.toString() val accountPassword = views.bootstrapAccountPasswordEditText.text?.toString()
if (accountPassword.isNullOrBlank()) { if (accountPassword.isNullOrBlank()) {
bootstrapAccountPasswordTil.error = getString(R.string.error_empty_field_your_password) views.bootstrapAccountPasswordTil.error = getString(R.string.error_empty_field_your_password)
} else { } else {
view?.hideKeyboard() view?.hideKeyboard()
sharedViewModel.handle(BootstrapActions.ReAuth(accountPassword)) sharedViewModel.handle(BootstrapActions.ReAuth(accountPassword))
@ -106,8 +106,8 @@ class BootstrapAccountPasswordFragment @Inject constructor(
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.AccountPassword) { if (state.step is BootstrapStep.AccountPassword) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false) views.bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
} }
} }

View File

@ -45,7 +45,7 @@ class BootstrapConclusionFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
views.bootstrapConclusionContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) } views.bootstrapConclusionContinue.views.itemVerificationClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) }
} }
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->

View File

@ -49,18 +49,18 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ssss_passphrase_security_progress.isGone = true views.ssssPassphraseSecurityProgress.isGone = true
bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice) views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice)
ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint) views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
withState(sharedViewModel) { withState(sharedViewModel) {
// set initial value (useful when coming back) // set initial value (useful when coming back)
ssss_passphrase_enter_edittext.setText(it.passphraseRepeat ?: "") views.ssssPassphraseEnterEdittext.setText(it.passphraseRepeat ?: "")
ssss_passphrase_enter_edittext.requestFocus() views.ssssPassphraseEnterEdittext.requestFocus()
} }
ssss_passphrase_enter_edittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -70,9 +70,9 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .subscribe {
ssss_passphrase_enter_til.error = null views.ssssPassphraseEnterTil.error = null
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: "")) sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: ""))
} }
.disposeOnDestroyView() .disposeOnDestroyView()
@ -85,20 +85,20 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
// } // }
} }
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapSubmit.debouncedClicks { submit() } views.bootstrapSubmit.debouncedClicks { submit() }
} }
private fun submit() = withState(sharedViewModel) { state -> private fun submit() = withState(sharedViewModel) { state ->
if (state.step !is BootstrapStep.ConfirmPassphrase) { if (state.step !is BootstrapStep.ConfirmPassphrase) {
return@withState return@withState
} }
val passphrase = ssss_passphrase_enter_edittext.text?.toString() val passphrase = views.ssssPassphraseEnterEdittext.text?.toString()
when { when {
passphrase.isNullOrBlank() -> passphrase.isNullOrBlank() ->
ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message) views.ssssPassphraseEnterTil.error = getString(R.string.passphrase_empty_error_message)
passphrase != state.passphrase -> passphrase != state.passphrase ->
ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_does_not_match) views.ssssPassphraseEnterTil.error = getString(R.string.passphrase_passphrase_does_not_match)
else -> { else -> {
view?.hideKeyboard() view?.hideKeyboard()
sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase)) sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase))
@ -109,8 +109,8 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.ConfirmPassphrase) { if (state.step is BootstrapStep.ConfirmPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
} }
} }

View File

@ -64,9 +64,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
withState(sharedViewModel) { withState(sharedViewModel) {
// set initial value (useful when coming back) // set initial value (useful when coming back)
bootstrapMigrateEditText.setText(it.passphrase ?: "") views.bootstrapMigrateEditText.setText(it.passphrase ?: "")
} }
bootstrapMigrateEditText.editorActionEvents() views.bootstrapMigrateEditText.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -76,19 +76,19 @@ class BootstrapMigrateBackupFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
bootstrapMigrateEditText.textChanges() views.bootstrapMigrateEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .subscribe {
bootstrapRecoveryKeyEnterTil.error = null views.bootstrapRecoveryKeyEnterTil.error = null
// sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) // sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
} }
.disposeOnDestroyView() .disposeOnDestroyView()
// sharedViewModel.observeViewEvents {} // sharedViewModel.observeViewEvents {}
bootstrapMigrateContinueButton.debouncedClicks { submit() } views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) } views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
} }
private fun submit() = withState(sharedViewModel) { state -> private fun submit() = withState(sharedViewModel) { state ->
@ -96,12 +96,12 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey() val isEnteringKey = getBackupSecretForMigration.useKey()
val secret = bootstrapMigrateEditText.text?.toString() val secret = views.bootstrapMigrateEditText.text?.toString()
if (secret.isNullOrEmpty()) { if (secret.isNullOrEmpty()) {
val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message
bootstrapRecoveryKeyEnterTil.error = getString(errRes) views.bootstrapRecoveryKeyEnterTil.error = getString(errRes)
} else if (isEnteringKey && !isValidRecoveryKey(secret)) { } else if (isEnteringKey && !isValidRecoveryKey(secret)) {
bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key) views.bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key)
} else { } else {
view?.hideKeyboard() view?.hideKeyboard()
if (isEnteringKey) { if (isEnteringKey) {
@ -118,38 +118,38 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey() val isEnteringKey = getBackupSecretForMigration.useKey()
if (isEnteringKey) { if (isEnteringKey) {
bootstrapMigrateShowPassword.isVisible = false views.bootstrapMigrateShowPassword.isVisible = false
bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE views.bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE
val recKey = getString(R.string.bootstrap_migration_backup_recovery_key) val recKey = getString(R.string.bootstrap_migration_backup_recovery_key)
bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey) views.bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey)
bootstrapMigrateEditText.hint = recKey views.bootstrapMigrateEditText.hint = recKey
bootstrapMigrateEditText.hint = recKey views.bootstrapMigrateEditText.hint = recKey
bootstrapMigrateForgotPassphrase.isVisible = false views.bootstrapMigrateForgotPassphrase.isVisible = false
bootstrapMigrateUseFile.isVisible = true views.bootstrapMigrateUseFile.isVisible = true
} else { } else {
bootstrapMigrateShowPassword.isVisible = true views.bootstrapMigrateShowPassword.isVisible = true
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) { if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false) views.bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password) views.bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)
bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase) views.bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase)
bootstrapMigrateForgotPassphrase.isVisible = true views.bootstrapMigrateForgotPassphrase.isVisible = true
val recKey = getString(R.string.bootstrap_migration_use_recovery_key) val recKey = getString(R.string.bootstrap_migration_use_recovery_key)
bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey) views.bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey)
.toSpannable() .toSpannable()
.colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
bootstrapMigrateUseFile.isVisible = false views.bootstrapMigrateUseFile.isVisible = false
} }
} }
@ -161,7 +161,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
?.bufferedReader() ?.bufferedReader()
?.use { it.readText() } ?.use { it.readText() }
?.let { ?.let {
bootstrapMigrateEditText.setText(it) views.bootstrapMigrateEditText.setText(it)
} }
} }
} }

View File

@ -53,9 +53,9 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
views.recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() } views.recoverySave.views.itemVerificationClickableZone.debouncedClicks { downloadRecoveryKey() }
views.recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() } views.recoveryCopy.views.itemVerificationClickableZone.debouncedClicks { shareRecoveryKey() }
views.recoveryContinue.clickableView.debouncedClicks { views.recoveryContinue.views.itemVerificationClickableZone.debouncedClicks {
// We do not display the final Fragment anymore // We do not display the final Fragment anymore
// TODO Do some cleanup // TODO Do some cleanup
// sharedViewModel.handle(BootstrapActions.GoToCompleted) // sharedViewModel.handle(BootstrapActions.GoToCompleted)

View File

@ -43,15 +43,15 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor()
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Actions when a key backup exist // Actions when a key backup exist
views.bootstrapSetupSecureSubmit.clickableView.debouncedClicks { views.bootstrapSetupSecureSubmit.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration) sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration)
} }
// Actions when there is no key backup // Actions when there is no key backup
views.bootstrapSetupSecureUseSecurityKey.clickableView.debouncedClicks { views.bootstrapSetupSecureUseSecurityKey.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false))
} }
views.bootstrapSetupSecureUseSecurityPassphrase.clickableView.debouncedClicks { views.bootstrapSetupSecureUseSecurityPassphrase.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true)) sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true))
} }
} }

View File

@ -69,7 +69,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
context.getString(R.string.sas_incoming_request_notif_content, name), context.getString(R.string.sas_incoming_request_notif_content, name),
R.drawable.ic_shield_black, R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity -> shouldBeDisplayedIn = { activity ->
if (activity is VectorBaseActivity) { if (activity is VectorBaseActivity<*>) {
// TODO a bit too ugly :/ // TODO a bit too ugly :/
activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let { activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let {
false.also { false.also {
@ -82,7 +82,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
) )
.apply { .apply {
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
} }
} }
@ -98,7 +98,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
addButton( addButton(
context.getString(R.string.action_open), context.getString(R.string.action_open),
Runnable { Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
} }
} }
@ -139,7 +139,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
) )
.apply { .apply {
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
val roomId = pr.roomId val roomId = pr.roomId
if (roomId.isNullOrBlank()) { if (roomId.isNullOrBlank()) {
it.navigator.waitSessionVerification(it) it.navigator.waitSessionVerification(it)

View File

@ -22,6 +22,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
@ -58,11 +59,11 @@ class SetIdentityServerFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
if (state.defaultIdentityServerUrl.isNullOrEmpty()) { if (state.defaultIdentityServerUrl.isNullOrEmpty()) {
// No default // No default
identityServerSetDefaultNotice.isVisible = false views.identityServerSetDefaultNotice.isVisible = false
identityServerSetDefaultSubmit.isVisible = false views.identityServerSetDefaultSubmit.isVisible = false
identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice_no_default) views.identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice_no_default)
} else { } else {
identityServerSetDefaultNotice.text = getString( views.identityServerSetDefaultNotice.text = getString(
R.string.identity_server_set_default_notice, R.string.identity_server_set_default_notice,
state.homeServerUrl.toReducedUrl(), state.homeServerUrl.toReducedUrl(),
state.defaultIdentityServerUrl.toReducedUrl() state.defaultIdentityServerUrl.toReducedUrl()
@ -71,10 +72,10 @@ class SetIdentityServerFragment @Inject constructor(
.colorizeMatchingText(state.defaultIdentityServerUrl.toReducedUrl(), .colorizeMatchingText(state.defaultIdentityServerUrl.toReducedUrl(),
colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast)) colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
identityServerSetDefaultNotice.isVisible = true views.identityServerSetDefaultNotice.isVisible = true
identityServerSetDefaultSubmit.isVisible = true views.identityServerSetDefaultSubmit.isVisible = true
identityServerSetDefaultSubmit.text = getString(R.string.identity_server_set_default_submit, state.defaultIdentityServerUrl.toReducedUrl()) views.identityServerSetDefaultSubmit.text = getString(R.string.identity_server_set_default_submit, state.defaultIdentityServerUrl.toReducedUrl())
identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice) views.identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice)
} }
} }
@ -83,28 +84,28 @@ class SetIdentityServerFragment @Inject constructor(
sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java) sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
identityServerSetDefaultAlternativeTextInput.setOnEditorActionListener { _, actionId, _ -> views.identityServerSetDefaultAlternativeTextInput.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(identityServerSetDefaultAlternativeTextInput.text.toString())) viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(views.identityServerSetDefaultAlternativeTextInput.text.toString()))
return@setOnEditorActionListener true return@setOnEditorActionListener true
} }
return@setOnEditorActionListener false return@setOnEditorActionListener false
} }
identityServerSetDefaultAlternativeTextInput views.identityServerSetDefaultAlternativeTextInput
.textChanges() .textChanges()
.subscribe { .subscribe {
identityServerSetDefaultAlternativeTil.error = null views.identityServerSetDefaultAlternativeTil.error = null
identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty() views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
identityServerSetDefaultSubmit.debouncedClicks { views.identityServerSetDefaultSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer) viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer)
} }
identityServerSetDefaultAlternativeSubmit.debouncedClicks { views.identityServerSetDefaultAlternativeSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(identityServerSetDefaultAlternativeTextInput.text.toString())) viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(views.identityServerSetDefaultAlternativeTextInput.text.toString()))
} }
viewModel.observeViewEvents { viewModel.observeViewEvents {
@ -147,7 +148,7 @@ class SetIdentityServerFragment @Inject constructor(
.show() .show()
} else { } else {
// Display the error inlined // Display the error inlined
identityServerSetDefaultAlternativeTil.error = message views.identityServerSetDefaultAlternativeTil.error = message
} }
} }

View File

@ -30,6 +30,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.extensions.replaceFragment
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.ActivityRoomDetailBinding
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.room.RequireActiveMembershipAction import im.vector.app.features.room.RequireActiveMembershipAction
import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewEvents
@ -42,12 +43,14 @@ import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewState
import javax.inject.Inject import javax.inject.Inject
class RoomDetailActivity : class RoomDetailActivity :
VectorBaseActivity(), VectorBaseActivity<ActivityRoomDetailBinding>(),
ToolbarConfigurable, ToolbarConfigurable,
RequireActiveMembershipViewModel.Factory, RequireActiveMembershipViewModel.Factory,
RoomWidgetPermissionViewModel.Factory { RoomWidgetPermissionViewModel.Factory {
override fun getLayoutRes() = R.layout.activity_room_detail override fun getBinding(): ActivityRoomDetailBinding {
return ActivityRoomDetailBinding.inflate(layoutInflater)
}
private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel() private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel()
@ -76,7 +79,7 @@ class RoomDetailActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
waitingView = waiting_view waitingView = views.waitingView.waitingView
val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) {
RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!) RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!)
} else { } else {
@ -106,7 +109,7 @@ class RoomDetailActivity :
is RequireActiveMembershipViewEvents.RoomLeft -> handleRoomLeft(it) is RequireActiveMembershipViewEvents.RoomLeft -> handleRoomLeft(it)
} }
} }
drawerLayout.addDrawerListener(drawerListener) views.drawerLayout.addDrawerListener(drawerListener)
} }
private fun handleRoomLeft(roomLeft: RequireActiveMembershipViewEvents.RoomLeft) { private fun handleRoomLeft(roomLeft: RequireActiveMembershipViewEvents.RoomLeft) {
@ -117,7 +120,7 @@ class RoomDetailActivity :
} }
private fun switchToRoom(switchToRoom: RoomDetailSharedAction.SwitchToRoom) { private fun switchToRoom(switchToRoom: RoomDetailSharedAction.SwitchToRoom) {
drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
// Do not replace the Fragment if it's the same roomId // Do not replace the Fragment if it's the same roomId
if (currentRoomId != switchToRoom.roomId) { if (currentRoomId != switchToRoom.roomId) {
currentRoomId = switchToRoom.roomId currentRoomId = switchToRoom.roomId
@ -127,7 +130,7 @@ class RoomDetailActivity :
} }
override fun onDestroy() { override fun onDestroy() {
drawerLayout.removeDrawerListener(drawerListener) views.drawerLayout.removeDrawerListener(drawerListener)
super.onDestroy() super.onDestroy()
} }
@ -139,7 +142,7 @@ class RoomDetailActivity :
override fun onDrawerStateChanged(newState: Int) { override fun onDrawerStateChanged(newState: Int) {
hideKeyboard() hideKeyboard()
if (!drawerLayout.isDrawerOpen(GravityCompat.START) && newState == DrawerLayout.STATE_DRAGGING) { if (!views.drawerLayout.isDrawerOpen(GravityCompat.START) && newState == DrawerLayout.STATE_DRAGGING) {
// User is starting to open the drawer, scroll the list to top // User is starting to open the drawer, scroll the list to top
scrollBreadcrumbsToTop() scrollBreadcrumbsToTop()
} }
@ -152,8 +155,8 @@ class RoomDetailActivity :
} }
override fun onBackPressed() { override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) { if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
} else { } else {
super.onBackPressed() super.onBackPressed()
} }

View File

@ -77,13 +77,13 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
} }
private fun setupViews() { private fun setupViews() {
loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() } views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() }
loginGenericTextInputFormSubmit.setOnClickListener { submit() } views.loginGenericTextInputFormSubmit.setOnClickListener { submit() }
} }
private fun setupAutoFill() { private fun setupAutoFill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
loginGenericTextInputFormTextInput.setAutofillHints( views.loginGenericTextInputFormTextInput.setAutofillHints(
when (params.mode) { when (params.mode) {
TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS
TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER
@ -94,9 +94,9 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
} }
private fun setupTil() { private fun setupTil() {
loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .subscribe {
loginGenericTextInputFormTil.error = null views.loginGenericTextInputFormTil.error = null
} }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
@ -104,35 +104,35 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
private fun setupUi() { private fun setupUi() {
when (params.mode) { when (params.mode) {
TextInputFormFragmentMode.SetEmail -> { TextInputFormFragmentMode.SetEmail -> {
loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title)
loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice) views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice)
loginGenericTextInputFormNotice2.setTextOrHide(null) views.loginGenericTextInputFormNotice2.setTextOrHide(null)
loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint) getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint)
loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
loginGenericTextInputFormOtherButton.isVisible = false views.loginGenericTextInputFormOtherButton.isVisible = false
loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit)
} }
TextInputFormFragmentMode.SetMsisdn -> { TextInputFormFragmentMode.SetMsisdn -> {
loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title)
loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice) views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice)
loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2)) views.loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2))
loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint) getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint)
loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE
loginGenericTextInputFormOtherButton.isVisible = false views.loginGenericTextInputFormOtherButton.isVisible = false
loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit) views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit)
} }
TextInputFormFragmentMode.ConfirmMsisdn -> { TextInputFormFragmentMode.ConfirmMsisdn -> {
loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title) views.loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title)
loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra) views.loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra)
loginGenericTextInputFormNotice2.setTextOrHide(null) views.loginGenericTextInputFormNotice2.setTextOrHide(null)
loginGenericTextInputFormTil.hint = views.loginGenericTextInputFormTil.hint =
getString(R.string.login_msisdn_confirm_hint) getString(R.string.login_msisdn_confirm_hint)
loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER
loginGenericTextInputFormOtherButton.isVisible = true views.loginGenericTextInputFormOtherButton.isVisible = true
loginGenericTextInputFormOtherButton.text = getString(R.string.login_msisdn_confirm_send_again) views.loginGenericTextInputFormOtherButton.text = getString(R.string.login_msisdn_confirm_send_again)
loginGenericTextInputFormSubmit.text = getString(R.string.login_msisdn_confirm_submit) views.loginGenericTextInputFormSubmit.text = getString(R.string.login_msisdn_confirm_submit)
} }
} }
} }
@ -150,7 +150,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
private fun submit() { private fun submit() {
cleanupUi() cleanupUi()
val text = loginGenericTextInputFormTextInput.text.toString() val text = views.loginGenericTextInputFormTextInput.text.toString()
if (text.isEmpty()) { if (text.isEmpty()) {
// Perform dummy action // Perform dummy action
@ -173,8 +173,8 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
} }
private fun cleanupUi() { private fun cleanupUi() {
loginGenericTextInputFormSubmit.hideKeyboard() views.loginGenericTextInputFormSubmit.hideKeyboard()
loginGenericTextInputFormSubmit.error = null views.loginGenericTextInputFormSubmit.error = null
} }
private fun getCountryCodeOrShowError(text: String): String? { private fun getCountryCodeOrShowError(text: String): String? {
@ -184,10 +184,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
val phoneNumber = PhoneNumberUtil.getInstance().parse(text, null) val phoneNumber = PhoneNumberUtil.getInstance().parse(text, null)
return PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode) return PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode)
} catch (e: NumberParseException) { } catch (e: NumberParseException) {
loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_other) views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_other)
} }
} else { } else {
loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_not_international) views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_not_international)
} }
// Error // Error
@ -195,10 +195,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
} }
private fun setupSubmitButton() { private fun setupSubmitButton() {
loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormSubmit.isEnabled = false
loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .subscribe {
loginGenericTextInputFormSubmit.isEnabled = isInputValid(it) views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it)
} }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
@ -228,7 +228,7 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
// This is normal use case, we go to the mail waiting screen // This is normal use case, we go to the mail waiting screen
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(loginViewModel.currentThreePid ?: ""))) loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(loginViewModel.currentThreePid ?: "")))
} else { } else {
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
} }
} }
TextInputFormFragmentMode.SetMsisdn -> { TextInputFormFragmentMode.SetMsisdn -> {
@ -236,19 +236,19 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
// This is normal use case, we go to the enter code screen // This is normal use case, we go to the enter code screen
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: ""))) loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: "")))
} else { } else {
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
} }
} }
TextInputFormFragmentMode.ConfirmMsisdn -> { TextInputFormFragmentMode.ConfirmMsisdn -> {
when { when {
throwable is Failure.SuccessError -> throwable is Failure.SuccessError ->
// The entered code is not correct // The entered code is not correct
loginGenericTextInputFormTil.error = getString(R.string.login_validation_code_is_not_correct) views.loginGenericTextInputFormTil.error = getString(R.string.login_validation_code_is_not_correct)
throwable.is401() -> throwable.is401() ->
// It can happen if user request again the 3pid // It can happen if user request again the 3pid
Unit Unit
else -> else ->
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
} }
} }
} }

View File

@ -60,24 +60,24 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
} }
private fun setupUi(state: LoginViewState) { private fun setupUi(state: LoginViewState) {
resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrl.toReducedUrl()) views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrl.toReducedUrl())
} }
private fun setupSubmitButton() { private fun setupSubmitButton() {
resetPasswordSubmit.setOnClickListener { submit() } views.resetPasswordSubmit.setOnClickListener { submit() }
Observable Observable
.combineLatest( .combineLatest(
resetPasswordEmail.textChanges().map { it.isEmail() }, views.resetPasswordEmail.textChanges().map { it.isEmail() },
passwordField.textChanges().map { it.isNotEmpty() }, views.passwordField.textChanges().map { it.isNotEmpty() },
BiFunction<Boolean, Boolean, Boolean> { isEmail, isPasswordNotEmpty -> BiFunction<Boolean, Boolean, Boolean> { isEmail, isPasswordNotEmpty ->
isEmail && isPasswordNotEmpty isEmail && isPasswordNotEmpty
} }
) )
.subscribeBy { .subscribeBy {
resetPasswordEmailTil.error = null views.resetPasswordEmailTil.error = null
passwordFieldTil.error = null views.passwordFieldTil.error = null
resetPasswordSubmit.isEnabled = it views.resetPasswordSubmit.isEnabled = it
} }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
@ -102,22 +102,22 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
} }
private fun doSubmit() { private fun doSubmit() {
val email = resetPasswordEmail.text.toString() val email = views.resetPasswordEmail.text.toString()
val password = passwordField.text.toString() val password = views.passwordField.text.toString()
loginViewModel.handle(LoginAction.ResetPassword(email, password)) loginViewModel.handle(LoginAction.ResetPassword(email, password))
} }
private fun cleanupUi() { private fun cleanupUi() {
resetPasswordSubmit.hideKeyboard() views.resetPasswordSubmit.hideKeyboard()
resetPasswordEmailTil.error = null views.resetPasswordEmailTil.error = null
passwordFieldTil.error = null views.passwordFieldTil.error = null
} }
private fun setupPasswordReveal() { private fun setupPasswordReveal() {
passwordShown = false passwordShown = false
passwordReveal.setOnClickListener { views.passwordReveal.setOnClickListener {
passwordShown = !passwordShown passwordShown = !passwordShown
renderPasswordField() renderPasswordField()
@ -127,14 +127,14 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
} }
private fun renderPasswordField() { private fun renderPasswordField() {
passwordField.showPassword(passwordShown) views.passwordField.showPassword(passwordShown)
if (passwordShown) { if (passwordShown) {
passwordReveal.setImageResource(R.drawable.ic_eye_closed) views.passwordReveal.setImageResource(R.drawable.ic_eye_closed)
passwordReveal.contentDescription = getString(R.string.a11y_hide_password) views.passwordReveal.contentDescription = getString(R.string.a11y_hide_password)
} else { } else {
passwordReveal.setImageResource(R.drawable.ic_eye) views.passwordReveal.setImageResource(R.drawable.ic_eye)
passwordReveal.contentDescription = getString(R.string.a11y_show_password) views.passwordReveal.contentDescription = getString(R.string.a11y_show_password)
} }
} }
@ -152,7 +152,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
renderPasswordField() renderPasswordField()
} }
is Fail -> { is Fail -> {
resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
} }
is Success -> { is Success -> {
Unit Unit

View File

@ -56,22 +56,22 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
} }
private fun setupViews() { private fun setupViews() {
loginServerUrlFormLearnMore.setOnClickListener { learnMore() } views.loginServerUrlFormLearnMore.setOnClickListener { learnMore() }
loginServerUrlFormClearHistory.setOnClickListener { clearHistory() } views.loginServerUrlFormClearHistory.setOnClickListener { clearHistory() }
loginServerUrlFormSubmit.setOnClickListener { submit() } views.loginServerUrlFormSubmit.setOnClickListener { submit() }
} }
private fun setupHomeServerField() { private fun setupHomeServerField() {
loginServerUrlFormHomeServerUrl.textChanges() views.loginServerUrlFormHomeServerUrl.textChanges()
.subscribe { .subscribe {
loginServerUrlFormHomeServerUrlTil.error = null views.loginServerUrlFormHomeServerUrlTil.error = null
loginServerUrlFormSubmit.isEnabled = it.isNotBlank() views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
loginServerUrlFormHomeServerUrl.dismissDropDown() views.loginServerUrlFormHomeServerUrl.dismissDropDown()
submit() submit()
return@setOnEditorActionListener true return@setOnEditorActionListener true
} }
@ -82,29 +82,29 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
private fun setupUi(state: LoginViewState) { private fun setupUi(state: LoginViewState) {
when (state.serverType) { when (state.serverType) {
ServerType.EMS -> { ServerType.EMS -> {
loginServerUrlFormIcon.isVisible = true views.loginServerUrlFormIcon.isVisible = true
loginServerUrlFormTitle.text = getString(R.string.login_connect_to_modular) views.loginServerUrlFormTitle.text = getString(R.string.login_connect_to_modular)
loginServerUrlFormText.text = getString(R.string.login_server_url_form_modular_text) views.loginServerUrlFormText.text = getString(R.string.login_server_url_form_modular_text)
loginServerUrlFormLearnMore.isVisible = true views.loginServerUrlFormLearnMore.isVisible = true
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint) views.loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint)
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice) views.loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
} }
else -> { else -> {
loginServerUrlFormIcon.isVisible = false views.loginServerUrlFormIcon.isVisible = false
loginServerUrlFormTitle.text = getString(R.string.login_server_other_title) views.loginServerUrlFormTitle.text = getString(R.string.login_server_other_title)
loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server) views.loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server)
loginServerUrlFormLearnMore.isVisible = false views.loginServerUrlFormLearnMore.isVisible = false
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint) views.loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint)
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice) views.loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
} }
} }
val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList() val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter( views.loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter(
requireContext(), requireContext(),
R.layout.item_completion_homeserver, R.layout.item_completion_homeserver,
completions completions
)) ))
loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU views.loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU
.takeIf { completions.isNotEmpty() } .takeIf { completions.isNotEmpty() }
?: TextInputLayout.END_ICON_NONE ?: TextInputLayout.END_ICON_NONE
} }
@ -126,26 +126,26 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
cleanupUi() cleanupUi()
// Static check of homeserver url, empty, malformed, etc. // Static check of homeserver url, empty, malformed, etc.
val serverUrl = loginServerUrlFormHomeServerUrl.text.toString().trim().ensureProtocol() val serverUrl = views.loginServerUrlFormHomeServerUrl.text.toString().trim().ensureProtocol()
when { when {
serverUrl.isBlank() -> { serverUrl.isBlank() -> {
loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server) views.loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
} }
else -> { else -> {
loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/) views.loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/)
loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl)) loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
} }
} }
} }
private fun cleanupUi() { private fun cleanupUi() {
loginServerUrlFormSubmit.hideKeyboard() views.loginServerUrlFormSubmit.hideKeyboard()
loginServerUrlFormHomeServerUrlTil.error = null views.loginServerUrlFormHomeServerUrlTil.error = null
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {
loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection views.loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection
&& throwable.ioException is UnknownHostException) { && throwable.ioException is UnknownHostException) {
// Invalid homeserver? // Invalid homeserver?
getString(R.string.login_error_homeserver_not_found) getString(R.string.login_error_homeserver_not_found)
@ -157,7 +157,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
override fun updateWithState(state: LoginViewState) { override fun updateWithState(state: LoginViewState) {
setupUi(state) setupUi(state)
loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty() views.loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty()
if (state.loginMode != LoginMode.Unknown) { if (state.loginMode != LoginMode.Unknown) {
// The home server url is valid // The home server url is valid

View File

@ -45,37 +45,37 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
} }
private fun setupViews() { private fun setupViews() {
loginSignupSigninSubmit.setOnClickListener { submit() } views.loginSignupSigninSubmit.setOnClickListener { submit() }
loginSignupSigninSignIn.setOnClickListener { signIn() } views.loginSignupSigninSignIn.setOnClickListener { signIn() }
} }
private fun setupUi(state: LoginViewState) { private fun setupUi(state: LoginViewState) {
when (state.serverType) { when (state.serverType) {
ServerType.MatrixOrg -> { ServerType.MatrixOrg -> {
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
loginSignupSigninServerIcon.isVisible = true views.loginSignupSigninServerIcon.isVisible = true
loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl()) views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text) views.loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text)
} }
ServerType.EMS -> { ServerType.EMS -> {
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services) views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
loginSignupSigninServerIcon.isVisible = true views.loginSignupSigninServerIcon.isVisible = true
loginSignupSigninTitle.text = getString(R.string.login_connect_to_modular) views.loginSignupSigninTitle.text = getString(R.string.login_connect_to_modular)
loginSignupSigninText.text = state.homeServerUrl.toReducedUrl() views.loginSignupSigninText.text = state.homeServerUrl.toReducedUrl()
} }
ServerType.Other -> { ServerType.Other -> {
loginSignupSigninServerIcon.isVisible = false views.loginSignupSigninServerIcon.isVisible = false
loginSignupSigninTitle.text = getString(R.string.login_server_other_title) views.loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl()) views.loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
} }
ServerType.Unknown -> Unit /* Should not happen */ ServerType.Unknown -> Unit /* Should not happen */
} }
when (state.loginMode) { when (state.loginMode) {
is LoginMode.SsoAndPassword -> { is LoginMode.SsoAndPassword -> {
loginSignupSigninSignInSocialLoginContainer.isVisible = true views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders() views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()
loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(id: String?) {
val url = withState(loginViewModel) { it.getSsoUrl(id) } val url = withState(loginViewModel) { it.getSsoUrl(id) }
openInCustomTab(url) openInCustomTab(url)
@ -84,8 +84,8 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
} }
else -> { else -> {
// SSO only is managed without container as well as No sso // SSO only is managed without container as well as No sso
loginSignupSigninSignInSocialLoginContainer.isVisible = false views.loginSignupSigninSignInSocialLoginContainer.isVisible = false
loginSignupSigninSocialLoginButtons.ssoIdentityProviders = null views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = null
} }
} }
} }
@ -94,12 +94,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
when (state.loginMode) { when (state.loginMode) {
is LoginMode.Sso -> { is LoginMode.Sso -> {
// change to only one button that is sign in with sso // change to only one button that is sign in with sso
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso) views.loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
loginSignupSigninSignIn.isVisible = false views.loginSignupSigninSignIn.isVisible = false
} }
else -> { else -> {
loginSignupSigninSubmit.text = getString(R.string.login_signup) views.loginSignupSigninSubmit.text = getString(R.string.login_signup)
loginSignupSigninSignIn.isVisible = true views.loginSignupSigninSignIn.isVisible = true
} }
} }
} }

View File

@ -56,9 +56,9 @@ class RoomPreviewNoPreviewFragment @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(roomPreviewNoPreviewToolbar) setupToolbar(views.roomPreviewNoPreviewToolbar)
roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback { views.roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback {
override fun onButtonClicked() { override fun onButtonClicked() {
roomPreviewViewModel.handle(RoomPreviewAction.Join) roomPreviewViewModel.handle(RoomPreviewAction.Join)
} }
@ -71,9 +71,9 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
} }
override fun invalidate() = withState(roomPreviewViewModel) { state -> override fun invalidate() = withState(roomPreviewViewModel) { state ->
TransitionManager.beginDelayedTransition(roomPreviewNoPreviewRoot) TransitionManager.beginDelayedTransition(views.roomPreviewNoPreviewRoot)
roomPreviewNoPreviewJoin.render( views.roomPreviewNoPreviewJoin.render(
when (state.roomJoinState) { when (state.roomJoinState) {
JoinState.NOT_JOINED -> ButtonStateView.State.Button JoinState.NOT_JOINED -> ButtonStateView.State.Button
JoinState.JOINING -> ButtonStateView.State.Loading JoinState.JOINING -> ButtonStateView.State.Loading
@ -83,10 +83,10 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
) )
if (state.lastError == null) { if (state.lastError == null) {
roomPreviewNoPreviewError.isVisible = false views.roomPreviewNoPreviewError.isVisible = false
} else { } else {
roomPreviewNoPreviewError.isVisible = true views.roomPreviewNoPreviewError.isVisible = true
roomPreviewNoPreviewError.text = errorFormatter.toHumanReadable(state.lastError) views.roomPreviewNoPreviewError.text = errorFormatter.toHumanReadable(state.lastError)
} }
if (state.roomJoinState == JoinState.JOINED) { if (state.roomJoinState == JoinState.JOINED) {
@ -99,37 +99,37 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
val bestName = state.roomName ?: state.roomAlias ?: state.roomId val bestName = state.roomName ?: state.roomAlias ?: state.roomId
when (state.peekingState) { when (state.peekingState) {
is Loading -> { is Loading -> {
roomPreviewPeekingProgress.isVisible = true views.roomPreviewPeekingProgress.isVisible = true
roomPreviewNoPreviewJoin.isVisible = false views.roomPreviewNoPreviewJoin.isVisible = false
} }
is Success -> { is Success -> {
roomPreviewPeekingProgress.isVisible = false views.roomPreviewPeekingProgress.isVisible = false
when (state.peekingState.invoke()) { when (state.peekingState.invoke()) {
PeekingState.FOUND -> { PeekingState.FOUND -> {
// show join buttons // show join buttons
roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewJoin.isVisible = true
renderState(bestName, state.matrixItem(), state.roomTopic) renderState(bestName, state.matrixItem(), state.roomTopic)
} }
PeekingState.NO_ACCESS -> { PeekingState.NO_ACCESS -> {
roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewJoin.isVisible = true
roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.isVisible = true
roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join) views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join)
renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic) renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic)
} }
else -> { else -> {
roomPreviewNoPreviewJoin.isVisible = false views.roomPreviewNoPreviewJoin.isVisible = false
roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.isVisible = true
roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found) views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found)
renderState(bestName, null, state.roomTopic) renderState(bestName, null, state.roomTopic)
} }
} }
} }
else -> { else -> {
// Render with initial state, no peeking // Render with initial state, no peeking
roomPreviewPeekingProgress.isVisible = false views.roomPreviewPeekingProgress.isVisible = false
roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewJoin.isVisible = true
renderState(bestName, state.matrixItem(), state.roomTopic) renderState(bestName, state.matrixItem(), state.roomTopic)
roomPreviewNoPreviewLabel.isVisible = false views.roomPreviewNoPreviewLabel.isVisible = false
} }
} }
} }
@ -137,18 +137,18 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) { private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) {
// Toolbar // Toolbar
if (matrixItem != null) { if (matrixItem != null) {
roomPreviewNoPreviewToolbarAvatar.isVisible = true views.roomPreviewNoPreviewToolbarAvatar.isVisible = true
roomPreviewNoPreviewAvatar.isVisible = true views.roomPreviewNoPreviewAvatar.isVisible = true
avatarRenderer.render(matrixItem, roomPreviewNoPreviewToolbarAvatar) avatarRenderer.render(matrixItem, views.roomPreviewNoPreviewToolbarAvatar)
avatarRenderer.render(matrixItem, roomPreviewNoPreviewAvatar) avatarRenderer.render(matrixItem, views.roomPreviewNoPreviewAvatar)
} else { } else {
roomPreviewNoPreviewToolbarAvatar.isVisible = false views.roomPreviewNoPreviewToolbarAvatar.isVisible = false
roomPreviewNoPreviewAvatar.isVisible = false views.roomPreviewNoPreviewAvatar.isVisible = false
} }
roomPreviewNoPreviewToolbarTitle.text = roomName views.roomPreviewNoPreviewToolbarTitle.text = roomName
// Screen // Screen
roomPreviewNoPreviewName.text = roomName views.roomPreviewNoPreviewName.text = roomName
roomPreviewNoPreviewTopic.setTextOrHide(topic) views.roomPreviewNoPreviewTopic.setTextOrHide(topic)
} }
} }

View File

@ -28,13 +28,14 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragment
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.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel import im.vector.app.features.room.RequireActiveMembershipViewModel
import im.vector.app.features.room.RequireActiveMembershipViewState import im.vector.app.features.room.RequireActiveMembershipViewState
import javax.inject.Inject import javax.inject.Inject
class RoomMemberProfileActivity : class RoomMemberProfileActivity :
VectorBaseActivity(), VectorBaseActivity<ActivitySimpleBinding>(),
ToolbarConfigurable, ToolbarConfigurable,
RequireActiveMembershipViewModel.Factory { RequireActiveMembershipViewModel.Factory {
@ -60,7 +61,9 @@ class RoomMemberProfileActivity :
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() {
if (isFirstCreation()) { if (isFirstCreation()) {

View File

@ -33,9 +33,9 @@ class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
rageshake = (activity as? VectorBaseActivity)?.rageShake rageshake = (activity as? VectorBaseActivity<*>)?.rageShake
rageshake?.interceptor = { rageshake?.interceptor = {
(activity as? VectorBaseActivity)?.showSnackbar(getString(R.string.rageshake_detected)) (activity as? VectorBaseActivity<*>)?.showSnackbar(getString(R.string.rageshake_detected))
} }
} }
@ -63,7 +63,7 @@ class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() {
findPreference<SeekBarPreference>(VectorPreferences.SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY)!! findPreference<SeekBarPreference>(VectorPreferences.SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY)!!
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
(activity as? VectorBaseActivity)?.let { (activity as? VectorBaseActivity<*>)?.let {
val newValueAsInt = newValue as? Int ?: return@OnPreferenceChangeListener true val newValueAsInt = newValue as? Int ?: return@OnPreferenceChangeListener true
rageshake?.setSensitivity(newValueAsInt) rageshake?.setSensitivity(newValueAsInt)

View File

@ -101,7 +101,7 @@ class SoftLogoutActivity : LoginActivity() {
MainActivity.restartApp(this, MainActivityArgs()) MainActivity.restartApp(this, MainActivityArgs())
} }
loginLoading.isVisible = softLogoutViewState.isLoading() views.loginLoading.isVisible = softLogoutViewState.isLoading()
} }
companion object { companion object {