Merge pull request #2542 from vector-im/feature/bma/view_bindings

View bindings
This commit is contained in:
Benoit Marty 2020-12-18 15:57:34 +01:00 committed by GitHub
commit 2b780a8b76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
274 changed files with 3820 additions and 3648 deletions

View file

@ -23,7 +23,7 @@ Test:
-
Other changes:
-
- Migrate to ViewBindings (#1072)
Changes in Element 1.0.13 (2020-12-18)
===================================================

View file

@ -16,7 +16,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
buildscript {
repositories {
@ -55,6 +54,10 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {

View file

@ -17,19 +17,17 @@
package im.vector.lib.attachmentviewer
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import im.vector.lib.attachmentviewer.databinding.ItemAnimatedImageAttachmentBinding
class AnimatedImageViewHolder constructor(itemView: View) :
BaseViewHolder(itemView) {
val touchImageView: ImageView = itemView.findViewById(R.id.imageView)
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
val views = ItemAnimatedImageAttachmentBinding.bind(itemView)
internal val target = DefaultImageLoaderTarget(this, this.touchImageView)
internal val target = DefaultImageLoaderTarget(this, views.imageView)
override fun onRecycled() {
super.onRecycled()
touchImageView.setImageDrawable(null)
views.imageView.setImageDrawable(null)
}
}

View file

@ -33,7 +33,8 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.transition.TransitionManager
import androidx.viewpager2.widget.ViewPager2
import kotlinx.android.synthetic.main.activity_attachment_viewer.*
import im.vector.lib.attachmentviewer.databinding.ActivityAttachmentViewerBinding
import java.lang.ref.WeakReference
import kotlin.math.abs
@ -50,12 +51,14 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private var overlayView: View? = null
set(value) {
if (value == overlayView) return
overlayView?.let { rootContainer.removeView(it) }
rootContainer.addView(value)
overlayView?.let { views.rootContainer.removeView(it) }
views.rootContainer.addView(value)
value?.updatePadding(top = topInset, bottom = bottomInset)
field = value
}
private lateinit var views: ActivityAttachmentViewerBinding
private lateinit var swipeDismissHandler: SwipeToDismissHandler
private lateinit var directionDetector: SwipeDirectionDetector
private lateinit var scaleDetector: ScaleGestureDetector
@ -95,17 +98,17 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
setContentView(R.layout.activity_attachment_viewer)
attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
setContentView(views.root)
views.attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
attachmentsAdapter = AttachmentsAdapter()
attachmentPager.adapter = attachmentsAdapter
imageTransitionView = transitionImageView
transitionImageContainer = findViewById(R.id.transitionImageContainer)
pager2 = attachmentPager
views.attachmentPager.adapter = attachmentsAdapter
imageTransitionView = views.transitionImageView
pager2 = views.attachmentPager
directionDetector = createSwipeDirectionDetector()
gestureDetector = createGestureDetector()
attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
views.attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
isImagePagerIdle = state == ViewPager2.SCROLL_STATE_IDLE
}
@ -116,12 +119,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
})
swipeDismissHandler = createSwipeToDismissHandler()
rootContainer.setOnTouchListener(swipeDismissHandler)
rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = dismissContainer.height / 4 }
views.rootContainer.setOnTouchListener(swipeDismissHandler)
views.rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = views.dismissContainer.height / 4 }
scaleDetector = createScaleGestureDetector()
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
ViewCompat.setOnApplyWindowInsetsListener(views.rootContainer) { _, insets ->
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
topInset = insets.systemWindowInsetTop
bottomInset = insets.systemWindowInsetBottom
@ -170,7 +173,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) {
wasScaled = true
// Log.v("ATTACHEMENTS", "dispatch to pager")
return attachmentPager.dispatchTouchEvent(ev)
return views.attachmentPager.dispatchTouchEvent(ev)
}
// Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}")
@ -196,16 +199,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private fun handleEventActionDown(event: MotionEvent) {
swipeDirection = null
wasScaled = false
attachmentPager.dispatchTouchEvent(event)
views.attachmentPager.dispatchTouchEvent(event)
swipeDismissHandler.onTouch(rootContainer, event)
swipeDismissHandler.onTouch(views.rootContainer, event)
isOverlayWasClicked = dispatchOverlayTouch(event)
}
private fun handleEventActionUp(event: MotionEvent) {
// wasDoubleTapped = false
swipeDismissHandler.onTouch(rootContainer, event)
attachmentPager.dispatchTouchEvent(event)
swipeDismissHandler.onTouch(views.rootContainer, event)
views.attachmentPager.dispatchTouchEvent(event)
isOverlayWasClicked = dispatchOverlayTouch(event)
}
@ -220,12 +223,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private fun toggleOverlayViewVisibility() {
if (systemUiVisibility) {
// we hide
TransitionManager.beginDelayedTransition(rootContainer)
TransitionManager.beginDelayedTransition(views.rootContainer)
hideSystemUI()
overlayView?.isVisible = false
} else {
// we show
TransitionManager.beginDelayedTransition(rootContainer)
TransitionManager.beginDelayedTransition(views.rootContainer)
showSystemUI()
overlayView?.isVisible = true
}
@ -238,11 +241,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
return when (swipeDirection) {
SwipeDirection.Up, SwipeDirection.Down -> {
if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) {
swipeDismissHandler.onTouch(rootContainer, event)
swipeDismissHandler.onTouch(views.rootContainer, event)
} else true
}
SwipeDirection.Left, SwipeDirection.Right -> {
attachmentPager.dispatchTouchEvent(event)
views.attachmentPager.dispatchTouchEvent(event)
}
else -> true
}
@ -250,8 +253,8 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private fun handleSwipeViewMove(translationY: Float, translationLimit: Int) {
val alpha = calculateTranslationAlpha(translationY, translationLimit)
backgroundView.alpha = alpha
dismissContainer.alpha = alpha
views.backgroundView.alpha = alpha
views.dismissContainer.alpha = alpha
overlayView?.alpha = alpha
}
@ -265,7 +268,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private fun createSwipeToDismissHandler()
: SwipeToDismissHandler = SwipeToDismissHandler(
swipeView = dismissContainer,
swipeView = views.dismissContainer,
shouldAnimateDismiss = { shouldAnimateDismiss() },
onDismiss = { animateClose() },
onSwipeViewMove = ::handleSwipeViewMove)

View file

@ -98,7 +98,7 @@ class AttachmentsAdapter : RecyclerView.Adapter<BaseViewHolder>() {
fun isScaled(position: Int): Boolean {
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
if (holder is ZoomableImageViewHolder) {
return holder.touchImageView.attacher.scale > 1f
return holder.views.touchImageView.attacher.scale > 1f
}
return false
}

View file

@ -44,29 +44,29 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = true
holder.views.imageLoaderProgress.isVisible = true
}
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false
holder.touchImageView.setImageDrawable(errorDrawable)
holder.views.imageLoaderProgress.isVisible = false
holder.views.imageView.setImageDrawable(errorDrawable)
}
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.touchImageView.setImageDrawable(placeholder)
holder.views.imageView.setImageDrawable(placeholder)
}
override fun onResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false
holder.views.imageLoaderProgress.isVisible = false
// Glide mess up the view size :/
holder.touchImageView.updateLayoutParams {
holder.views.imageView.updateLayoutParams {
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.MATCH_PARENT
}
holder.touchImageView.setImageDrawable(resource)
holder.views.imageView.setImageDrawable(resource)
if (resource is Animatable) {
resource.start()
}
@ -77,30 +77,30 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = true
holder.touchImageView.setImageDrawable(placeholder)
holder.views.imageLoaderProgress.isVisible = true
holder.views.touchImageView.setImageDrawable(placeholder)
}
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false
holder.touchImageView.setImageDrawable(errorDrawable)
holder.views.imageLoaderProgress.isVisible = false
holder.views.touchImageView.setImageDrawable(errorDrawable)
}
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.touchImageView.setImageDrawable(placeholder)
holder.views.touchImageView.setImageDrawable(placeholder)
}
override fun onResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false
holder.views.imageLoaderProgress.isVisible = false
// Glide mess up the view size :/
holder.touchImageView.updateLayoutParams {
holder.views.touchImageView.updateLayoutParams {
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.MATCH_PARENT
}
holder.touchImageView.setImageDrawable(resource)
holder.views.touchImageView.setImageDrawable(resource)
}
}
}

View file

@ -49,19 +49,19 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return
holder.thumbnailImage.setImageDrawable(placeholder)
holder.views.videoThumbnailImage.setImageDrawable(placeholder)
}
override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return
holder.thumbnailImage.setImageDrawable(resource)
holder.views.videoThumbnailImage.setImageDrawable(resource)
}
override fun onVideoFileLoading(uid: String) {
if (holder.boundResourceUid != uid) return
holder.thumbnailImage.isVisible = true
holder.loaderProgressBar.isVisible = true
holder.videoView.isVisible = false
holder.views.videoThumbnailImage.isVisible = true
holder.views.videoLoaderProgress.isVisible = true
holder.views.videoView.isVisible = false
}
override fun onVideoFileLoadFailed(uid: String) {
@ -82,8 +82,8 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
}
private fun arrangeForVideoReady() {
holder.thumbnailImage.isVisible = false
holder.loaderProgressBar.isVisible = false
holder.videoView.isVisible = true
holder.views.videoThumbnailImage.isVisible = false
holder.views.videoLoaderProgress.isVisible = false
holder.views.videoView.isVisible = true
}
}

View file

@ -18,11 +18,8 @@ package im.vector.lib.attachmentviewer
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.VideoView
import androidx.core.view.isVisible
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -44,13 +41,9 @@ class VideoViewHolder constructor(itemView: View) :
var eventListener: WeakReference<AttachmentEventListener>? = null
val thumbnailImage: ImageView = itemView.findViewById(R.id.videoThumbnailImage)
val videoView: VideoView = itemView.findViewById(R.id.videoView)
val loaderProgressBar: ProgressBar = itemView.findViewById(R.id.videoLoaderProgress)
val videoControlIcon: ImageView = itemView.findViewById(R.id.videoControlIcon)
val errorTextView: TextView = itemView.findViewById(R.id.videoMediaViewerErrorView)
val views = ItemVideoAttachmentBinding.bind(itemView)
internal val target = DefaultVideoLoaderTarget(this, thumbnailImage)
internal val target = DefaultVideoLoaderTarget(this, views.videoThumbnailImage)
override fun onRecycled() {
super.onRecycled()
@ -77,12 +70,12 @@ class VideoViewHolder constructor(itemView: View) :
}
override fun entersBackground() {
if (videoView.isPlaying) {
progress = videoView.currentPosition
if (views.videoView.isPlaying) {
progress = views.videoView.currentPosition
progressDisposable?.dispose()
progressDisposable = null
videoView.stopPlayback()
videoView.pause()
views.videoView.stopPlayback()
views.videoView.pause()
}
}
@ -92,9 +85,9 @@ class VideoViewHolder constructor(itemView: View) :
override fun onSelected(selected: Boolean) {
if (!selected) {
if (videoView.isPlaying) {
progress = videoView.currentPosition
videoView.stopPlayback()
if (views.videoView.isPlaying) {
progress = views.videoView.currentPosition
views.videoView.stopPlayback()
} else {
progress = 0
}
@ -109,34 +102,34 @@ class VideoViewHolder constructor(itemView: View) :
}
private fun startPlaying() {
thumbnailImage.isVisible = false
loaderProgressBar.isVisible = false
videoView.isVisible = true
views.videoThumbnailImage.isVisible = false
views.videoLoaderProgress.isVisible = false
views.videoView.isVisible = true
videoView.setOnPreparedListener {
views.videoView.setOnPreparedListener {
progressDisposable?.dispose()
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
.timeInterval()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
val duration = videoView.duration
val progress = videoView.currentPosition
val isPlaying = videoView.isPlaying
val duration = views.videoView.duration
val progress = views.videoView.currentPosition
val isPlaying = views.videoView.isPlaying
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
}
}
try {
videoView.setVideoPath(mVideoPath)
views.videoView.setVideoPath(mVideoPath)
} catch (failure: Throwable) {
// Couldn't open
Log.v(VideoViewHolder::class.java.name, "Failed to start video")
}
if (!wasPaused) {
videoView.start()
views.videoView.start()
if (progress > 0) {
videoView.seekTo(progress)
views.videoView.seekTo(progress)
}
}
}
@ -146,17 +139,17 @@ class VideoViewHolder constructor(itemView: View) :
when (commands) {
AttachmentCommands.StartVideo -> {
wasPaused = false
videoView.start()
views.videoView.start()
}
AttachmentCommands.PauseVideo -> {
wasPaused = true
videoView.pause()
views.videoView.pause()
}
is AttachmentCommands.SeekTo -> {
val duration = videoView.duration
val duration = views.videoView.duration
if (duration > 0) {
val seekDuration = duration * (commands.percentProgress / 100f)
videoView.seekTo(seekDuration.toInt())
views.videoView.seekTo(seekDuration.toInt())
}
}
}

View file

@ -17,31 +17,29 @@
package im.vector.lib.attachmentviewer
import android.view.View
import android.widget.ProgressBar
import com.github.chrisbanes.photoview.PhotoView
import im.vector.lib.attachmentviewer.databinding.ItemImageAttachmentBinding
class ZoomableImageViewHolder constructor(itemView: View) :
BaseViewHolder(itemView) {
val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView)
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
val views = ItemImageAttachmentBinding.bind(itemView)
init {
touchImageView.setAllowParentInterceptOnEdge(false)
touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
views.touchImageView.setAllowParentInterceptOnEdge(false)
views.touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
// Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor")
// It's a bit annoying but when you pitch down the scaling
// is not exactly one :/
touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
views.touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
}
touchImageView.setScale(1.0f, true)
touchImageView.setAllowParentInterceptOnEdge(true)
views.touchImageView.setScale(1.0f, true)
views.touchImageView.setAllowParentInterceptOnEdge(true)
}
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView)
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, views.touchImageView)
override fun onRecycled() {
super.onRecycled()
touchImageView.setImageDrawable(null)
views.touchImageView.setImageDrawable(null)
}
}

View file

@ -1,7 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
apply plugin: 'realm-android'
buildscript {
@ -13,10 +13,6 @@ buildscript {
}
}
androidExtensions {
experimental = true
}
android {
compileSdkVersion 29
testOptions.unitTests.includeAndroidResources = true

View file

@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth.data
import android.os.Parcelable
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true)
@Parcelize

View file

@ -20,7 +20,7 @@ import android.net.Uri
import android.os.Parcelable
import androidx.exifinterface.media.ExifInterface
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
@Parcelize

View file

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.auth.registration
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
/**
* This class represent a localized privacy policy for registration Flow.

View file

@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.attachments
import android.os.Parcelable
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
// Check the validity of some fields

View file

@ -16,7 +16,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 29

View file

@ -109,9 +109,6 @@ import retrofit2\.adapter\.rxjava\.HttpException
### This is generally not necessary, no need to reset the padding if there is no drawable
setCompoundDrawablePadding\(0\)
### Deprecated use class form SDK API 26
ButterKnife\.findById\(
# Change thread with Rx
# DISABLED
#runOnUiThread
@ -175,3 +172,6 @@ getSystemService\(Context
### Use DefaultSharedPreferences.getInstance() instead for better performance
PreferenceManager\.getDefaultSharedPreferences==2
### Use ViewBindings
# findViewById

View file

@ -3,7 +3,7 @@ package ${escapeKotlinIdentifiers(packageName)}
import android.os.Bundle
<#if createFragmentArgs>
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import com.airbnb.mvrx.args
</#if>
import android.view.View
@ -36,8 +36,8 @@ class ${fragmentClass} @Inject constructor(
}
override fun onDestroyView() {
super.onDestroyView()
// Clear your view, unsubscribe...
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->

View file

@ -3,17 +3,13 @@ import com.android.build.OutputFile
apply plugin: 'com.android.application'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
kapt {
correctErrorTypes = true
}
androidExtensions {
experimental = true
}
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 0
@ -280,6 +276,10 @@ android {
java.srcDirs += "src/sharedTest/java"
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
@ -386,10 +386,6 @@ dependencies {
implementation 'com.otaliastudios:autocomplete:1.1.0'
// Butterknife
implementation 'com.jakewharton:butterknife:10.2.0'
kapt 'com.jakewharton:butterknife-compiler:10.2.0'
// Shake detection
implementation 'com.squareup:seismic:1.0.2'

View file

@ -76,7 +76,9 @@ class UiAllScreensSanityTest {
private val uiTestBase = UiTestBase()
// Last passing: 2020-11-09
// Last passing:
// 2020-11-09
// 2020-12-16 After ViewBinding huge change
@Test
fun allScreensTest() {
// Create an account

View file

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

View file

@ -24,7 +24,6 @@ import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.content.getSystemService
import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
@ -35,16 +34,17 @@ import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
import kotlinx.android.synthetic.debug.activity_debug_menu.*
import timber.log.Timber
import javax.inject.Inject
class DebugMenuActivity : VectorBaseActivity() {
class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
override fun getLayoutRes() = R.layout.activity_debug_menu
override fun getBinding() = ActivityDebugMenuBinding.inflate(layoutInflater)
@Inject
lateinit var activeSessionHolder: ActiveSessionHolder
@ -66,24 +66,32 @@ class DebugMenuActivity : VectorBaseActivity() {
val string = buffer.toString(Charsets.ISO_8859_1)
renderQrCode(string)
setupViews()
}
private fun setupViews() {
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
views.debugShowSasEmoji.setOnClickListener { showSasEmoji() }
views.debugTestNotification.setOnClickListener { testNotification() }
views.debugTestMaterialThemeLight.setOnClickListener { testMaterialThemeLight() }
views.debugTestMaterialThemeDark.setOnClickListener { testMaterialThemeDark() }
views.debugTestCrash.setOnClickListener { testCrash() }
views.debugScanQrCode.setOnClickListener { scanQRCode() }
}
private fun renderQrCode(text: String) {
debug_qr_code.setData(text)
views.debugQrCode.setData(text)
}
@OnClick(R.id.debug_test_text_view_link)
fun testTextViewLink() {
private fun testTextViewLink() {
startActivity(Intent(this, TestLinkifyActivity::class.java))
}
@OnClick(R.id.debug_show_sas_emoji)
fun showSasEmoji() {
private fun showSasEmoji() {
startActivity(Intent(this, DebugSasEmojiActivity::class.java))
}
@OnClick(R.id.debug_test_notification)
fun testNotification() {
private fun testNotification() {
val notificationManager = getSystemService<NotificationManager>()!!
// Create channel first
@ -166,23 +174,19 @@ class DebugMenuActivity : VectorBaseActivity() {
)
}
@OnClick(R.id.debug_test_material_theme_light)
fun testMaterialThemeLight() {
private fun testMaterialThemeLight() {
startActivity(Intent(this, DebugMaterialThemeLightActivity::class.java))
}
@OnClick(R.id.debug_test_material_theme_dark)
fun testMaterialThemeDark() {
private fun testMaterialThemeDark() {
startActivity(Intent(this, DebugMaterialThemeDarkActivity::class.java))
}
@OnClick(R.id.debug_test_crash)
fun testCrash() {
private fun testCrash() {
throw RuntimeException("Application crashed from user demand")
}
@OnClick(R.id.debug_scan_qr_code)
fun scanQRCode() {
private fun scanQRCode() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
doScanQRCode()
}

View file

@ -19,28 +19,18 @@ package im.vector.app.features.debug
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import butterknife.BindView
import butterknife.ButterKnife
import im.vector.app.R
import im.vector.app.databinding.ActivityTestLinkifyBinding
import im.vector.app.databinding.ItemTestLinkifyBinding
class TestLinkifyActivity : AppCompatActivity() {
@BindView(R.id.test_linkify_content_view)
lateinit var scrollContent: LinearLayout
@BindView(R.id.test_linkify_coordinator)
lateinit var coordinatorLayout: CoordinatorLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_linkify)
ButterKnife.bind(this)
scrollContent.removeAllViews()
val views = ActivityTestLinkifyBinding.inflate(layoutInflater)
setContentView(views.root)
views.testLinkifyContentView.removeAllViews()
listOf(
"https://www.html5rocks.com/en/tutorials/webrtc/basics/ |",
@ -89,10 +79,9 @@ class TestLinkifyActivity : AppCompatActivity() {
)
.forEach { textContent ->
val item = LayoutInflater.from(this)
.inflate(R.layout.item_test_linkify, scrollContent, false)
item.findViewById<TextView>(R.id.test_linkify_auto_text)
?.apply {
.inflate(R.layout.item_test_linkify, views.testLinkifyContentView, false)
val subViews = ItemTestLinkifyBinding.bind(item)
subViews.testLinkifyAutoText.apply {
text = textContent
/* TODO Use BetterLinkMovementMethod when the other PR is merged
movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() {
@ -107,8 +96,7 @@ class TestLinkifyActivity : AppCompatActivity() {
*/
}
item.findViewById<TextView>(R.id.test_linkify_custom_text)
?.apply {
subViews.testLinkifyCustomText.apply {
text = textContent
/* TODO Use BetterLinkMovementMethod when the other PR is merged
movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() {
@ -125,7 +113,8 @@ class TestLinkifyActivity : AppCompatActivity() {
// TODO Call VectorLinkify.addLinks(text)
}
scrollContent.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

@ -18,24 +18,26 @@ package im.vector.app.features.debug.sas
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
class DebugSasEmojiActivity : AppCompatActivity() {
private lateinit var views: FragmentGenericRecyclerBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_generic_recycler)
views = FragmentGenericRecyclerBinding.inflate(layoutInflater)
setContentView(views.root)
val controller = SasEmojiController()
genericRecyclerView.configureWith(controller)
views.genericRecyclerView.configureWith(controller)
controller.setData(SasState(getAllVerificationEmojis()))
}
override fun onDestroy() {
genericRecyclerView.cleanup()
views.genericRecyclerView.cleanup()
super.onDestroy()
}
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".features.debug.DebugMenuActivity"

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/test_linkify_coordinator"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#7F70808D"

View file

@ -2,7 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/debugMaterialCoordinator"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"

View file

@ -312,11 +312,6 @@ SOFTWARE.
<br/>
Copyright (c) 2017
</li>
<li>
<b>Butterknife</b>
<br/>
Copyright 2013 Jake Wharton
</li>
<li>
<b>seismic</b>
<br/>

View file

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

View file

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

View file

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

View file

@ -20,14 +20,12 @@ import android.app.Activity
import android.content.DialogInterface
import android.text.Editable
import android.view.KeyEvent
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.DialogPromptPasswordBinding
class PromptPasswordDialog {
@ -35,21 +33,18 @@ class PromptPasswordDialog {
fun show(activity: Activity, listener: (String) -> Unit) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null)
val passwordTil = dialogLayout.findViewById<TextInputLayout>(R.id.promptPasswordTil)
val passwordEditText = dialogLayout.findViewById<TextInputEditText>(R.id.promptPassword)
val views = DialogPromptPasswordBinding.bind(dialogLayout)
val textWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
passwordTil.error = null
views.promptPasswordTil.error = null
}
}
passwordEditText.addTextChangedListener(textWatcher)
views.promptPassword.addTextChangedListener(textWatcher)
val showPassword = dialogLayout.findViewById<ImageView>(R.id.promptPasswordPasswordReveal)
showPassword.setOnClickListener {
views.promptPasswordPasswordReveal.setOnClickListener {
passwordVisible = !passwordVisible
passwordEditText.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.promptPassword.showPassword(passwordVisible)
views.promptPasswordPasswordReveal.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
AlertDialog.Builder(activity)
@ -73,10 +68,10 @@ class PromptPasswordDialog {
setOnShowListener {
getButton(AlertDialog.BUTTON_POSITIVE)
.setOnClickListener {
if (passwordEditText.text.toString().isEmpty()) {
passwordTil.error = activity.getString(R.string.error_empty_field_your_password)
if (views.promptPassword.text.toString().isEmpty()) {
views.promptPasswordTil.error = activity.getString(R.string.error_empty_field_your_password)
} else {
listener.invoke(passwordEditText.text.toString())
listener.invoke(views.promptPassword.text.toString())
dismiss()
}
}

View file

@ -16,12 +16,11 @@
package im.vector.app.core.dialogs
import android.app.Activity
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.DialogSslFingerprintBinding
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
import timber.log.Timber
import java.util.HashMap
@ -95,30 +94,27 @@ class UnrecognizedCertificateDialog @Inject constructor(
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val layout: View = inflater.inflate(R.layout.dialog_ssl_fingerprint, null)
val sslFingerprintTitle = layout.findViewById<TextView>(R.id.ssl_fingerprint_title)
sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString())
val sslFingerprint = layout.findViewById<TextView>(R.id.ssl_fingerprint)
sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr
val sslUserId = layout.findViewById<TextView>(R.id.ssl_user_id)
val layout = inflater.inflate(R.layout.dialog_ssl_fingerprint, null)
val views = DialogSslFingerprintBinding.bind(layout)
views.sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString())
views.sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr
if (userId != null) {
sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
views.sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
stringProvider.getString(R.string.username),
userId)
} else {
sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
views.sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
stringProvider.getString(R.string.hs_url),
homeServerUrl)
}
val sslExpl = layout.findViewById<TextView>(R.id.ssl_explanation)
if (existing) {
if (homeServerConnectionConfigHasFingerprints) {
sslExpl.text = stringProvider.getString(R.string.ssl_expected_existing_expl)
views.sslExplanation.text = stringProvider.getString(R.string.ssl_expected_existing_expl)
} else {
sslExpl.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl)
views.sslExplanation.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl)
}
} else {
sslExpl.text = stringProvider.getString(R.string.ssl_cert_new_account_expl)
views.sslExplanation.text = stringProvider.getString(R.string.ssl_cert_new_account_expl)
}
builder.setView(layout)
builder.setTitle(R.string.ssl_could_not_verify)

View file

@ -23,15 +23,15 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import im.vector.app.core.platform.VectorBaseActivity
fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
}
fun VectorBaseActivity.addFragment(
fun AppCompatActivity.addFragment(
frameId: Int,
fragment: Fragment,
allowStateLoss: Boolean = false
@ -39,7 +39,7 @@ fun VectorBaseActivity.addFragment(
supportFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
}
fun <T : Fragment> VectorBaseActivity.addFragment(
fun <T : Fragment> AppCompatActivity.addFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -51,7 +51,7 @@ fun <T : Fragment> VectorBaseActivity.addFragment(
}
}
fun VectorBaseActivity.replaceFragment(
fun AppCompatActivity.replaceFragment(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -60,7 +60,7 @@ fun VectorBaseActivity.replaceFragment(
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseActivity.replaceFragment(
fun <T : Fragment> AppCompatActivity.replaceFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -72,7 +72,7 @@ fun <T : Fragment> VectorBaseActivity.replaceFragment(
}
}
fun VectorBaseActivity.addFragmentToBackstack(
fun AppCompatActivity.addFragmentToBackstack(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -81,7 +81,8 @@ fun VectorBaseActivity.addFragmentToBackstack(
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseActivity.addFragmentToBackstack(frameId: Int,
fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
tag: String? = null,
@ -93,7 +94,7 @@ fun <T : Fragment> VectorBaseActivity.addFragmentToBackstack(frameId: Int,
}
}
fun VectorBaseActivity.hideKeyboard() {
fun AppCompatActivity.hideKeyboard() {
currentFocus?.hideKeyboard()
}

View file

@ -24,7 +24,6 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.selectTxtFileToWrite
import java.text.SimpleDateFormat
import java.util.Date
@ -34,7 +33,7 @@ fun Fragment.registerStartForActivityResult(onResult: (ActivityResult) -> Unit):
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
}
fun VectorBaseFragment.addFragment(
fun Fragment.addFragment(
frameId: Int,
fragment: Fragment,
allowStateLoss: Boolean = false
@ -42,7 +41,7 @@ fun VectorBaseFragment.addFragment(
parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
}
fun <T : Fragment> VectorBaseFragment.addFragment(
fun <T : Fragment> Fragment.addFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -54,7 +53,7 @@ fun <T : Fragment> VectorBaseFragment.addFragment(
}
}
fun VectorBaseFragment.replaceFragment(
fun Fragment.replaceFragment(
frameId: Int,
fragment: Fragment,
allowStateLoss: Boolean = false
@ -62,7 +61,7 @@ fun VectorBaseFragment.replaceFragment(
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment) }
}
fun <T : Fragment> VectorBaseFragment.replaceFragment(
fun <T : Fragment> Fragment.replaceFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -74,7 +73,7 @@ fun <T : Fragment> VectorBaseFragment.replaceFragment(
}
}
fun VectorBaseFragment.addFragmentToBackstack(
fun Fragment.addFragmentToBackstack(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -83,7 +82,7 @@ fun VectorBaseFragment.addFragmentToBackstack(
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(
fun <T : Fragment> Fragment.addFragmentToBackstack(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -95,7 +94,7 @@ fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(
}
}
fun VectorBaseFragment.addChildFragment(
fun Fragment.addChildFragment(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -104,7 +103,7 @@ fun VectorBaseFragment.addChildFragment(
childFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseFragment.addChildFragment(
fun <T : Fragment> Fragment.addChildFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -116,7 +115,7 @@ fun <T : Fragment> VectorBaseFragment.addChildFragment(
}
}
fun VectorBaseFragment.replaceChildFragment(
fun Fragment.replaceChildFragment(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -125,7 +124,7 @@ fun VectorBaseFragment.replaceChildFragment(
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseFragment.replaceChildFragment(
fun <T : Fragment> Fragment.replaceChildFragment(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,
@ -137,7 +136,7 @@ fun <T : Fragment> VectorBaseFragment.replaceChildFragment(
}
}
fun VectorBaseFragment.addChildFragmentToBackstack(
fun Fragment.addChildFragmentToBackstack(
frameId: Int,
fragment: Fragment,
tag: String? = null,
@ -146,7 +145,7 @@ fun VectorBaseFragment.addChildFragmentToBackstack(
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseFragment.addChildFragmentToBackstack(
fun <T : Fragment> Fragment.addChildFragmentToBackstack(
frameId: Int,
fragmentClass: Class<T>,
params: Parcelable? = null,

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.extensions
import android.os.Bundle
import android.os.Parcelable
import com.airbnb.mvrx.MvRx
fun Parcelable?.toMvRxBundle(): Bundle? {
return this?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}

View file

@ -18,14 +18,13 @@ package im.vector.app.core.platform
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import im.vector.app.R
import kotlinx.android.synthetic.main.view_button_state.view.*
import im.vector.app.databinding.ViewButtonStateBinding
class ButtonStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) {
@ -47,11 +46,15 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
// Big or Flat button
var button: Button
private val views: ViewButtonStateBinding
init {
View.inflate(context, R.layout.view_button_state, this)
inflate(context, R.layout.view_button_state, this)
views = ViewButtonStateBinding.bind(this)
layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
buttonStateRetry.setOnClickListener {
views.buttonStateRetry.setOnClickListener {
callback?.onRetryClicked()
}
@ -63,15 +66,15 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
.apply {
try {
if (getBoolean(R.styleable.ButtonStateView_bsv_use_flat_button, true)) {
button = buttonStateButtonFlat
buttonStateButtonBig.isVisible = false
button = views.buttonStateButtonFlat
views.buttonStateButtonBig.isVisible = false
} else {
button = buttonStateButtonBig
buttonStateButtonFlat.isVisible = false
button = views.buttonStateButtonBig
views.buttonStateButtonFlat.isVisible = false
}
button.text = getString(R.styleable.ButtonStateView_bsv_button_text)
buttonStateLoaded.setImageDrawable(getDrawable(R.styleable.ButtonStateView_bsv_loaded_image_src))
views.buttonStateLoaded.setImageDrawable(getDrawable(R.styleable.ButtonStateView_bsv_loaded_image_src))
} finally {
recycle()
}
@ -90,8 +93,8 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
button.isInvisible = true
}
buttonStateLoading.isVisible = newState == State.Loading
buttonStateLoaded.isVisible = newState == State.Loaded
buttonStateRetry.isVisible = newState == State.Error
views.buttonStateLoading.isVisible = newState == State.Loading
views.buttonStateLoaded.isVisible = newState == State.Loaded
views.buttonStateRetry.isVisible = newState == State.Error
}
}

View file

@ -15,37 +15,25 @@
*/
package im.vector.app.core.platform
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.core.view.isGone
import androidx.core.view.isVisible
import butterknife.BindView
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.hideKeyboard
import kotlinx.android.synthetic.main.activity.*
import im.vector.app.databinding.ActivityBinding
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
/**
* Simple activity with a toolbar, a waiting overlay, and a fragment container and a session.
*/
abstract class SimpleFragmentActivity : VectorBaseActivity() {
abstract class SimpleFragmentActivity : VectorBaseActivity<ActivityBinding>() {
override fun getLayoutRes() = R.layout.activity
final override fun getBinding() = ActivityBinding.inflate(layoutInflater)
@BindView(R.id.waiting_view_status_circular_progress)
lateinit var waitingCircularProgress: View
final override fun getCoordinatorLayout() = views.coordinatorLayout
@BindView(R.id.waiting_view_status_text)
lateinit var waitingStatusText: TextView
@BindView(R.id.waiting_view_status_horizontal_progress)
lateinit var waitingHorizontalProgress: ProgressBar
@Inject lateinit var session: Session
lateinit var session: Session
@CallSuper
override fun injectWith(injector: ScreenComponent) {
@ -53,8 +41,8 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
}
override fun initUiAndData() {
configureToolbar(toolbar)
waitingView = findViewById(R.id.waiting_view)
configureToolbar(views.toolbar)
waitingView = views.waitingView.waitingView
}
/**
@ -63,21 +51,21 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
*/
fun updateWaitingView(data: WaitingViewData?) {
data?.let {
waitingStatusText.text = data.message
views.waitingView.waitingStatusText.text = data.message
if (data.progress != null && data.progressTotal != null) {
waitingHorizontalProgress.isIndeterminate = false
waitingHorizontalProgress.progress = data.progress
waitingHorizontalProgress.max = data.progressTotal
waitingHorizontalProgress.isVisible = true
waitingCircularProgress.isVisible = false
views.waitingView.waitingHorizontalProgress.isIndeterminate = false
views.waitingView.waitingHorizontalProgress.progress = data.progress
views.waitingView.waitingHorizontalProgress.max = data.progressTotal
views.waitingView.waitingHorizontalProgress.isVisible = true
views.waitingView.waitingCircularProgress.isVisible = false
} else if (data.isIndeterminate) {
waitingHorizontalProgress.isIndeterminate = true
waitingHorizontalProgress.isVisible = true
waitingCircularProgress.isVisible = false
views.waitingView.waitingHorizontalProgress.isIndeterminate = true
views.waitingView.waitingHorizontalProgress.isVisible = true
views.waitingView.waitingCircularProgress.isVisible = false
} else {
waitingHorizontalProgress.isVisible = false
waitingCircularProgress.isVisible = true
views.waitingView.waitingHorizontalProgress.isVisible = false
views.waitingView.waitingCircularProgress.isVisible = true
}
showWaitingView()
@ -86,17 +74,17 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
}
}
override fun showWaitingView() {
override fun showWaitingView(text: String?) {
hideKeyboard()
waitingStatusText.isGone = waitingStatusText.text.isNullOrBlank()
super.showWaitingView()
views.waitingView.waitingStatusText.isGone = views.waitingView.waitingStatusText.text.isNullOrBlank()
super.showWaitingView(text)
}
override fun hideWaitingView() {
waitingStatusText.text = null
waitingStatusText.isGone = true
waitingHorizontalProgress.progress = 0
waitingHorizontalProgress.isVisible = false
views.waitingView.waitingStatusText.text = null
views.waitingView.waitingStatusText.isGone = true
views.waitingView.waitingHorizontalProgress.progress = 0
views.waitingView.waitingHorizontalProgress.isVisible = false
super.hideWaitingView()
}

View file

@ -24,7 +24,7 @@ import android.widget.FrameLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.updateConstraintSet
import kotlinx.android.synthetic.main.view_state.view.*
import im.vector.app.databinding.ViewStateBinding
class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) {
@ -42,6 +42,8 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
data class Error(val message: CharSequence? = null) : State()
}
private val views: ViewStateBinding
var eventCallback: EventCallback? = null
var contentView: View? = null
@ -58,33 +60,34 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
}
init {
View.inflate(context, R.layout.view_state, this)
inflate(context, R.layout.view_state, this)
views = ViewStateBinding.bind(this)
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
errorRetryView.setOnClickListener {
views.errorRetryView.setOnClickListener {
eventCallback?.onRetryClicked()
}
state = State.Content
}
private fun update(newState: State) {
progressBar.isVisible = newState is State.Loading
errorView.isVisible = newState is State.Error
emptyView.isVisible = newState is State.Empty
views.progressBar.isVisible = newState is State.Loading
views.errorView.isVisible = newState is State.Error
views.emptyView.isVisible = newState is State.Empty
contentView?.isVisible = newState is State.Content
when (newState) {
is State.Content -> Unit
is State.Loading -> Unit
is State.Empty -> {
emptyImageView.setImageDrawable(newState.image)
emptyView.updateConstraintSet {
views.emptyImageView.setImageDrawable(newState.image)
views.emptyView.updateConstraintSet {
it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f)
}
emptyMessageView.text = newState.message
emptyTitleView.text = newState.title
views.emptyMessageView.text = newState.message
views.emptyTitleView.text = newState.title
}
is State.Error -> {
errorMessageView.text = newState.message
views.errorMessageView.text = newState.message
}
}
}

View file

@ -20,16 +20,15 @@ import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
import androidx.annotation.CallSuper
import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
@ -40,10 +39,7 @@ import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.MvRx
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar
import im.vector.app.BuildConfig
@ -61,6 +57,7 @@ import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
@ -83,20 +80,18 @@ import im.vector.app.receivers.DebugReceiver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber
import kotlin.system.measureTimeMillis
abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
/* ==========================================================================================
* UI
* View
* ========================================================================================== */
@Nullable
@JvmField
@BindView(R.id.vector_coordinator_layout)
var coordinatorLayout: CoordinatorLayout? = null
protected lateinit var views: VB
/* ==========================================================================================
* View model
@ -138,8 +133,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
// Filter for multiple invalid token error
private var mainActivityStarted = false
private var unBinder: Unbinder? = null
private var savedInstanceState: Bundle? = null
// For debug only
@ -176,6 +169,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
uiDisposables.add(this)
}
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("onCreate Activity ${javaClass.simpleName}")
val vectorComponent = getVectorComponent()
@ -223,11 +217,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
// Hack for font size
applyFontSize()
if (getLayoutRes() != -1) {
setContentView(getLayoutRes())
}
unBinder = ButterKnife.bind(this)
views = getBinding()
setContentView(views.root)
this.savedInstanceState = savedInstanceState
@ -306,8 +297,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
override fun onDestroy() {
super.onDestroy()
Timber.i("onDestroy Activity ${javaClass.simpleName}")
unBinder?.unbind()
unBinder = null
uiDisposables.dispose()
}
@ -467,7 +456,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
private fun recursivelyDispatchOnBackPressed(fm: FragmentManager, fromToolbar: Boolean): Boolean {
val reverseOrder = fm.fragments.filterIsInstance<VectorBaseFragment>().reversed()
val reverseOrder = fm.fragments.filterIsInstance<VectorBaseFragment<*>>().reversed()
for (f in reverseOrder) {
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager, fromToolbar)
if (handledByChildFragments) {
@ -513,10 +502,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
}
fun Parcelable?.toMvRxBundle(): Bundle? {
return this?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}
// ==============================================================================================
// Handle loading view (also called waiting view or spinner view)
// ==============================================================================================
@ -537,10 +522,13 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
fun isWaitingViewVisible() = waitingView?.isVisible == true
/**
* Show the waiting view
* Show the waiting view, and set text if not null.
*/
open fun showWaitingView() {
open fun showWaitingView(text: String? = null) {
waitingView?.isVisible = true
if (text != null) {
waitingView?.findViewById<TextView>(R.id.waitingStatusText)?.setTextOrHide(text)
}
}
/**
@ -554,8 +542,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
* OPEN METHODS
* ========================================================================================== */
@LayoutRes
open fun getLayoutRes() = -1
abstract fun getBinding(): VB
open fun displayInFullscreen() = false
@ -582,13 +569,13 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
* ========================================================================================== */
fun showSnackbar(message: String) {
coordinatorLayout?.let {
getCoordinatorLayout()?.let {
Snackbar.make(it, message, Snackbar.LENGTH_SHORT).show()
}
}
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
coordinatorLayout?.let {
getCoordinatorLayout()?.let {
Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
withActionTitle?.let {
setAction(withActionTitle, { action?.invoke() })
@ -597,6 +584,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
}
open fun getCoordinatorLayout(): CoordinatorLayout? = null
/* ==========================================================================================
* User Consent
* ========================================================================================== */

View file

@ -25,10 +25,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.lifecycle.ViewModelProvider
import butterknife.ButterKnife
import butterknife.Unbinder
import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxView
import com.airbnb.mvrx.MvRxViewId
@ -48,7 +46,7 @@ import java.util.concurrent.TimeUnit
/**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
*/
abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
abstract class VectorBaseBottomSheetDialogFragment<VB: ViewBinding> : BottomSheetDialogFragment(), MvRxView {
private val mvrxViewIdProperty = MvRxViewId()
final override val mvrxViewId: String by mvrxViewIdProperty
@ -58,10 +56,13 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
* View
* ========================================================================================== */
@LayoutRes
abstract fun getLayoutResId(): Int
private var _binding: VB? = null
private var unBinder: Unbinder? = null
// This property is only valid between onCreateView and onDestroyView.
protected val views: VB
get() = _binding!!
abstract fun getBinding(inflater: LayoutInflater, container: ViewGroup?): VB
/* ==========================================================================================
* View model
@ -81,8 +82,8 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
private var bottomSheetBehavior: BottomSheetBehavior<FrameLayout>? = null
val vectorBaseActivity: VectorBaseActivity by lazy {
activity as VectorBaseActivity
val vectorBaseActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*>
}
open val showExpanded = false
@ -106,17 +107,14 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(getLayoutResId(), container, false)
unBinder = ButterKnife.bind(this, view)
return view
_binding = getBinding(inflater, container)
return views.root
}
@CallSuper
override fun onDestroyView() {
super.onDestroyView()
unBinder?.unbind()
unBinder = null
uiDisposables.clear()
super.onDestroyView()
}
@CallSuper

View file

@ -28,15 +28,12 @@ import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider
import butterknife.ButterKnife
import butterknife.Unbinder
import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding3.view.clicks
@ -46,20 +43,19 @@ import im.vector.app.core.di.HasScreenInjector
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.navigation.Navigator
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
import java.util.concurrent.TimeUnit
abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScreenInjector {
// Butterknife unbinder
private var mUnBinder: Unbinder? = null
protected val vectorBaseActivity: VectorBaseActivity by lazy {
activity as VectorBaseActivity
protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*>
}
/* ==========================================================================================
@ -86,6 +82,16 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
protected val fragmentViewModelProvider
get() = ViewModelProvider(this, viewModelFactory)
/* ==========================================================================================
* Views
* ========================================================================================== */
private var _binding: VB? = null
// This property is only valid between onCreateView and onDestroyView.
protected val views: VB
get() = _binding!!
/* ==========================================================================================
* Life cycle
* ========================================================================================== */
@ -110,11 +116,11 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Timber.i("onCreateView Fragment ${javaClass.simpleName}")
return inflater.inflate(getLayoutResId(), container, false)
_binding = getBinding(inflater, container)
return views.root
}
@LayoutRes
abstract fun getLayoutResId(): Int
abstract fun getBinding(inflater: LayoutInflater, container: ViewGroup?): VB
@CallSuper
override fun onResume() {
@ -125,7 +131,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mUnBinder = ButterKnife.bind(this, view)
Timber.i("onViewCreated Fragment ${javaClass.simpleName}")
}
open fun showLoading(message: CharSequence?) {
@ -138,11 +144,10 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper
override fun onDestroyView() {
super.onDestroyView()
Timber.i("onDestroyView Fragment ${javaClass.simpleName}")
mUnBinder?.unbind()
mUnBinder = null
uiDisposables.clear()
_binding = null
super.onDestroyView()
}
override fun onDestroy() {
@ -180,10 +185,6 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
arguments = args.toMvRxBundle()
}
fun Parcelable?.toMvRxBundle(): Bundle? {
return this?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}
@MainThread
protected fun <T : Restorable> T.register(): T {
assertMainThread()
@ -192,7 +193,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
}
protected fun showErrorInSnackbar(throwable: Throwable) {
vectorBaseActivity.coordinatorLayout?.let {
vectorBaseActivity.getCoordinatorLayout()?.let {
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
.show()
}

View file

@ -1,69 +0,0 @@
/*
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.preference
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import androidx.preference.PreferenceViewHolder
import im.vector.app.R
/**
* Preference used in Room setting for Room aliases
*/
class AddressPreference : VectorPreference {
// members
private var mMainAddressIconView: ImageView? = null
private var mIsMainIconVisible = false
/**
* @return the main icon view.
*/
val mainIconView: View?
get() = mMainAddressIconView
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
init {
widgetLayoutResource = R.layout.vector_settings_address_preference
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val view = holder.itemView
mMainAddressIconView = view.findViewById(R.id.main_address_icon_view)
mMainAddressIconView!!.visibility = if (mIsMainIconVisible) View.VISIBLE else View.GONE
}
/**
* Set the main address icon visibility.
*
* @param isVisible true to display the main icon
*/
fun setMainIconVisible(isVisible: Boolean) {
mIsMainIconVisible = isVisible
mMainAddressIconView?.visibility = if (mIsMainIconVisible) View.VISIBLE else View.GONE
}
}

View file

@ -1,103 +0,0 @@
/*
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.preference
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.preference.PreferenceViewHolder
import androidx.preference.SwitchPreference
import im.vector.app.R
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.group.Group
class VectorGroupPreference : SwitchPreference {
private var mAvatarView: ImageView? = null
private var mGroup: Group? = null
private var mSession: Session? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val createdView = holder.itemView
if (mAvatarView == null) {
try {
// insert the group avatar to the left
val iconView = createdView.findViewById<ImageView>(android.R.id.icon)
var iconViewParent = iconView.parent
while (null != iconViewParent.parent) {
iconViewParent = iconViewParent.parent
}
val inflater = LayoutInflater.from(context)
val layout = inflater.inflate(R.layout.vector_settings_round_group_avatar, (iconViewParent as LinearLayout), false) as FrameLayout
mAvatarView = layout.findViewById(R.id.settings_round_group_avatar)
val params = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.CENTER
layout.layoutParams = params
iconViewParent.addView(layout, 0)
} catch (e: Exception) {
mAvatarView = null
}
}
refreshAvatar()
}
/**
* Init the group information
*
* @param group the group
* @param session the session
*/
fun setGroup(group: Group, session: Session) {
mGroup = group
mSession = session
refreshAvatar()
}
/**
* Refresh the avatar
*/
private fun refreshAvatar() {
if (null != mAvatarView && null != mSession && null != mGroup) {
// TODO
// VectorUtils.loadGroupAvatar(context, session, mAvatarView, mGroup)
}
}
}

View file

@ -17,44 +17,45 @@
package im.vector.app.core.ui.bottomsheet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetGenericListBinding
import javax.inject.Inject
/**
* Generic Bottom sheet with actions
*/
abstract class BottomSheetGeneric<STATE : BottomSheetGenericState, ACTION : BottomSheetGenericAction> :
VectorBaseBottomSheetDialogFragment(),
VectorBaseBottomSheetDialogFragment<BottomSheetGenericListBinding>(),
BottomSheetGenericController.Listener<ACTION> {
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
final override val showExpanded = true
final override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
final override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetGenericListBinding {
return BottomSheetGenericListBinding.inflate(inflater, container, false)
}
abstract fun getController(): BottomSheetGenericController<STATE, ACTION>
@CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(getController(), viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetRecyclerView.configureWith(getController(), viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
getController().listener = this
}
@CallSuper
override fun onDestroyView() {
recyclerView.cleanup()
views.bottomSheetRecyclerView.cleanup()
getController().listener = null
super.onDestroyView()
}

View file

@ -23,10 +23,10 @@ import android.text.style.ClickableSpan
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.utils.tappableMatchingText
import im.vector.app.databinding.ViewActiveConferenceViewBinding
import im.vector.app.features.home.room.detail.RoomDetailViewState
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.session.room.model.Membership
@ -48,12 +48,15 @@ class ActiveConferenceView @JvmOverloads constructor(
var callback: Callback? = null
var jitsiWidget: Widget? = null
private lateinit var views: ViewActiveConferenceViewBinding
init {
setupView()
}
private fun setupView() {
inflate(context, R.layout.view_active_conference_view, this)
views = ViewActiveConferenceViewBinding.bind(this)
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
// "voice" and "video" texts are underlined and clickable
@ -78,12 +81,12 @@ class ActiveConferenceView @JvmOverloads constructor(
}
})
findViewById<TextView>(R.id.activeConferenceInfo).apply {
views.activeConferenceInfo.apply {
text = styledText
movementMethod = LinkMovementMethod.getInstance()
}
findViewById<TextView>(R.id.deleteWidgetButton).setOnClickListener {
views.deleteWidgetButton.setOnClickListener {
jitsiWidget?.let { callback?.onDelete(it) }
}
}
@ -105,7 +108,7 @@ class ActiveConferenceView @JvmOverloads constructor(
jitsiWidget = activeConf
}
// if sent by me or if i can moderate?
findViewById<TextView>(R.id.deleteWidgetButton).isVisible = state.isAllowedToManageWidgets
views.deleteWidgetButton.isVisible = state.isAllowedToManageWidgets
} else {
isVisible = false
}

View file

@ -20,19 +20,15 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.ItemVerificationActionBinding
import im.vector.app.features.themes.ThemeUtils
class BottomSheetActionButton @JvmOverloads constructor(
@ -40,32 +36,18 @@ class BottomSheetActionButton @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
@BindView(R.id.itemVerificationActionTitle)
lateinit var actionTextView: TextView
@BindView(R.id.itemVerificationActionSubTitle)
lateinit var descriptionTextView: TextView
@BindView(R.id.itemVerificationLeftIcon)
lateinit var leftIconImageView: ImageView
@BindView(R.id.itemVerificationActionIcon)
lateinit var rightIconImageView: ImageView
@BindView(R.id.itemVerificationClickableZone)
lateinit var clickableView: View
val views : ItemVerificationActionBinding
var title: String? = null
set(value) {
field = value
actionTextView.setTextOrHide(value)
views.itemVerificationActionTitle.setTextOrHide(value)
}
var subTitle: String? = null
set(value) {
field = value
descriptionTextView.setTextOrHide(value)
views.itemVerificationActionSubTitle.setTextOrHide(value)
}
var forceStartPadding: Boolean? = null
@ -73,9 +55,9 @@ class BottomSheetActionButton @JvmOverloads constructor(
field = value
if (leftIcon == null) {
if (forceStartPadding == true) {
leftIconImageView.isInvisible = true
views.itemVerificationLeftIcon.isInvisible = true
} else {
leftIconImageView.isGone = true
views.itemVerificationLeftIcon.isGone = true
}
}
}
@ -85,38 +67,38 @@ class BottomSheetActionButton @JvmOverloads constructor(
field = value
if (value == null) {
if (forceStartPadding == true) {
leftIconImageView.isInvisible = true
views.itemVerificationLeftIcon.isInvisible = true
} else {
leftIconImageView.isGone = true
views.itemVerificationLeftIcon.isGone = true
}
leftIconImageView.setImageDrawable(null)
views.itemVerificationLeftIcon.setImageDrawable(null)
} else {
leftIconImageView.isVisible = true
leftIconImageView.setImageDrawable(value)
views.itemVerificationLeftIcon.isVisible = true
views.itemVerificationLeftIcon.setImageDrawable(value)
}
}
var rightIcon: Drawable? = null
set(value) {
field = value
rightIconImageView.setImageDrawable(value)
views.itemVerificationActionIcon.setImageDrawable(value)
}
var tint: Int? = null
set(value) {
field = value
leftIconImageView.imageTintList = value?.let { ColorStateList.valueOf(value) }
views.itemVerificationLeftIcon.imageTintList = value?.let { ColorStateList.valueOf(value) }
}
var titleTextColor: Int? = null
set(value) {
field = value
value?.let { actionTextView.setTextColor(it) }
value?.let { views.itemVerificationActionTitle.setTextColor(it) }
}
init {
inflate(context, R.layout.item_verification_action, this)
ButterKnife.bind(this)
views = ItemVerificationActionBinding.bind(this)
context.withStyledAttributes(attrs, R.styleable.BottomSheetActionButton) {
title = getString(R.styleable.BottomSheetActionButton_actionTitle) ?: ""

View file

@ -22,7 +22,7 @@ import android.view.View
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import im.vector.app.R
import kotlinx.android.synthetic.main.view_jump_to_read_marker.view.*
import im.vector.app.databinding.ViewJumpToReadMarkerBinding
class JumpToReadMarkerView @JvmOverloads constructor(
context: Context,
@ -43,11 +43,12 @@ class JumpToReadMarkerView @JvmOverloads constructor(
private fun setupView() {
inflate(context, R.layout.view_jump_to_read_marker, this)
val views = ViewJumpToReadMarkerBinding.bind(this)
setBackgroundColor(ContextCompat.getColor(context, R.color.notification_accent_color))
jumpToReadMarkerLabelView.setOnClickListener {
views.jumpToReadMarkerLabelView.setOnClickListener {
callback?.onJumpToReadMarkerClicked()
}
closeJumpToReadMarkerView.setOnClickListener {
views.closeJumpToReadMarkerView.setOnClickListener {
visibility = View.INVISIBLE
callback?.onClearReadMarkerClicked()
}

View file

@ -19,15 +19,13 @@ package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.edit
import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.databinding.ViewKeysBackupBannerBinding
import timber.log.Timber
/**
@ -40,21 +38,11 @@ class KeysBackupBanner @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener {
@BindView(R.id.view_keys_backup_banner_text_1)
lateinit var textView1: TextView
@BindView(R.id.view_keys_backup_banner_text_2)
lateinit var textView2: TextView
@BindView(R.id.view_keys_backup_banner_close_group)
lateinit var close: View
@BindView(R.id.view_keys_backup_banner_loading)
lateinit var loading: View
var delegate: Delegate? = null
private var state: State = State.Initial
private lateinit var views: ViewKeysBackupBannerBinding
init {
setupView()
DefaultSharedPreferences.getInstance(context).edit {
@ -100,8 +88,7 @@ class KeysBackupBanner @JvmOverloads constructor(
}
}
@OnClick(R.id.view_keys_backup_banner_close)
internal fun onCloseClicked() {
private fun onCloseClicked() {
state.let {
when (it) {
is State.Setup -> {
@ -133,11 +120,12 @@ class KeysBackupBanner @JvmOverloads constructor(
private fun setupView() {
inflate(context, R.layout.view_keys_backup_banner, this)
ButterKnife.bind(this)
setOnClickListener(this)
textView1.setOnClickListener(this)
textView2.setOnClickListener(this)
views = ViewKeysBackupBannerBinding.bind(this)
views.viewKeysBackupBannerText1.setOnClickListener(this)
views.viewKeysBackupBannerText2.setOnClickListener(this)
views.viewKeysBackupBannerClose.setOnClickListener { onCloseClicked() }
}
private fun renderInitial() {
@ -156,10 +144,10 @@ class KeysBackupBanner @JvmOverloads constructor(
} else {
isVisible = true
textView1.setText(R.string.secure_backup_banner_setup_line1)
textView2.isVisible = true
textView2.setText(R.string.secure_backup_banner_setup_line2)
close.isVisible = true
views.viewKeysBackupBannerText1.setText(R.string.secure_backup_banner_setup_line1)
views.viewKeysBackupBannerText2.isVisible = true
views.viewKeysBackupBannerText2.setText(R.string.secure_backup_banner_setup_line2)
views.viewKeysBackupBannerCloseGroup.isVisible = true
}
}
@ -169,10 +157,10 @@ class KeysBackupBanner @JvmOverloads constructor(
} else {
isVisible = true
textView1.setText(R.string.keys_backup_banner_recover_line1)
textView2.isVisible = true
textView2.setText(R.string.keys_backup_banner_recover_line2)
close.isVisible = true
views.viewKeysBackupBannerText1.setText(R.string.keys_backup_banner_recover_line1)
views.viewKeysBackupBannerText2.isVisible = true
views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_recover_line2)
views.viewKeysBackupBannerCloseGroup.isVisible = true
}
}
@ -182,28 +170,28 @@ class KeysBackupBanner @JvmOverloads constructor(
} else {
isVisible = true
textView1.setText(R.string.keys_backup_banner_update_line1)
textView2.isVisible = true
textView2.setText(R.string.keys_backup_banner_update_line2)
close.isVisible = true
views.viewKeysBackupBannerText1.setText(R.string.keys_backup_banner_update_line1)
views.viewKeysBackupBannerText2.isVisible = true
views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_update_line2)
views.viewKeysBackupBannerCloseGroup.isVisible = true
}
}
private fun renderBackingUp() {
isVisible = true
textView1.setText(R.string.secure_backup_banner_setup_line1)
textView2.isVisible = true
textView2.setText(R.string.keys_backup_banner_in_progress)
loading.isVisible = true
views.viewKeysBackupBannerText1.setText(R.string.secure_backup_banner_setup_line1)
views.viewKeysBackupBannerText2.isVisible = true
views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_in_progress)
views.viewKeysBackupBannerLoading.isVisible = true
}
/**
* Hide all views that are not visible in all state
*/
private fun hideAll() {
textView2.isVisible = false
close.isVisible = false
loading.isVisible = false
views.viewKeysBackupBannerText2.isVisible = false
views.viewKeysBackupBannerCloseGroup.isVisible = false
views.viewKeysBackupBannerLoading.isVisible = false
}
/**

View file

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

View file

@ -17,14 +17,11 @@ package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.IntRange
import butterknife.BindColor
import butterknife.BindView
import butterknife.ButterKnife
import androidx.core.content.ContextCompat
import im.vector.app.R
import im.vector.app.databinding.ViewPasswordStrengthBarBinding
/**
* A password strength bar custom widget
@ -41,37 +38,13 @@ class PasswordStrengthBar @JvmOverloads constructor(
defStyleAttr: Int = 0)
: LinearLayout(context, attrs, defStyleAttr) {
@BindView(R.id.password_strength_bar_1)
lateinit var bar1: View
private val views: ViewPasswordStrengthBarBinding
@BindView(R.id.password_strength_bar_2)
lateinit var bar2: View
@BindView(R.id.password_strength_bar_3)
lateinit var bar3: View
@BindView(R.id.password_strength_bar_4)
lateinit var bar4: View
@BindColor(R.color.password_strength_bar_undefined)
@JvmField
var colorBackground: Int = 0
@BindColor(R.color.password_strength_bar_weak)
@JvmField
var colorWeak: Int = 0
@BindColor(R.color.password_strength_bar_low)
@JvmField
var colorLow: Int = 0
@BindColor(R.color.password_strength_bar_ok)
@JvmField
var colorOk: Int = 0
@BindColor(R.color.password_strength_bar_strong)
@JvmField
var colorStrong: Int = 0
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 colorLow = ContextCompat.getColor(context, R.color.password_strength_bar_low)
private val colorOk = ContextCompat.getColor(context, R.color.password_strength_bar_ok)
private val colorStrong = ContextCompat.getColor(context, R.color.password_strength_bar_strong)
@IntRange(from = 0, to = 4)
var strength = 0
@ -80,43 +53,42 @@ class PasswordStrengthBar @JvmOverloads constructor(
when (newValue) {
0 -> {
bar1.setBackgroundColor(colorBackground)
bar2.setBackgroundColor(colorBackground)
bar3.setBackgroundColor(colorBackground)
bar4.setBackgroundColor(colorBackground)
views.passwordStrengthBar1.setBackgroundColor(colorBackground)
views.passwordStrengthBar2.setBackgroundColor(colorBackground)
views.passwordStrengthBar3.setBackgroundColor(colorBackground)
views.passwordStrengthBar4.setBackgroundColor(colorBackground)
}
1 -> {
bar1.setBackgroundColor(colorWeak)
bar2.setBackgroundColor(colorBackground)
bar3.setBackgroundColor(colorBackground)
bar4.setBackgroundColor(colorBackground)
views.passwordStrengthBar1.setBackgroundColor(colorWeak)
views.passwordStrengthBar2.setBackgroundColor(colorBackground)
views.passwordStrengthBar3.setBackgroundColor(colorBackground)
views.passwordStrengthBar4.setBackgroundColor(colorBackground)
}
2 -> {
bar1.setBackgroundColor(colorLow)
bar2.setBackgroundColor(colorLow)
bar3.setBackgroundColor(colorBackground)
bar4.setBackgroundColor(colorBackground)
views.passwordStrengthBar1.setBackgroundColor(colorLow)
views.passwordStrengthBar2.setBackgroundColor(colorLow)
views.passwordStrengthBar3.setBackgroundColor(colorBackground)
views.passwordStrengthBar4.setBackgroundColor(colorBackground)
}
3 -> {
bar1.setBackgroundColor(colorOk)
bar2.setBackgroundColor(colorOk)
bar3.setBackgroundColor(colorOk)
bar4.setBackgroundColor(colorBackground)
views.passwordStrengthBar1.setBackgroundColor(colorOk)
views.passwordStrengthBar2.setBackgroundColor(colorOk)
views.passwordStrengthBar3.setBackgroundColor(colorOk)
views.passwordStrengthBar4.setBackgroundColor(colorBackground)
}
4 -> {
bar1.setBackgroundColor(colorStrong)
bar2.setBackgroundColor(colorStrong)
bar3.setBackgroundColor(colorStrong)
bar4.setBackgroundColor(colorStrong)
views.passwordStrengthBar1.setBackgroundColor(colorStrong)
views.passwordStrengthBar2.setBackgroundColor(colorStrong)
views.passwordStrengthBar3.setBackgroundColor(colorStrong)
views.passwordStrengthBar4.setBackgroundColor(colorStrong)
}
}
}
init {
LayoutInflater.from(context)
.inflate(R.layout.view_password_strength_bar, this, true)
inflate(context, R.layout.view_password_strength_bar, this)
views = ViewPasswordStrengthBarBinding.bind(this)
orientation = HORIZONTAL
ButterKnife.bind(this)
strength = 0
}
}

View file

@ -23,10 +23,10 @@ import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.databinding.ViewReadReceiptsBinding
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.toMatrixItem
import kotlinx.android.synthetic.main.view_read_receipts.view.*
private const val MAX_RECEIPT_DISPLAYED = 5
private const val MAX_RECEIPT_DESCRIBED = 3
@ -37,12 +37,21 @@ class ReadReceiptsView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val receiptAvatars: List<ImageView> by lazy {
listOf(receiptAvatar1, receiptAvatar2, receiptAvatar3, receiptAvatar4, receiptAvatar5)
}
private val views : ViewReadReceiptsBinding
init {
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() {
@ -69,12 +78,12 @@ class ReadReceiptsView @JvmOverloads constructor(
.take(MAX_RECEIPT_DESCRIBED)
if (readReceipts.size > MAX_RECEIPT_DISPLAYED) {
receiptMore.visibility = View.VISIBLE
receiptMore.text = context.getString(
views.receiptMore.visibility = View.VISIBLE
views.receiptMore.text = context.getString(
R.string.x_plus, readReceipts.size - MAX_RECEIPT_DISPLAYED
)
} else {
receiptMore.visibility = View.GONE
views.receiptMore.visibility = View.GONE
}
contentDescription = when (readReceipts.size) {
1 ->

View file

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

View file

@ -30,6 +30,7 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.deleteAllFiles
import im.vector.app.databinding.FragmentLoadingBinding
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.ShortcutsHandler
import im.vector.app.features.login.LoginActivity
@ -42,7 +43,7 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.ui.UiStateRepository
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -66,7 +67,7 @@ data class MainActivityArgs(
* This Activity, when started with argument, is also doing some cleanup when user signs out,
* clears cache, is logged out, or is soft logged out
*/
class MainActivity : VectorBaseActivity(), UnlockedActivity {
class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActivity {
companion object {
private const val EXTRA_ARGS = "EXTRA_ARGS"
@ -83,6 +84,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity {
}
}
override fun getBinding() = FragmentLoadingBinding.inflate(layoutInflater)
private lateinit var args: MainActivityArgs
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager

View file

@ -30,7 +30,6 @@ import android.view.animation.AnimationSet
import android.view.animation.OvershootInterpolator
import android.view.animation.ScaleAnimation
import android.view.animation.TranslateAnimation
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.PopupWindow
@ -42,6 +41,7 @@ import im.vector.app.core.extensions.getMeasurements
import im.vector.app.core.utils.PERMISSIONS_EMPTY
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding
import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
import kotlin.math.max
@ -62,25 +62,19 @@ class AttachmentTypeSelectorView(context: Context,
private val iconColorGenerator = ColorGenerator.MATERIAL
private var galleryButton: ImageButton
private var cameraButton: ImageButton
private var fileButton: ImageButton
private var stickersButton: ImageButton
private var audioButton: ImageButton
private var contactButton: ImageButton
private val views: ViewAttachmentTypeSelectorBinding
private var anchor: View? = null
init {
val root = FrameLayout(context)
val layout = inflater.inflate(R.layout.view_attachment_type_selector, root, true)
galleryButton = layout.findViewById<ImageButton>(R.id.attachmentGalleryButton).configure(Type.GALLERY)
cameraButton = layout.findViewById<ImageButton>(R.id.attachmentCameraButton).configure(Type.CAMERA)
fileButton = layout.findViewById<ImageButton>(R.id.attachmentFileButton).configure(Type.FILE)
stickersButton = layout.findViewById<ImageButton>(R.id.attachmentStickersButton).configure(Type.STICKER)
audioButton = layout.findViewById<ImageButton>(R.id.attachmentAudioButton).configure(Type.AUDIO)
contactButton = layout.findViewById<ImageButton>(R.id.attachmentContactButton).configure(Type.CONTACT)
contentView = root
contentView = inflater.inflate(R.layout.view_attachment_type_selector, null, false)
views = ViewAttachmentTypeSelectorBinding.bind(contentView)
views.attachmentGalleryButton.configure(Type.GALLERY)
views.attachmentCameraButton.configure(Type.CAMERA)
views.attachmentFileButton.configure(Type.FILE)
views.attachmentStickersButton.configure(Type.STICKER)
views.attachmentAudioButton.configure(Type.AUDIO)
views.attachmentContactButton.configure(Type.CONTACT)
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.WRAP_CONTENT
animationStyle = 0
@ -108,12 +102,12 @@ class AttachmentTypeSelectorView(context: Context,
contentView.doOnNextLayout {
animateWindowInCircular(anchor, contentView)
}
animateButtonIn(galleryButton, ANIMATION_DURATION / 2)
animateButtonIn(cameraButton, ANIMATION_DURATION / 2)
animateButtonIn(fileButton, ANIMATION_DURATION / 4)
animateButtonIn(audioButton, ANIMATION_DURATION / 2)
animateButtonIn(contactButton, ANIMATION_DURATION / 4)
animateButtonIn(stickersButton, 0)
animateButtonIn(views.attachmentGalleryButton, ANIMATION_DURATION / 2)
animateButtonIn(views.attachmentCameraButton, ANIMATION_DURATION / 2)
animateButtonIn(views.attachmentFileButton, ANIMATION_DURATION / 4)
animateButtonIn(views.attachmentAudioButton, ANIMATION_DURATION / 2)
animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4)
animateButtonIn(views.attachmentStickersButton, 0)
}
override fun dismiss() {

View file

@ -24,10 +24,11 @@ import im.vector.app.R
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.themes.ActivityOtherThemes
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarConfigurable {
companion object {
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
@ -51,7 +52,9 @@ class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun getOtherThemes() = ActivityOtherThemes.AttachmentsPreview
override fun getLayoutRes() = R.layout.activity_simple
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
override fun initUiAndData() {
if (isFirstCreation()) {

View file

@ -21,6 +21,7 @@ import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -45,9 +46,9 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.OnSnapPositionChangeListener
import im.vector.app.core.utils.SnapOnScrollListener
import im.vector.app.core.utils.attachSnapHelperWithListener
import im.vector.app.databinding.FragmentAttachmentsPreviewBinding
import im.vector.app.features.media.createUCropWithDefaultSettings
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import java.io.File
@ -62,19 +63,21 @@ class AttachmentsPreviewFragment @Inject constructor(
private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController,
private val attachmentBigPreviewController: AttachmentBigPreviewController,
private val colorProvider: ColorProvider
) : VectorBaseFragment(), AttachmentMiniaturePreviewController.Callback {
) : VectorBaseFragment<FragmentAttachmentsPreviewBinding>(), AttachmentMiniaturePreviewController.Callback {
private val fragmentArgs: AttachmentsPreviewArgs by args()
private val viewModel: AttachmentsPreviewViewModel by fragmentViewModel()
override fun getLayoutResId() = R.layout.fragment_attachments_preview
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentAttachmentsPreviewBinding {
return FragmentAttachmentsPreviewBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
applyInsets()
setupRecyclerViews()
setupToolbar(attachmentPreviewerToolbar)
attachmentPreviewerSendButton.setOnClickListener {
setupToolbar(views.attachmentPreviewerToolbar)
views.attachmentPreviewerSendButton.setOnClickListener {
setResultAndFinish()
}
}
@ -119,10 +122,10 @@ class AttachmentsPreviewFragment @Inject constructor(
override fun getMenuRes() = R.menu.vector_attachments_preview
override fun onDestroyView() {
super.onDestroyView()
attachmentPreviewerMiniatureList.cleanup()
attachmentPreviewerBigList.cleanup()
views.attachmentPreviewerMiniatureList.cleanup()
views.attachmentPreviewerBigList.cleanup()
attachmentMiniaturePreviewController.callback = null
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->
@ -133,9 +136,9 @@ class AttachmentsPreviewFragment @Inject constructor(
} else {
attachmentMiniaturePreviewController.setData(state)
attachmentBigPreviewController.setData(state)
attachmentPreviewerBigList.scrollToPosition(state.currentAttachmentIndex)
attachmentPreviewerMiniatureList.scrollToPosition(state.currentAttachmentIndex)
attachmentPreviewerSendImageOriginalSize.text = resources.getQuantityString(R.plurals.send_images_with_original_size, state.attachments.size)
views.attachmentPreviewerBigList.scrollToPosition(state.currentAttachmentIndex)
views.attachmentPreviewerMiniatureList.scrollToPosition(state.currentAttachmentIndex)
views.attachmentPreviewerSendImageOriginalSize.text = resources.getQuantityString(R.plurals.send_images_with_original_size, state.attachments.size)
}
}
@ -146,17 +149,17 @@ class AttachmentsPreviewFragment @Inject constructor(
private fun setResultAndFinish() = withState(viewModel) {
(requireActivity() as? AttachmentsPreviewActivity)?.setResultAndFinish(
it.attachments,
attachmentPreviewerSendImageOriginalSize.isChecked
views.attachmentPreviewerSendImageOriginalSize.isChecked
)
}
private fun applyInsets() {
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
ViewCompat.setOnApplyWindowInsetsListener(attachmentPreviewerBottomContainer) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(views.attachmentPreviewerBottomContainer) { v, insets ->
v.updatePadding(bottom = insets.systemWindowInsetBottom)
insets
}
ViewCompat.setOnApplyWindowInsetsListener(attachmentPreviewerToolbar) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(views.attachmentPreviewerToolbar) { v, insets ->
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.systemWindowInsetTop
}
@ -180,13 +183,13 @@ class AttachmentsPreviewFragment @Inject constructor(
private fun setupRecyclerViews() {
attachmentMiniaturePreviewController.callback = this
attachmentPreviewerMiniatureList.let {
views.attachmentPreviewerMiniatureList.let {
it.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
it.setHasFixedSize(true)
it.adapter = attachmentMiniaturePreviewController.adapter
}
attachmentPreviewerBigList.let {
views.attachmentPreviewerBigList.let {
it.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
it.attachSnapHelperWithListener(
PagerSnapHelper(),

View file

@ -17,18 +17,23 @@
package im.vector.app.features.call
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import kotlinx.android.synthetic.main.bottom_sheet_call_controls.*
import im.vector.app.databinding.BottomSheetCallControlsBinding
import me.gujun.android.span.span
class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun getLayoutResId() = R.layout.bottom_sheet_call_controls
class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallControlsBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetCallControlsBinding {
return BottomSheetCallControlsBinding.inflate(inflater, container, false)
}
private val callViewModel: VectorCallViewModel by activityViewModel()
@ -39,16 +44,16 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
renderState(it)
}
callControlsSoundDevice.clickableView.debouncedClicks {
views.callControlsSoundDevice.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.SwitchSoundDevice)
}
callControlsSwitchCamera.clickableView.debouncedClicks {
views.callControlsSwitchCamera.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleCamera)
dismiss()
}
callControlsToggleSDHD.clickableView.debouncedClicks {
views.callControlsToggleSDHD.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleHDSD)
dismiss()
}
@ -109,30 +114,30 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
private fun renderState(state: VectorCallViewState) {
callControlsSoundDevice.title = getString(R.string.call_select_sound_device)
callControlsSoundDevice.subTitle = when (state.soundDevice) {
views.callControlsSoundDevice.title = getString(R.string.call_select_sound_device)
views.callControlsSoundDevice.subTitle = when (state.soundDevice) {
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
}
callControlsSwitchCamera.isVisible = state.isVideoCall && state.canSwitchCamera
callControlsSwitchCamera.subTitle = getString(if (state.isFrontCamera) R.string.call_camera_front else R.string.call_camera_back)
views.callControlsSwitchCamera.isVisible = state.isVideoCall && state.canSwitchCamera
views.callControlsSwitchCamera.subTitle = getString(if (state.isFrontCamera) R.string.call_camera_front else R.string.call_camera_back)
if (state.isVideoCall) {
callControlsToggleSDHD.isVisible = true
views.callControlsToggleSDHD.isVisible = true
if (state.isHD) {
callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_off)
callControlsToggleSDHD.subTitle = null
callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd_disabled)
views.callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_off)
views.callControlsToggleSDHD.subTitle = null
views.callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd_disabled)
} else {
callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_on)
callControlsToggleSDHD.subTitle = null
callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd)
views.callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_on)
views.callControlsToggleSDHD.subTitle = null
views.callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd)
}
} else {
callControlsToggleSDHD.isVisible = false
views.callControlsToggleSDHD.isVisible = false
}
}
}

View file

@ -18,16 +18,11 @@ package im.vector.app.features.call
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import im.vector.app.R
import kotlinx.android.synthetic.main.view_call_controls.view.*
import im.vector.app.databinding.ViewCallControlsBinding
import org.matrix.android.sdk.api.session.call.CallState
import org.webrtc.PeerConnection
@ -35,115 +30,100 @@ class CallControlsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val views: ViewCallControlsBinding
var interactionListener: InteractionListener? = null
@BindView(R.id.ringingControls)
lateinit var ringingControls: ViewGroup
@BindView(R.id.iv_icr_accept_call)
lateinit var ringingControlAccept: ImageView
@BindView(R.id.iv_icr_end_call)
lateinit var ringingControlDecline: ImageView
@BindView(R.id.connectedControls)
lateinit var connectedControls: ViewGroup
@BindView(R.id.iv_mute_toggle)
lateinit var muteIcon: ImageView
@BindView(R.id.iv_video_toggle)
lateinit var videoToggleIcon: ImageView
init {
ConstraintLayout.inflate(context, R.layout.view_call_controls, this)
inflate(context, R.layout.view_call_controls, this)
// layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
ButterKnife.bind(this)
views = ViewCallControlsBinding.bind(this)
views.ringingControlAccept.setOnClickListener { acceptIncomingCall() }
views.ringingControlDecline.setOnClickListener { declineIncomingCall() }
views.ivEndCall.setOnClickListener { endOngoingCall() }
views.muteIcon.setOnClickListener { toggleMute() }
views.videoToggleIcon.setOnClickListener { toggleVideo() }
views.ivLeftMiniControl.setOnClickListener { returnToChat() }
views.ivMore.setOnClickListener { moreControlOption() }
}
@OnClick(R.id.iv_icr_accept_call)
fun acceptIncomingCall() {
private fun acceptIncomingCall() {
interactionListener?.didAcceptIncomingCall()
}
@OnClick(R.id.iv_icr_end_call)
fun declineIncomingCall() {
private fun declineIncomingCall() {
interactionListener?.didDeclineIncomingCall()
}
@OnClick(R.id.iv_end_call)
fun endOngoingCall() {
private fun endOngoingCall() {
interactionListener?.didEndCall()
}
@OnClick(R.id.iv_mute_toggle)
fun toggleMute() {
private fun toggleMute() {
interactionListener?.didTapToggleMute()
}
@OnClick(R.id.iv_video_toggle)
fun toggleVideo() {
private fun toggleVideo() {
interactionListener?.didTapToggleVideo()
}
@OnClick(R.id.iv_leftMiniControl)
fun returnToChat() {
private fun returnToChat() {
interactionListener?.returnToChat()
}
@OnClick(R.id.iv_more)
fun moreControlOption() {
private fun moreControlOption() {
interactionListener?.didTapMore()
}
fun updateForState(state: VectorCallViewState) {
val callState = state.callState.invoke()
if (state.isAudioMuted) {
muteIcon.setImageResource(R.drawable.ic_microphone_off)
muteIcon.contentDescription = resources.getString(R.string.a11y_unmute_microphone)
views.muteIcon.setImageResource(R.drawable.ic_microphone_off)
views.muteIcon.contentDescription = resources.getString(R.string.a11y_unmute_microphone)
} else {
muteIcon.setImageResource(R.drawable.ic_microphone_on)
muteIcon.contentDescription = resources.getString(R.string.a11y_mute_microphone)
views.muteIcon.setImageResource(R.drawable.ic_microphone_on)
views.muteIcon.contentDescription = resources.getString(R.string.a11y_mute_microphone)
}
if (state.isVideoEnabled) {
videoToggleIcon.setImageResource(R.drawable.ic_video)
videoToggleIcon.contentDescription = resources.getString(R.string.a11y_stop_camera)
views.videoToggleIcon.setImageResource(R.drawable.ic_video)
views.videoToggleIcon.contentDescription = resources.getString(R.string.a11y_stop_camera)
} else {
videoToggleIcon.setImageResource(R.drawable.ic_video_off)
videoToggleIcon.contentDescription = resources.getString(R.string.a11y_start_camera)
views.videoToggleIcon.setImageResource(R.drawable.ic_video_off)
views.videoToggleIcon.contentDescription = resources.getString(R.string.a11y_start_camera)
}
when (callState) {
is CallState.Idle,
is CallState.Dialing,
is CallState.Answering -> {
ringingControls.isVisible = true
ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
views.ringingControls.isVisible = true
views.ringingControlAccept.isVisible = false
views.ringingControlDecline.isVisible = true
views.connectedControls.isVisible = false
}
is CallState.LocalRinging -> {
ringingControls.isVisible = true
ringingControlAccept.isVisible = true
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
views.ringingControls.isVisible = true
views.ringingControlAccept.isVisible = true
views.ringingControlDecline.isVisible = true
views.connectedControls.isVisible = false
}
is CallState.Connected -> {
if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
ringingControls.isVisible = false
connectedControls.isVisible = true
iv_video_toggle.isVisible = state.isVideoCall
views.ringingControls.isVisible = false
views.connectedControls.isVisible = true
views.videoToggleIcon.isVisible = state.isVideoCall
} else {
ringingControls.isVisible = true
ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
views.ringingControls.isVisible = true
views.ringingControlAccept.isVisible = false
views.ringingControlDecline.isVisible = true
views.connectedControls.isVisible = false
}
}
is CallState.Terminated,
null -> {
ringingControls.isVisible = false
connectedControls.isVisible = false
views.ringingControls.isVisible = false
views.connectedControls.isVisible = false
}
}
}

View file

@ -32,7 +32,6 @@ import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import butterknife.BindView
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
@ -45,12 +44,12 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.databinding.ActivityCallBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_call.*
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.EglUtils
import org.matrix.android.sdk.api.session.call.MxCallDetail
@ -58,7 +57,6 @@ import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.webrtc.EglBase
import org.webrtc.PeerConnection
import org.webrtc.RendererCommon
import org.webrtc.SurfaceViewRenderer
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -72,9 +70,9 @@ data class CallArgs(
val isVideoCall: Boolean
) : Parcelable
class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionListener {
class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallControlsView.InteractionListener {
override fun getLayoutRes() = R.layout.activity_call
override fun getBinding() = ActivityCallBinding.inflate(layoutInflater)
@Inject lateinit var avatarRenderer: AvatarRenderer
@ -90,15 +88,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
@Inject lateinit var viewModelFactory: VectorCallViewModel.Factory
@BindView(R.id.pip_video_view)
lateinit var pipRenderer: SurfaceViewRenderer
@BindView(R.id.fullscreen_video_view)
lateinit var fullscreenRenderer: SurfaceViewRenderer
@BindView(R.id.callControls)
lateinit var callControlsView: CallControlsView
private var rootEglBase: EglBase? = null
var systemUiVisibility = false
@ -158,7 +147,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
super.onCreate(savedInstanceState)
// This will need to be refined
ViewCompat.setOnApplyWindowInsetsListener(constraintLayout) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(views.constraintLayout) { v, insets ->
v.updatePadding(bottom = if (systemUiVisibility) insets.systemWindowInsetBottom else 0)
insets
}
@ -178,7 +167,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
turnScreenOnAndKeyguardOff()
}
constraintLayout.clicks()
views.constraintLayout.clicks()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { toggleUiSystemVisibility() }
@ -210,10 +199,10 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
}
override fun onDestroy() {
peerConnectionManager.detachRenderers(listOf(pipRenderer, fullscreenRenderer))
peerConnectionManager.detachRenderers(listOf(views.pipRenderer, views.fullscreenRenderer))
if (surfaceRenderersAreInitialized) {
pipRenderer.release()
fullscreenRenderer.release()
views.pipRenderer.release()
views.fullscreenRenderer.release()
}
turnScreenOffAndKeyguardOn()
super.onDestroy()
@ -228,54 +217,54 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
return
}
callControlsView.updateForState(state)
views.callControlsView.updateForState(state)
val callState = state.callState.invoke()
callConnectingProgress.isVisible = false
views.callConnectingProgress.isVisible = false
when (callState) {
is CallState.Idle,
is CallState.Dialing -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_ring)
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
views.callStatusText.setText(R.string.call_ring)
configureCallInfo(state)
}
is CallState.LocalRinging -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.text = null
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
views.callStatusText.text = null
configureCallInfo(state)
}
is CallState.Answering -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting)
callConnectingProgress.isVisible = true
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
views.callStatusText.setText(R.string.call_connecting)
views.callConnectingProgress.isVisible = true
configureCallInfo(state)
}
is CallState.Connected -> {
if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
if (callArgs.isVideoCall) {
callVideoGroup.isVisible = true
callInfoGroup.isVisible = false
pip_video_view.isVisible = !state.isVideoCaptureInError
views.callVideoGroup.isVisible = true
views.callInfoGroup.isVisible = false
views.pipRenderer.isVisible = !state.isVideoCaptureInError
} else {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
configureCallInfo(state)
callStatusText.text = null
views.callStatusText.text = null
}
} else {
// This state is not final, if you change network, new candidates will be sent
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
configureCallInfo(state)
callStatusText.setText(R.string.call_connecting)
callConnectingProgress.isVisible = true
views.callStatusText.setText(R.string.call_connecting)
views.callConnectingProgress.isVisible = true
}
// ensure all attached?
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
peerConnectionManager.attachViewRenderers(views.pipRenderer, views.fullscreenRenderer, null)
}
is CallState.Terminated -> {
finish()
@ -287,14 +276,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private fun configureCallInfo(state: VectorCallViewState) {
state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar)
participantNameText.text = it.getBestName()
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
avatarRenderer.render(it, views.otherMemberAvatar)
views.participantNameText.text = it.getBestName()
views.callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
}
}
private fun configureCallViews() {
callControlsView.interactionListener = this
views.callControlsView.interactionListener = this
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
@ -314,21 +303,24 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
}
// Init Picture in Picture renderer
pipRenderer.init(rootEglBase!!.eglBaseContext, null)
pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
views.pipRenderer.init(rootEglBase!!.eglBaseContext, null)
views.pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
// Init Full Screen renderer
fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null)
fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
views.fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null)
views.fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
pipRenderer.setZOrderMediaOverlay(true)
pipRenderer.setEnableHardwareScaler(true /* enabled */)
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
views.pipRenderer.setZOrderMediaOverlay(true)
views.pipRenderer.setEnableHardwareScaler(true /* enabled */)
views.fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer,
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() })
peerConnectionManager.attachViewRenderers(
views.pipRenderer,
views.fullscreenRenderer,
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() }
)
pipRenderer.setOnClickListener {
views.pipRenderer.setOnClickListener {
callViewModel.handle(VectorCallViewActions.ToggleCamera)
}
surfaceRenderersAreInitialized = true

View file

@ -20,7 +20,6 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import android.widget.FrameLayout
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
@ -28,11 +27,10 @@ import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.viewModel
import com.facebook.react.modules.core.PermissionListener
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_jitsi.*
import im.vector.app.databinding.ActivityJitsiBinding
import kotlinx.parcelize.Parcelize
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
import org.jitsi.meet.sdk.JitsiMeetActivityInterface
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
@ -42,7 +40,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import java.net.URL
import javax.inject.Inject
class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, JitsiMeetViewListener {
class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMeetActivityInterface, JitsiMeetViewListener {
@Parcelize
data class Args(
@ -51,7 +49,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
val enableVideo: Boolean
) : Parcelable
override fun getLayoutRes() = R.layout.activity_jitsi
override fun getBinding() = ActivityJitsiBinding.inflate(layoutInflater)
@Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory
@ -76,7 +74,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
super.initUiAndData()
jitsiMeetView = JitsiMeetView(this)
val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
jitsi_layout.addView(jitsiMeetView, params)
views.jitsiLayout.addView(jitsiMeetView, params)
jitsiMeetView?.listener = this
}
@ -84,13 +82,13 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
when (viewState.widget) {
is Fail -> finish()
is Success -> {
findViewById<View>(R.id.jitsi_progress_layout).isVisible = false
views.jitsiProgressLayout.isVisible = false
jitsiMeetView?.isVisible = true
configureJitsiView(viewState)
}
else -> {
jitsiMeetView?.isVisible = false
findViewById<View>(R.id.jitsi_progress_layout).isVisible = true
views.jitsiProgressLayout.isVisible = true
}
}
}

View file

@ -17,7 +17,9 @@
package im.vector.app.features.contactsbook
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
@ -29,12 +31,13 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentContactsBookBinding
import im.vector.app.features.userdirectory.PendingInvitee
import im.vector.app.features.userdirectory.UserListAction
import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel
import kotlinx.android.synthetic.main.fragment_contacts_book.*
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User
import java.util.concurrent.TimeUnit
@ -43,9 +46,12 @@ import javax.inject.Inject
class ContactsBookFragment @Inject constructor(
val contactsBookViewModelFactory: ContactsBookViewModel.Factory,
private val contactsBookController: ContactsBookController
) : VectorBaseFragment(), ContactsBookController.Callback {
) : VectorBaseFragment<FragmentContactsBookBinding>(), ContactsBookController.Callback {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentContactsBookBinding {
return FragmentContactsBookBinding.inflate(inflater, container, false)
}
override fun getLayoutResId() = R.layout.fragment_contacts_book
private val viewModel: UserListViewModel by activityViewModel()
// Use activityViewModel to avoid loading several times the data
@ -64,7 +70,7 @@ class ContactsBookFragment @Inject constructor(
}
private fun setupConsentView() {
phoneBookSearchForMatrixContacts.setOnClickListener {
views.phoneBookSearchForMatrixContacts.setOnClickListener {
withState(contactsBookViewModel) { state ->
AlertDialog.Builder(requireActivity())
.setTitle(R.string.identity_server_consent_dialog_title)
@ -79,7 +85,7 @@ class ContactsBookFragment @Inject constructor(
}
private fun setupOnlyBoundContactsView() {
phoneBookOnlyBoundContacts.checkedChanges()
views.phoneBookOnlyBoundContacts.checkedChanges()
.subscribe {
contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it))
}
@ -87,7 +93,7 @@ class ContactsBookFragment @Inject constructor(
}
private fun setupFilterView() {
phoneBookFilter
views.phoneBookFilter
.textChanges()
.skipInitialValue()
.debounce(300, TimeUnit.MILLISECONDS)
@ -98,25 +104,25 @@ class ContactsBookFragment @Inject constructor(
}
override fun onDestroyView() {
phoneBookRecyclerView.cleanup()
views.phoneBookRecyclerView.cleanup()
contactsBookController.callback = null
super.onDestroyView()
}
private fun setupRecyclerView() {
contactsBookController.callback = this
phoneBookRecyclerView.configureWith(contactsBookController)
views.phoneBookRecyclerView.configureWith(contactsBookController)
}
private fun setupCloseView() {
phoneBookClose.debouncedClicks {
views.phoneBookClose.debouncedClicks {
sharedActionViewModel.post(UserListSharedAction.GoBack)
}
}
override fun invalidate() = withState(contactsBookViewModel) { state ->
phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
views.phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
views.phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
contactsBookController.setData(state)
}

View file

@ -51,7 +51,7 @@ import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel
import im.vector.app.features.userdirectory.UserListViewState
import kotlinx.android.synthetic.main.activity.*
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection
@ -77,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE
views.toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel

View file

@ -16,6 +16,8 @@
package im.vector.app.features.createdirect
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import com.airbnb.mvrx.activityViewModel
import com.google.zxing.Result
@ -26,19 +28,22 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding
import im.vector.app.features.userdirectory.PendingInvitee
import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
import me.dm7.barcodescanner.zxing.ZXingScannerView
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.user.model.User
import javax.inject.Inject
class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment<FragmentQrCodeScannerBinding>(), ZXingScannerView.ResultHandler {
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
}
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) {
@ -48,14 +53,14 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
private fun startCamera() {
// Start camera on resume
scannerView.startCamera()
views.scannerView.startCamera()
}
override fun onResume() {
super.onResume()
view?.hideKeyboard()
// Register ourselves as a handler for scan results.
scannerView.setResultHandler(this)
views.scannerView.setResultHandler(this)
// Start camera on resume
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
startCamera()
@ -65,9 +70,9 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
override fun onPause() {
super.onPause()
// Unregister ourselves as a handler for scan results.
scannerView.setResultHandler(null)
views.scannerView.setResultHandler(null)
// Stop camera on pause
scannerView.stopCamera()
views.scannerView.stopCamera()
}
// Copied from https://github.com/markusfisch/BinaryEye/blob/

View file

@ -17,42 +17,37 @@ package im.vector.app.features.crypto.keysbackup.restore
import android.app.Activity
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentKeysBackupRestoreFromKeyBinding
import org.matrix.android.sdk.api.extensions.tryOrNull
import javax.inject.Inject
class KeysBackupRestoreFromKeyFragment @Inject constructor()
: VectorBaseFragment() {
: VectorBaseFragment<FragmentKeysBackupRestoreFromKeyBinding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_key
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromKeyBinding {
return FragmentKeysBackupRestoreFromKeyBinding.inflate(inflater, container, false)
}
private lateinit var viewModel: KeysBackupRestoreFromKeyViewModel
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
@BindView(R.id.keys_backup_key_enter_til)
lateinit var mKeyInputLayout: TextInputLayout
@BindView(R.id.keys_restore_key_enter_edittext)
lateinit var mKeyTextEdit: EditText
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromKeyViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
mKeyTextEdit.setText(viewModel.recoveryCode.value)
mKeyTextEdit.setOnEditorActionListener { _, actionId, _ ->
views.keyTextEdit.setText(viewModel.recoveryCode.value)
views.keyTextEdit.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onRestoreFromKey()
return@setOnEditorActionListener true
@ -60,21 +55,23 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
return@setOnEditorActionListener false
}
mKeyInputLayout.error = viewModel.recoveryCodeErrorText.value
views.keyInputLayout.error = viewModel.recoveryCodeErrorText.value
viewModel.recoveryCodeErrorText.observe(viewLifecycleOwner, Observer { newValue ->
mKeyInputLayout.error = newValue
views.keyInputLayout.error = newValue
})
views.keysRestoreButton.setOnClickListener { onRestoreFromKey() }
views.keysBackupImport.setOnClickListener { onImport() }
views.keyTextEdit.doOnTextChanged { text, _, _, _ -> onRestoreKeyTextEditChange(text) }
}
@OnTextChanged(R.id.keys_restore_key_enter_edittext)
fun onRestoreKeyTextEditChange(s: Editable?) {
private fun onRestoreKeyTextEditChange(s: CharSequence?) {
s?.toString()?.let {
viewModel.updateCode(it)
}
}
@OnClick(R.id.keys_restore_button)
fun onRestoreFromKey() {
private fun onRestoreFromKey() {
val value = viewModel.recoveryCode.value
if (value.isNullOrBlank()) {
viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message)
@ -83,8 +80,7 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
}
}
@OnClick(R.id.keys_backup_import)
fun onImport() {
private fun onImport() {
startImportTextFromFileIntent(requireContext(), textFileStartForActivityResult)
}
@ -98,8 +94,8 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
?.bufferedReader()
?.use { it.readText() }
?.let {
mKeyTextEdit.setText(it)
mKeyTextEdit.setSelection(it.length)
views.keyTextEdit.setText(it)
views.keyTextEdit.setSelection(it.length)
}
}
}

View file

@ -16,46 +16,32 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle
import android.text.Editable
import android.text.SpannableString
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.core.text.set
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupRestoreFromPassphraseBinding
import javax.inject.Inject
class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBaseFragment() {
class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBaseFragment<FragmentKeysBackupRestoreFromPassphraseBinding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_passphrase
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromPassphraseBinding {
return FragmentKeysBackupRestoreFromPassphraseBinding.inflate(inflater, container, false)
}
private lateinit var viewModel: KeysBackupRestoreFromPassphraseViewModel
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
@BindView(R.id.keys_backup_passphrase_enter_til)
lateinit var mPassphraseInputLayout: TextInputLayout
@BindView(R.id.keys_backup_passphrase_enter_edittext)
lateinit var mPassphraseTextEdit: EditText
@BindView(R.id.keys_backup_view_show_password)
lateinit var mPassphraseReveal: ImageView
@BindView(R.id.keys_backup_passphrase_help_with_link)
lateinit var helperTextWithLink: TextView
@OnClick(R.id.keys_backup_view_show_password)
fun toggleVisibilityMode() {
private fun toggleVisibilityMode() {
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
@ -66,24 +52,29 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
viewModel.passphraseErrorText.observe(viewLifecycleOwner, Observer { newValue ->
mPassphraseInputLayout.error = newValue
views.keysBackupPassphraseEnterTil.error = newValue
})
helperTextWithLink.text = spannableStringForHelperText()
views.helperTextWithLink.text = spannableStringForHelperText()
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
val shouldBeVisible = it ?: false
mPassphraseTextEdit.showPassword(shouldBeVisible)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.keysBackupPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.keysBackupViewShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
mPassphraseTextEdit.setOnEditorActionListener { _, actionId, _ ->
views.keysBackupPassphraseEnterEdittext.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onRestoreBackup()
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
views.keysBackupViewShowPassword.setOnClickListener { toggleVisibilityMode() }
views.helperTextWithLink.setOnClickListener { onUseRecoveryKey() }
views.keysBackupRestoreWithPassphraseSubmit.setOnClickListener { onRestoreBackup() }
views.keysBackupPassphraseEnterEdittext.doOnTextChanged { text, _, _, _ -> onPassphraseTextEditChange(text) }
}
private fun spannableStringForHelperText(): SpannableString {
@ -102,18 +93,15 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
return spanString
}
@OnTextChanged(R.id.keys_backup_passphrase_enter_edittext)
fun onPassphraseTextEditChange(s: Editable?) {
private fun onPassphraseTextEditChange(s: CharSequence?) {
s?.toString()?.let { viewModel.updatePassphrase(it) }
}
@OnClick(R.id.keys_backup_passphrase_help_with_link)
fun onUseRecoveryKey() {
private fun onUseRecoveryKey() {
sharedViewModel.moveToRecoverWithKey()
}
@OnClick(R.id.keys_backup_restore_with_passphrase_submit)
fun onRestoreBackup() {
private fun onRestoreBackup() {
val value = viewModel.passphrase.value
if (value.isNullOrBlank()) {
viewModel.passphraseErrorText.value = getString(R.string.passphrase_empty_error_message)

View file

@ -16,25 +16,22 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.view.ViewGroup
import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent
import im.vector.app.databinding.FragmentKeysBackupRestoreSuccessBinding
import javax.inject.Inject
class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragment() {
class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragment<FragmentKeysBackupRestoreSuccessBinding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_success
@BindView(R.id.keys_backup_restore_success)
lateinit var mSuccessText: TextView
@BindView(R.id.keys_backup_restore_success_info)
lateinit var mSuccessDetailsText: TextView
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreSuccessBinding {
return FragmentKeysBackupRestoreSuccessBinding.inflate(inflater, container, false)
}
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
@ -48,18 +45,18 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
it.totalNumberOfKeys, it.totalNumberOfKeys)
val part2 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part2,
it.successfullyNumberOfImportedKeys, it.successfullyNumberOfImportedKeys)
mSuccessDetailsText.text = String.format("%s\n%s", part1, part2)
views.successDetailsText.text = String.format("%s\n%s", part1, part2)
}
// We don't put emoji in string xml as it will crash on old devices
mSuccessText.text = context?.getString(R.string.keys_backup_restore_success_title, "🎉")
views.successText.text = context?.getString(R.string.keys_backup_restore_success_title, "🎉")
} else {
mSuccessText.text = context?.getString(R.string.keys_backup_restore_success_title_already_up_to_date)
mSuccessDetailsText.isVisible = false
views.successText.text = context?.getString(R.string.keys_backup_restore_success_title_already_up_to_date)
views.successDetailsText.isVisible = false
}
views.keysBackupSetupDoneButton.setOnClickListener { onDone() }
}
@OnClick(R.id.keys_backup_setup_done_button)
fun onDone() {
private fun onDone() {
sharedViewModel.importRoomKeysFinishWithResult.value = LiveEvent(sharedViewModel.importKeyResult!!)
}
}

View file

@ -16,7 +16,9 @@
package im.vector.app.features.crypto.keysbackup.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
@ -24,28 +26,31 @@ import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupSettingsBinding
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import kotlinx.android.synthetic.main.fragment_keys_backup_settings.*
import javax.inject.Inject
class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController)
: VectorBaseFragment(),
: VectorBaseFragment<FragmentKeysBackupSettingsBinding>(),
KeysBackupSettingsRecyclerViewController.Listener {
override fun getLayoutResId() = R.layout.fragment_keys_backup_settings
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSettingsBinding {
return FragmentKeysBackupSettingsBinding.inflate(inflater, container, false)
}
private val viewModel: KeysBackupSettingsViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
keysBackupSettingsRecyclerView.configureWith(keysBackupSettingsRecyclerViewController)
views.keysBackupSettingsRecyclerView.configureWith(keysBackupSettingsRecyclerViewController)
keysBackupSettingsRecyclerViewController.listener = this
}
override fun onDestroyView() {
keysBackupSettingsRecyclerViewController.listener = null
keysBackupSettingsRecyclerView.cleanup()
views.keysBackupSettingsRecyclerView.cleanup()
super.onDestroyView()
}

View file

@ -17,29 +17,24 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.view.ViewGroup
import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent
import im.vector.app.databinding.FragmentKeysBackupSetupStep1Binding
import javax.inject.Inject
class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment() {
class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment<FragmentKeysBackupSetupStep1Binding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step1
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep1Binding {
return FragmentKeysBackupSetupStep1Binding.inflate(inflater, container, false)
}
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@BindView(R.id.keys_backup_setup_step1_advanced)
lateinit var advancedOptionText: TextView
@BindView(R.id.keys_backup_setup_step1_manualExport)
lateinit var manualExportButton: Button
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -48,18 +43,19 @@ class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment()
viewModel.showManualExport.observe(viewLifecycleOwner, Observer {
val showOption = it ?: false
// Can't use isVisible because the kotlin compiler will crash with Back-end (JVM) Internal error: wrong code generated
advancedOptionText.visibility = if (showOption) View.VISIBLE else View.GONE
manualExportButton.visibility = if (showOption) View.VISIBLE else View.GONE
views.keysBackupSetupStep1AdvancedOptionText.visibility = if (showOption) View.VISIBLE else View.GONE
views.keysBackupSetupStep1ManualExportButton.visibility = if (showOption) View.VISIBLE else View.GONE
})
views.keysBackupSetupStep1Button.setOnClickListener { onButtonClick() }
views.keysBackupSetupStep1ManualExportButton.setOnClickListener { onManualExportClick() }
}
@OnClick(R.id.keys_backup_setup_step1_button)
fun onButtonClick() {
private fun onButtonClick() {
viewModel.navigateEvent.value = LiveEvent(KeysBackupSetupSharedViewModel.NAVIGATE_TO_STEP_2)
}
@OnClick(R.id.keys_backup_setup_step1_manualExport)
fun onManualExportClick() {
private fun onManualExportClick() {
viewModel.navigateEvent.value = LiveEvent(KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT)
}
}

View file

@ -16,64 +16,40 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.ImageView
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.OnClick
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout
import com.nulabinc.zxcvbn.Zxcvbn
import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.ui.views.PasswordStrengthBar
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
import im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment() {
class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<FragmentKeysBackupSetupStep2Binding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step2
@BindView(R.id.keys_backup_root)
lateinit var rootGroup: ViewGroup
@BindView(R.id.keys_backup_passphrase_enter_edittext)
lateinit var mPassphraseTextEdit: EditText
@BindView(R.id.keys_backup_passphrase_enter_til)
lateinit var mPassphraseInputLayout: TextInputLayout
@BindView(R.id.keys_backup_view_show_password)
lateinit var mPassphraseReveal: ImageView
@BindView(R.id.keys_backup_passphrase_confirm_edittext)
lateinit var mPassphraseConfirmTextEdit: EditText
@BindView(R.id.keys_backup_passphrase_confirm_til)
lateinit var mPassphraseConfirmInputLayout: TextInputLayout
@BindView(R.id.keys_backup_passphrase_security_progress)
lateinit var mPassphraseProgressLevel: PasswordStrengthBar
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep2Binding {
return FragmentKeysBackupSetupStep2Binding.inflate(inflater, container, false)
}
private val zxcvbn = Zxcvbn()
@OnTextChanged(R.id.keys_backup_passphrase_enter_edittext)
fun onPassphraseChanged() {
viewModel.passphrase.value = mPassphraseTextEdit.text.toString()
private fun onPassphraseChanged() {
viewModel.passphrase.value = views.keysBackupSetupStep2PassphraseEnterEdittext.text.toString()
viewModel.confirmPassphraseError.value = null
}
@OnTextChanged(R.id.keys_backup_passphrase_confirm_edittext)
fun onConfirmPassphraseChanged() {
viewModel.confirmPassphrase.value = mPassphraseConfirmTextEdit.text.toString()
private fun onConfirmPassphraseChanged() {
viewModel.confirmPassphrase.value = views.keysBackupSetupStep2PassphraseConfirmEditText.text.toString()
}
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@ -85,6 +61,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
viewModel.shouldPromptOnBack = true
bindViewToViewModel()
setupViews()
}
/* ==========================================================================================
@ -94,24 +71,24 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
private fun bindViewToViewModel() {
viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength ->
if (strength == null) {
mPassphraseProgressLevel.strength = 0
mPassphraseInputLayout.error = null
views.keysBackupSetupStep2PassphraseStrengthLevel.strength = 0
views.keysBackupSetupStep2PassphraseEnterTil.error = null
} else {
val score = strength.score
mPassphraseProgressLevel.strength = score
views.keysBackupSetupStep2PassphraseStrengthLevel.strength = score
if (score in 1..3) {
val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale)
if (warning != null) {
mPassphraseInputLayout.error = warning
views.keysBackupSetupStep2PassphraseEnterTil.error = warning
}
val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale)
if (suggestions != null) {
mPassphraseInputLayout.error = suggestions.firstOrNull()
views.keysBackupSetupStep2PassphraseEnterTil.error = suggestions.firstOrNull()
}
} else {
mPassphraseInputLayout.error = null
views.keysBackupSetupStep2PassphraseEnterTil.error = null
}
}
})
@ -129,28 +106,28 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
}
})
mPassphraseTextEdit.setText(viewModel.passphrase.value)
views.keysBackupSetupStep2PassphraseEnterEdittext.setText(viewModel.passphrase.value)
viewModel.passphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(rootGroup)
mPassphraseInputLayout.error = it
TransitionManager.beginDelayedTransition(views.keysBackupRoot)
views.keysBackupSetupStep2PassphraseEnterTil.error = it
})
mPassphraseConfirmTextEdit.setText(viewModel.confirmPassphrase.value)
views.keysBackupSetupStep2PassphraseConfirmEditText.setText(viewModel.confirmPassphrase.value)
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
val shouldBeVisible = it ?: false
mPassphraseTextEdit.showPassword(shouldBeVisible)
mPassphraseConfirmTextEdit.showPassword(shouldBeVisible)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.keysBackupSetupStep2PassphraseEnterEdittext.showPassword(shouldBeVisible)
views.keysBackupSetupStep2PassphraseConfirmEditText.showPassword(shouldBeVisible)
views.keysBackupSetupStep2ShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(rootGroup)
mPassphraseConfirmInputLayout.error = it
TransitionManager.beginDelayedTransition(views.keysBackupRoot)
views.keysBackupSetupStep2PassphraseConfirmTil.error = it
})
mPassphraseConfirmTextEdit.setOnEditorActionListener { _, actionId, _ ->
views.keysBackupSetupStep2PassphraseConfirmEditText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
doNext()
return@setOnEditorActionListener true
@ -159,13 +136,20 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
}
}
@OnClick(R.id.keys_backup_view_show_password)
fun toggleVisibilityMode() {
private fun setupViews() {
views.keysBackupSetupStep2ShowPassword.setOnClickListener { toggleVisibilityMode() }
views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
views.keysBackupSetupStep2PassphraseEnterEdittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() }
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
}
private fun toggleVisibilityMode() {
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
@OnClick(R.id.keys_backup_setup_step2_button)
fun doNext() {
private fun doNext() {
when {
viewModel.passphrase.value.isNullOrEmpty() -> {
viewModel.passphraseError.value = context?.getString(R.string.passphrase_empty_error_message)
@ -184,8 +168,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
}
}
@OnClick(R.id.keys_backup_setup_step2_skip_button)
fun skipPassphrase() {
private fun skipPassphrase() {
when {
viewModel.passphrase.value.isNullOrEmpty() -> {
// Generate a recovery key for the user

View file

@ -18,16 +18,15 @@ package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import arrow.core.Try
import butterknife.BindView
import butterknife.OnClick
import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
@ -36,6 +35,8 @@ import im.vector.app.core.utils.LiveEvent
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.selectTxtFileToWrite
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentKeysBackupSetupStep3Binding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -46,18 +47,11 @@ import java.util.Date
import java.util.Locale
import javax.inject.Inject
class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() {
class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<FragmentKeysBackupSetupStep3Binding>() {
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step3
@BindView(R.id.keys_backup_setup_step3_button)
lateinit var mFinishButton: Button
@BindView(R.id.keys_backup_recovery_key_text)
lateinit var mRecoveryKeyTextView: TextView
@BindView(R.id.keys_backup_setup_step3_line2_text)
lateinit var mRecoveryKeyLabel2TextView: TextView
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep3Binding {
return FragmentKeysBackupSetupStep3Binding.inflate(inflater, container, false)
}
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@ -70,10 +64,10 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
viewModel.passphrase.observe(viewLifecycleOwner, Observer {
if (it.isNullOrBlank()) {
// Recovery was generated, so show key and options to save
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase)
mFinishButton.text = getString(R.string.keys_backup_setup_step3_button_title_no_passphrase)
views.keysBackupSetupStep3Label2.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase)
views.keysBackupSetupStep3FinishButton.text = getString(R.string.keys_backup_setup_step3_button_title_no_passphrase)
mRecoveryKeyTextView.text = viewModel.recoveryKey.value!!
views.keysBackupSetupStep3RecoveryKeyText.text = viewModel.recoveryKey.value!!
.replace(" ", "")
.chunked(16)
.joinToString("\n") {
@ -81,17 +75,24 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
.chunked(4)
.joinToString(" ")
}
mRecoveryKeyTextView.isVisible = true
views.keysBackupSetupStep3RecoveryKeyText.isVisible = true
} else {
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2)
mFinishButton.text = getString(R.string.keys_backup_setup_step3_button_title)
mRecoveryKeyTextView.isVisible = false
views.keysBackupSetupStep3Label2.text = getString(R.string.keys_backup_setup_step3_text_line2)
views.keysBackupSetupStep3FinishButton.text = getString(R.string.keys_backup_setup_step3_button_title)
views.keysBackupSetupStep3RecoveryKeyText.isVisible = false
}
})
setupViews()
}
@OnClick(R.id.keys_backup_setup_step3_button)
fun onFinishButtonClicked() {
private fun setupViews() {
views.keysBackupSetupStep3FinishButton.setOnClickListener { onFinishButtonClicked() }
views.keysBackupSetupStep3CopyButton.setOnClickListener { onCopyButtonClicked() }
views.keysBackupSetupStep3RecoveryKeyText.setOnClickListener { onRecoveryKeyClicked() }
}
private fun onFinishButtonClicked() {
if (viewModel.megolmBackupCreationInfo == null) {
// nothing
} else {
@ -103,8 +104,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
}
}
@OnClick(R.id.keys_backup_setup_step3_copy_button)
fun onCopyButtonClicked() {
private fun onCopyButtonClicked() {
val dialog = BottomSheetDialog(requireActivity())
dialog.setContentView(R.layout.bottom_sheet_save_recovery_key)
dialog.setCanceledOnTouchOutside(true)
@ -155,8 +155,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
dialog.show()
}
@OnClick(R.id.keys_backup_recovery_key_text)
fun onRecoveryKeyClicked() {
private fun onRecoveryKeyClicked() {
viewModel.recoveryKey.value?.let {
viewModel.copyHasBeenMade = true

View file

@ -35,8 +35,7 @@ import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.recover.SetupMode
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity.*
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
import kotlin.reflect.KClass
@ -65,7 +64,7 @@ class SharedSecureStorageActivity :
super.onCreate(savedInstanceState)
supportFragmentManager.addFragmentOnAttachListener(this)
toolbar.visibility = View.GONE
views.toolbar.visibility = View.GONE
viewModel.observeViewEvents { observeViewEvents(it) }
@ -132,7 +131,7 @@ class SharedSecureStorageActivity :
}
override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) {
if (fragment is VectorBaseBottomSheetDialogFragment) {
if (fragment is VectorBaseBottomSheetDialogFragment<*>) {
fragment.resultListener = this
}
}

View file

@ -18,7 +18,9 @@ package im.vector.app.features.crypto.quads
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents
@ -27,23 +29,26 @@ import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
import org.matrix.android.sdk.api.extensions.tryOrNull
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment() {
class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() {
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_key
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssAccessFromKeyBinding {
return FragmentSsssAccessFromKeyBinding.inflate(inflater, container, false)
}
val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -53,35 +58,35 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
}
.disposeOnDestroyView()
ssss_key_enter_edittext.textChanges()
views.ssssKeyEnterEdittext.textChanges()
.skipInitialValue()
.subscribe {
ssss_key_enter_til.error = null
ssss_key_submit.isEnabled = it.isNotBlank()
views.ssssKeyEnterTil.error = null
views.ssssKeySubmit.isEnabled = it.isNotBlank()
}
.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.observeViewEvents {
when (it) {
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() {
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
ssss_key_submit.isEnabled = false
views.ssssKeySubmit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
}
@ -93,7 +98,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
?.bufferedReader()
?.use { it.readText() }
?.let {
ssss_key_enter_edittext.setText(it)
views.ssssKeyEnterEdittext.setText(it)
}
}
}

View file

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.quads
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.activityViewModel
@ -29,16 +31,19 @@ import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SharedSecuredStoragePassphraseFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentSsssAccessFromPassphraseBinding>() {
override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssAccessFromPassphraseBinding {
return FragmentSsssAccessFromPassphraseBinding.inflate(inflater, container, false)
}
val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
@ -48,7 +53,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
// If has passphrase
val pass = getString(R.string.recovery_passphrase)
val key = getString(R.string.recovery_key)
ssss_restore_with_passphrase_warning_text.text = getString(
views.ssssRestoreWithPassphraseWarningText.text = getString(
R.string.enter_secret_storage_passphrase_or_key,
pass,
key
@ -57,7 +62,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
.colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
.colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
ssss_passphrase_enter_edittext.editorActionEvents()
views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -67,40 +72,40 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
}
.disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges()
views.ssssPassphraseEnterEdittext.textChanges()
.subscribe {
ssss_passphrase_enter_til.error = null
ssss_passphrase_submit.isEnabled = it.isNotBlank()
views.ssssPassphraseEnterTil.error = null
views.ssssPassphraseSubmit.isEnabled = it.isNotBlank()
}
.disposeOnDestroyView()
ssss_passphrase_reset.clickableView.debouncedClicks {
views.ssssPassphraseReset.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
}
sharedViewModel.observeViewEvents {
when (it) {
is SharedSecureStorageViewEvent.InlineError -> {
ssss_passphrase_enter_til.error = it.message
views.ssssPassphraseEnterTil.error = it.message
}
}
}
ssss_passphrase_submit.debouncedClicks { submit() }
ssss_passphrase_use_key.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
views.ssssPassphraseSubmit.debouncedClicks { submit() }
views.ssssPassphraseUseKey.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
}
fun submit() {
val text = ssss_passphrase_enter_edittext.text.toString()
val text = views.ssssPassphraseEnterEdittext.text.toString()
if (text.isBlank()) return // Should not reach this point as button disabled
ssss_passphrase_submit.isEnabled = false
views.ssssPassphraseSubmit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text))
}
override fun invalidate() = withState(sharedViewModel) { state ->
val shouldBeVisible = state.passphraseVisible
ssss_passphrase_enter_edittext.showPassword(shouldBeVisible)
ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.ssssViewShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}

View file

@ -17,41 +17,47 @@
package im.vector.app.features.crypto.quads
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSsssResetAllBinding
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import kotlinx.android.synthetic.main.fragment_ssss_reset_all.*
import javax.inject.Inject
class SharedSecuredStorageResetAllFragment @Inject constructor() : VectorBaseFragment() {
class SharedSecuredStorageResetAllFragment @Inject constructor()
: VectorBaseFragment<FragmentSsssResetAllBinding>() {
override fun getLayoutResId() = R.layout.fragment_ssss_reset_all
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssResetAllBinding {
return FragmentSsssResetAllBinding.inflate(inflater, container, false)
}
val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ssss_reset_button_reset.debouncedClicks {
views.ssssResetButtonReset.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.DoResetAll)
}
ssss_reset_button_cancel.debouncedClicks {
views.ssssResetButtonCancel.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.Back)
}
ssss_reset_other_devices.debouncedClicks {
views.ssssResetOtherDevices.debouncedClicks {
withState(sharedViewModel) {
DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST")
}
}
sharedViewModel.subscribe(this) { state ->
ssss_reset_other_devices.setTextOrHide(
views.ssssResetOtherDevices.setTextOrHide(
state.activeDeviceCount
.takeIf { it > 0 }
?.let { resources.getQuantityString(R.plurals.secure_backup_reset_devices_you_can_verify, it, it) }

View file

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel
@ -30,18 +32,19 @@ import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentBootstrapEnterAccountPasswordBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_account_password.*
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.ssss_view_show_password
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BootstrapAccountPasswordFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentBootstrapEnterAccountPasswordBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_account_password
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterAccountPasswordBinding {
return FragmentBootstrapEnterAccountPasswordBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
@ -49,13 +52,13 @@ class BootstrapAccountPasswordFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
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()
.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)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -65,21 +68,21 @@ class BootstrapAccountPasswordFragment @Inject constructor(
}
.disposeOnDestroyView()
bootstrapAccountPasswordEditText.textChanges()
views.bootstrapAccountPasswordEditText.textChanges()
.distinctUntilChanged()
.subscribe {
if (!it.isNullOrBlank()) {
bootstrapAccountPasswordTil.error = null
views.bootstrapAccountPasswordTil.error = null
}
}
.disposeOnDestroyView()
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapPasswordButton.debouncedClicks { submit() }
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
views.bootstrapPasswordButton.debouncedClicks { submit() }
withState(sharedViewModel) { state ->
(state.step as? BootstrapStep.AccountPassword)?.failure?.let {
bootstrapAccountPasswordTil.error = it
views.bootstrapAccountPasswordTil.error = it
}
}
}
@ -88,9 +91,9 @@ class BootstrapAccountPasswordFragment @Inject constructor(
if (state.step !is BootstrapStep.AccountPassword) {
return@withState
}
val accountPassword = bootstrapAccountPasswordEditText.text?.toString()
val accountPassword = views.bootstrapAccountPasswordEditText.text?.toString()
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 {
view?.hideKeyboard()
sharedViewModel.handle(BootstrapActions.ReAuth(accountPassword))
@ -100,8 +103,8 @@ class BootstrapAccountPasswordFragment @Inject constructor(
override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.AccountPassword) {
val isPasswordVisible = state.step.isPasswordVisible
bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false)
views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}
}

View file

@ -36,12 +36,12 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_bootstrap.*
import im.vector.app.databinding.BottomSheetBootstrapBinding
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
import kotlin.reflect.KClass
class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetBootstrapBinding>() {
@Parcelize
data class Args(
@ -59,7 +59,9 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this)
}
override fun getLayoutResId() = R.layout.bottom_sheet_bootstrap
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetBootstrapBinding {
return BottomSheetBootstrapBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -120,60 +122,60 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun invalidate() = withState(viewModel) { state ->
when (state.step) {
is BootstrapStep.CheckingMigration -> {
bootstrapIcon.isVisible = false
bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
views.bootstrapIcon.isVisible = false
views.bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
showFragment(BootstrapWaitingFragment::class, Bundle())
}
is BootstrapStep.FirstForm -> {
bootstrapIcon.isVisible = false
bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
views.bootstrapIcon.isVisible = false
views.bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
showFragment(BootstrapSetupRecoveryKeyFragment::class, Bundle())
}
is BootstrapStep.SetupPassphrase -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp))
bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp))
views.bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title)
showFragment(BootstrapEnterPassphraseFragment::class, Bundle())
}
is BootstrapStep.ConfirmPassphrase -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp))
bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp))
views.bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title)
showFragment(BootstrapConfirmPassphraseFragment::class, Bundle())
}
is BootstrapStep.AccountPassword -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
bootstrapTitleText.text = getString(R.string.account_password)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
views.bootstrapTitleText.text = getString(R.string.account_password)
showFragment(BootstrapAccountPasswordFragment::class, Bundle())
}
is BootstrapStep.Initializing -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
bootstrapTitleText.text = getString(R.string.bootstrap_loading_title)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
views.bootstrapTitleText.text = getString(R.string.bootstrap_loading_title)
showFragment(BootstrapWaitingFragment::class, Bundle())
}
is BootstrapStep.SaveRecoveryKey -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
bootstrapTitleText.text = getString(R.string.bottom_sheet_save_your_recovery_key_title)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
views.bootstrapTitleText.text = getString(R.string.bottom_sheet_save_your_recovery_key_title)
showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle())
}
is BootstrapStep.DoneSuccess -> {
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
bootstrapTitleText.text = getString(R.string.bootstrap_finish_title)
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp))
views.bootstrapTitleText.text = getString(R.string.bootstrap_finish_title)
showFragment(BootstrapConclusionFragment::class, Bundle())
}
is BootstrapStep.GetBackupSecretForMigration -> {
val isKey = state.step.useKey()
val drawableRes = if (isKey) R.drawable.ic_security_key_24dp else R.drawable.ic_security_phrase_24dp
bootstrapIcon.isVisible = true
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(
views.bootstrapIcon.isVisible = true
views.bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(
requireContext(),
drawableRes)
)
bootstrapTitleText.text = getString(R.string.upgrade_security)
views.bootstrapTitleText.text = getString(R.string.upgrade_security)
showFragment(BootstrapMigrateBackupFragment::class, Bundle())
}
}.exhaustive

View file

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
@ -25,27 +27,30 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import kotlinx.android.synthetic.main.fragment_bootstrap_conclusion.*
import im.vector.app.databinding.FragmentBootstrapConclusionBinding
import javax.inject.Inject
class BootstrapConclusionFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentBootstrapConclusionBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_conclusion
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapConclusionBinding {
return FragmentBootstrapConclusionBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bootstrapConclusionContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) }
views.bootstrapConclusionContinue.views.itemVerificationClickableZone.debouncedClicks { sharedViewModel.handle(BootstrapActions.Completed) }
}
override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step !is BootstrapStep.DoneSuccess) return@withState
bootstrapConclusionText.text = getString(
views.bootstrapConclusionText.text = getString(
R.string.bootstrap_cross_signing_success,
getString(R.string.recovery_passphrase),
getString(R.string.message_key)

View file

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.view.isGone
import com.airbnb.mvrx.parentFragmentViewModel
@ -28,32 +30,36 @@ import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragment() {
class BootstrapConfirmPassphraseFragment @Inject constructor()
: VectorBaseFragment<FragmentBootstrapEnterPassphraseBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding {
return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)
ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint)
views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice)
views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
withState(sharedViewModel) {
// set initial value (useful when coming back)
ssss_passphrase_enter_edittext.setText(it.passphraseRepeat ?: "")
ssss_passphrase_enter_edittext.requestFocus()
views.ssssPassphraseEnterEdittext.setText(it.passphraseRepeat ?: "")
views.ssssPassphraseEnterEdittext.requestFocus()
}
ssss_passphrase_enter_edittext.editorActionEvents()
views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -63,9 +69,9 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragm
}
.disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges()
views.ssssPassphraseEnterEdittext.textChanges()
.subscribe {
ssss_passphrase_enter_til.error = null
views.ssssPassphraseEnterTil.error = null
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: ""))
}
.disposeOnDestroyView()
@ -78,20 +84,20 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragm
// }
}
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapSubmit.debouncedClicks { submit() }
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
views.bootstrapSubmit.debouncedClicks { submit() }
}
private fun submit() = withState(sharedViewModel) { state ->
if (state.step !is BootstrapStep.ConfirmPassphrase) {
return@withState
}
val passphrase = ssss_passphrase_enter_edittext.text?.toString()
val passphrase = views.ssssPassphraseEnterEdittext.text?.toString()
when {
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 ->
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 -> {
view?.hideKeyboard()
sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase))
@ -102,8 +108,8 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragm
override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.ConfirmPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}
}

View file

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
@ -26,29 +28,33 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragment() {
class BootstrapEnterPassphraseFragment @Inject constructor()
: VectorBaseFragment<FragmentBootstrapEnterPassphraseBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding {
return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_notice)
ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint)
views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_notice)
views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
withState(sharedViewModel) {
// set initial value (useful when coming back)
ssss_passphrase_enter_edittext.setText(it.passphrase ?: "")
views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "")
}
ssss_passphrase_enter_edittext.editorActionEvents()
views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -58,7 +64,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
}
.disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges()
views.ssssPassphraseEnterEdittext.textChanges()
.subscribe {
// ssss_passphrase_enter_til.error = null
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
@ -74,8 +80,8 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
// }
}
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapSubmit.debouncedClicks { submit() }
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
views.bootstrapSubmit.debouncedClicks { submit() }
}
private fun submit() = withState(sharedViewModel) { state ->
@ -83,11 +89,11 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
return@withState
}
val score = state.passphraseStrength.invoke()?.score
val passphrase = ssss_passphrase_enter_edittext.text?.toString()
val passphrase = views.ssssPassphraseEnterEdittext.text?.toString()
if (passphrase.isNullOrBlank()) {
ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message)
views.ssssPassphraseEnterTil.error = getString(R.string.passphrase_empty_error_message)
} else if (score != 4) {
ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_too_weak)
views.ssssPassphraseEnterTil.error = getString(R.string.passphrase_passphrase_too_weak)
} else {
sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase))
}
@ -96,21 +102,21 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.SetupPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
state.passphraseStrength.invoke()?.let { strength ->
val score = strength.score
ssss_passphrase_security_progress.strength = score
views.ssssPassphraseSecurityProgress.strength = score
if (score in 1..3) {
val hint =
strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() }
?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull()
if (hint != null && hint != ssss_passphrase_enter_til.error.toString()) {
ssss_passphrase_enter_til.error = hint
if (hint != null && hint != views.ssssPassphraseEnterTil.error.toString()) {
views.ssssPassphraseEnterTil.error = hint
}
} else {
ssss_passphrase_enter_til.error = null
views.ssssPassphraseEnterTil.error = null
}
}
}

View file

@ -21,7 +21,9 @@ import android.os.Bundle
import android.text.InputType.TYPE_CLASS_TEXT
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
@ -37,9 +39,9 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
import kotlinx.android.synthetic.main.fragment_bootstrap_migrate_backup.*
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import java.util.concurrent.TimeUnit
@ -47,9 +49,11 @@ import javax.inject.Inject
class BootstrapMigrateBackupFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentBootstrapMigrateBackupBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapMigrateBackupBinding {
return FragmentBootstrapMigrateBackupBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
@ -58,9 +62,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
withState(sharedViewModel) {
// 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)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
@ -70,19 +74,19 @@ class BootstrapMigrateBackupFragment @Inject constructor(
}
.disposeOnDestroyView()
bootstrapMigrateEditText.textChanges()
views.bootstrapMigrateEditText.textChanges()
.skipInitialValue()
.subscribe {
bootstrapRecoveryKeyEnterTil.error = null
views.bootstrapRecoveryKeyEnterTil.error = null
// sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
}
.disposeOnDestroyView()
// sharedViewModel.observeViewEvents {}
bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
views.bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
}
private fun submit() = withState(sharedViewModel) { state ->
@ -90,12 +94,12 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey()
val secret = bootstrapMigrateEditText.text?.toString()
val secret = views.bootstrapMigrateEditText.text?.toString()
if (secret.isNullOrEmpty()) {
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)) {
bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key)
views.bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key)
} else {
view?.hideKeyboard()
if (isEnteringKey) {
@ -112,38 +116,38 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey()
if (isEnteringKey) {
bootstrapMigrateShowPassword.isVisible = false
bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE
views.bootstrapMigrateShowPassword.isVisible = false
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)
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
bootstrapMigrateForgotPassphrase.isVisible = false
bootstrapMigrateUseFile.isVisible = true
views.bootstrapMigrateEditText.hint = recKey
views.bootstrapMigrateForgotPassphrase.isVisible = false
views.bootstrapMigrateUseFile.isVisible = true
} else {
bootstrapMigrateShowPassword.isVisible = true
views.bootstrapMigrateShowPassword.isVisible = true
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
val isPasswordVisible = state.step.isPasswordVisible
bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
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)
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()
.colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
bootstrapMigrateUseFile.isVisible = false
views.bootstrapMigrateUseFile.isVisible = false
}
}
@ -155,7 +159,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
?.bufferedReader()
?.use { it.readText() }
?.let {
bootstrapMigrateEditText.setText(it)
views.bootstrapMigrateEditText.setText(it)
}
}
}

View file

@ -20,7 +20,9 @@ import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
@ -30,7 +32,8 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.core.utils.toast
import kotlinx.android.synthetic.main.fragment_bootstrap_save_key.*
import im.vector.app.databinding.FragmentBootstrapSaveKeyBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -38,18 +41,20 @@ import javax.inject.Inject
class BootstrapSaveRecoveryKeyFragment @Inject constructor(
private val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentBootstrapSaveKeyBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_save_key
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSaveKeyBinding {
return FragmentBootstrapSaveKeyBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() }
recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() }
recoveryContinue.clickableView.debouncedClicks {
views.recoverySave.views.itemVerificationClickableZone.debouncedClicks { downloadRecoveryKey() }
views.recoveryCopy.views.itemVerificationClickableZone.debouncedClicks { shareRecoveryKey() }
views.recoveryContinue.views.itemVerificationClickableZone.debouncedClicks {
// We do not display the final Fragment anymore
// TODO Do some cleanup
// sharedViewModel.handle(BootstrapActions.GoToCompleted)
@ -112,7 +117,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
val step = state.step
if (step !is BootstrapStep.SaveRecoveryKey) return@withState
recoveryContinue.isVisible = step.isSaved
bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
views.recoveryContinue.isVisible = step.isSaved
views.bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
}
}

View file

@ -17,18 +17,24 @@
package im.vector.app.features.crypto.recover
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_bootstrap_setup_recovery.*
import im.vector.app.databinding.FragmentBootstrapSetupRecoveryBinding
import javax.inject.Inject
class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragment() {
class BootstrapSetupRecoveryKeyFragment @Inject constructor()
: VectorBaseFragment<FragmentBootstrapSetupRecoveryBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_setup_recovery
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSetupRecoveryBinding {
return FragmentBootstrapSetupRecoveryBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
@ -36,15 +42,15 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme
super.onViewCreated(view, savedInstanceState)
// Actions when a key backup exist
bootstrapSetupSecureSubmit.clickableView.debouncedClicks {
views.bootstrapSetupSecureSubmit.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration)
}
// Actions when there is no key backup
bootstrapSetupSecureUseSecurityKey.clickableView.debouncedClicks {
views.bootstrapSetupSecureUseSecurityKey.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false))
}
bootstrapSetupSecureUseSecurityPassphrase.clickableView.debouncedClicks {
views.bootstrapSetupSecureUseSecurityPassphrase.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true))
}
}
@ -53,23 +59,23 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme
if (state.step is BootstrapStep.FirstForm) {
if (state.step.keyBackUpExist) {
// Display the set up action
bootstrapSetupSecureSubmit.isVisible = true
bootstrapSetupSecureUseSecurityKey.isVisible = false
bootstrapSetupSecureUseSecurityPassphrase.isVisible = false
bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false
views.bootstrapSetupSecureSubmit.isVisible = true
views.bootstrapSetupSecureUseSecurityKey.isVisible = false
views.bootstrapSetupSecureUseSecurityPassphrase.isVisible = false
views.bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false
} else {
if (state.step.reset) {
bootstrapSetupSecureText.text = getString(R.string.reset_secure_backup_title)
bootstrapSetupWarningTextView.isVisible = true
views.bootstrapSetupSecureText.text = getString(R.string.reset_secure_backup_title)
views.bootstrapSetupWarningTextView.isVisible = true
} else {
bootstrapSetupSecureText.text = getString(R.string.bottom_sheet_setup_secure_backup_subtitle)
bootstrapSetupWarningTextView.isVisible = false
views.bootstrapSetupSecureText.text = getString(R.string.bottom_sheet_setup_secure_backup_subtitle)
views.bootstrapSetupWarningTextView.isVisible = false
}
// Choose between create a passphrase or use a recovery key
bootstrapSetupSecureSubmit.isVisible = false
bootstrapSetupSecureUseSecurityKey.isVisible = true
bootstrapSetupSecureUseSecurityPassphrase.isVisible = true
bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = true
views.bootstrapSetupSecureSubmit.isVisible = false
views.bootstrapSetupSecureUseSecurityKey.isVisible = true
views.bootstrapSetupSecureUseSecurityPassphrase.isVisible = true
views.bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = true
}
}
}

View file

@ -16,26 +16,31 @@
package im.vector.app.features.crypto.recover
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_bootstrap_waiting.*
import im.vector.app.databinding.FragmentBootstrapWaitingBinding
import javax.inject.Inject
class BootstrapWaitingFragment @Inject constructor() : VectorBaseFragment() {
class BootstrapWaitingFragment @Inject constructor()
: VectorBaseFragment<FragmentBootstrapWaitingBinding>() {
override fun getLayoutResId() = R.layout.fragment_bootstrap_waiting
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapWaitingBinding {
return FragmentBootstrapWaitingBinding.inflate(inflater, container, false)
}
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun invalidate() = withState(sharedViewModel) { state ->
when (state.step) {
is BootstrapStep.Initializing -> {
bootstrapLoadingStatusText.isVisible = true
bootstrapDescriptionText.isVisible = true
bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message
views.bootstrapLoadingStatusText.isVisible = true
views.bootstrapDescriptionText.isVisible = true
views.bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message
}
// is BootstrapStep.CheckingMigration -> {
// bootstrapLoadingStatusText.isVisible = false
@ -43,8 +48,8 @@ class BootstrapWaitingFragment @Inject constructor() : VectorBaseFragment() {
// }
else -> {
// just show the spinner
bootstrapLoadingStatusText.isVisible = false
bootstrapDescriptionText.isVisible = false
views.bootstrapLoadingStatusText.isVisible = false
views.bootstrapDescriptionText.isVisible = false
}
}
}

View file

@ -19,10 +19,10 @@ package im.vector.app.features.crypto.recover
import android.app.Activity
import android.content.DialogInterface
import android.view.KeyEvent
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import im.vector.app.R
import im.vector.app.databinding.DialogRecoveryKeySavedInfoBinding
import me.gujun.android.span.image
import me.gujun.android.span.span
@ -30,10 +30,9 @@ class KeepItSafeDialog {
fun show(activity: Activity) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_recovery_key_saved_info, null)
val views = DialogRecoveryKeySavedInfoBinding.bind(dialogLayout)
val descriptionText = dialogLayout.findViewById<TextView>(R.id.keepItSafeText)
descriptionText.text = span {
views.keepItSafeText.text = span {
span {
image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!)
+" "

View file

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

View file

@ -16,10 +16,14 @@
package im.vector.app.features.crypto.verification
import im.vector.app.R
import android.view.LayoutInflater
import android.view.ViewGroup
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentProgressBinding
import javax.inject.Inject
class QuadSLoadingFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_progress
class QuadSLoadingFragment @Inject constructor() : VectorBaseFragment<FragmentProgressBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentProgressBinding {
return FragmentProgressBinding.inflate(inflater, container, false)
}
}

View file

@ -20,13 +20,12 @@ import android.app.Dialog
import android.os.Bundle
import android.os.Parcelable
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import butterknife.BindView
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@ -37,6 +36,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetVerificationBinding
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.crypto.verification.cancel.VerificationCancelFragment
import im.vector.app.features.crypto.verification.cancel.VerificationNotMeFragment
@ -48,7 +48,7 @@ import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrS
import im.vector.app.features.crypto.verification.request.VerificationRequestFragment
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.settings.VectorSettingsActivity
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@ -60,7 +60,7 @@ import timber.log.Timber
import javax.inject.Inject
import kotlin.reflect.KClass
class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetVerificationBinding>() {
@Parcelize
data class VerificationArgs(
@ -86,16 +86,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this)
}
@BindView(R.id.verificationRequestName)
lateinit var otherUserNameText: TextView
@BindView(R.id.verificationRequestShield)
lateinit var otherUserShield: ImageView
@BindView(R.id.verificationRequestAvatar)
lateinit var otherUserAvatarImageView: ImageView
override fun getLayoutResId() = R.layout.bottom_sheet_verification
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationBinding {
return BottomSheetVerificationBinding.inflate(inflater, container, false)
}
init {
isCancelable = false
@ -126,7 +119,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
VerificationBottomSheetViewEvents.GoToSettings -> {
dismiss()
(activity as? VectorBaseActivity)?.navigator?.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY)
(activity as? VectorBaseActivity<*>)?.let { activity ->
activity.navigator.openSettings(activity, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY)
}
}
}.exhaustive
}
@ -163,27 +158,27 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun invalidate() = withState(viewModel) { state ->
state.otherUserMxItem?.let { matrixItem ->
if (state.isMe) {
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
avatarRenderer.render(matrixItem, views.otherUserAvatarImageView)
if (state.sasTransactionState == VerificationTxState.Verified
|| state.qrTransactionState == VerificationTxState.Verified
|| state.verifiedFromPrivateKeys) {
otherUserShield.setImageResource(R.drawable.ic_shield_trusted)
views.otherUserShield.setImageResource(R.drawable.ic_shield_trusted)
} else {
otherUserShield.setImageResource(R.drawable.ic_shield_warning)
views.otherUserShield.setImageResource(R.drawable.ic_shield_warning)
}
otherUserNameText.text = getString(
views.otherUserNameText.text = getString(
if (state.selfVerificationMode) R.string.crosssigning_verify_this_session else R.string.crosssigning_verify_session
)
otherUserShield.isVisible = true
views.otherUserShield.isVisible = true
} else {
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
avatarRenderer.render(matrixItem, views.otherUserAvatarImageView)
if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
otherUserShield.isVisible = true
views.otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
views.otherUserShield.isVisible = true
} else {
otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName())
otherUserShield.isVisible = false
views.otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName())
views.otherUserShield.isVisible = false
}
}
}
@ -200,13 +195,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
if (state.userThinkItsNotHim) {
otherUserNameText.text = getString(R.string.dialog_title_warning)
views.otherUserNameText.text = getString(R.string.dialog_title_warning)
showFragment(VerificationNotMeFragment::class, Bundle())
return@withState
}
if (state.userWantsToCancel) {
otherUserNameText.text = getString(R.string.are_you_sure)
views.otherUserNameText.text = getString(R.string.are_you_sure)
showFragment(VerificationCancelFragment::class, Bundle())
return@withState
}
@ -298,7 +293,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// Transaction has not yet started
if (state.pendingRequest.invoke()?.cancelConclusion != null) {
// The request has been declined, we should dismiss
otherUserNameText.text = getString(R.string.verification_cancelled)
views.otherUserNameText.text = getString(R.string.verification_cancelled)
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
false,

View file

@ -17,24 +17,29 @@
package im.vector.app.features.crypto.verification.cancel
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationCancelFragment @Inject constructor(
val controller: VerificationCancelController
) : VectorBaseFragment(), VerificationCancelController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationCancelController.Listener {
private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -42,13 +47,13 @@ class VerificationCancelFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -17,24 +17,29 @@
package im.vector.app.features.crypto.verification.cancel
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationNotMeFragment @Inject constructor(
val controller: VerificationNotMeController
) : VectorBaseFragment(), VerificationNotMeController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationNotMeController.Listener {
private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -42,13 +47,13 @@ class VerificationNotMeFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -17,11 +17,12 @@ package im.vector.app.features.crypto.verification.choose
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
@ -29,23 +30,27 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import im.vector.app.features.qrcode.QrCodeScannerActivity
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import timber.log.Timber
import javax.inject.Inject
class VerificationChooseMethodFragment @Inject constructor(
val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory,
val controller: VerificationChooseMethodController
) : VectorBaseFragment(), VerificationChooseMethodController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationChooseMethodController.Listener {
private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class)
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -54,13 +59,13 @@ class VerificationChooseMethodFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -17,23 +17,25 @@ package im.vector.app.features.crypto.verification.conclusion
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
class VerificationConclusionFragment @Inject constructor(
val controller: VerificationConclusionController
) : VectorBaseFragment(), VerificationConclusionController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationConclusionController.Listener {
@Parcelize
data class Args(
@ -46,7 +48,9 @@ class VerificationConclusionFragment @Inject constructor(
private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -55,13 +59,13 @@ class VerificationConclusionFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -16,29 +16,34 @@
package im.vector.app.features.crypto.verification.emoji
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationEmojiCodeFragment @Inject constructor(
val viewModelFactory: VerificationEmojiCodeViewModel.Factory,
val controller: VerificationEmojiCodeController
) : VectorBaseFragment(), VerificationEmojiCodeController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationEmojiCodeController.Listener {
private val viewModel by fragmentViewModel(VerificationEmojiCodeViewModel::class)
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -47,13 +52,13 @@ class VerificationEmojiCodeFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -18,19 +18,20 @@ package im.vector.app.features.crypto.verification.qrconfirmation
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.MvRx
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
class VerificationQRWaitingFragment @Inject constructor(
val controller: VerificationQRWaitingController
) : VectorBaseFragment() {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>() {
@Parcelize
data class Args(
@ -38,7 +39,9 @@ class VerificationQRWaitingFragment @Inject constructor(
val otherUserName: String
) : Parcelable
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -49,11 +52,11 @@ class VerificationQRWaitingFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
}
}

View file

@ -16,25 +16,30 @@
package im.vector.app.features.crypto.verification.qrconfirmation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationQrScannedByOtherFragment @Inject constructor(
val controller: VerificationQrScannedByOtherController
) : VectorBaseFragment(), VerificationQrScannedByOtherController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationQrScannedByOtherController.Listener {
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -46,13 +51,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -16,25 +16,30 @@
package im.vector.app.features.crypto.verification.request
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationRequestFragment @Inject constructor(
val controller: VerificationRequestController
) : VectorBaseFragment(), VerificationRequestController.Listener {
) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationRequestController.Listener {
private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -42,13 +47,13 @@ class VerificationRequestFragment @Inject constructor(
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}

View file

@ -17,8 +17,11 @@ package im.vector.app.features.discovery
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
@ -27,12 +30,12 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.settings.VectorSettingsActivity
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.terms.TermsService
@ -41,9 +44,12 @@ import javax.inject.Inject
class DiscoverySettingsFragment @Inject constructor(
private val controller: DiscoverySettingsController,
val viewModelFactory: DiscoverySettingsViewModel.Factory
) : VectorBaseFragment(), DiscoverySettingsController.Listener {
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(),
DiscoverySettingsController.Listener {
override fun getLayoutResId() = R.layout.fragment_generic_recycler
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
}
private val viewModel by fragmentViewModel(DiscoverySettingsViewModel::class)
@ -55,7 +61,7 @@ class DiscoverySettingsFragment @Inject constructor(
sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
controller.listener = this
genericRecyclerView.configureWith(controller)
views.genericRecyclerView.configureWith(controller)
sharedViewModel.navigateEvent.observeEvent(this) {
when (it) {
@ -74,7 +80,7 @@ class DiscoverySettingsFragment @Inject constructor(
}
override fun onDestroyView() {
genericRecyclerView.cleanup()
views.genericRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
@ -85,7 +91,7 @@ class DiscoverySettingsFragment @Inject constructor(
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_discovery_category)
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.settings_discovery_category)
// If some 3pids are pending, we can try to check if they have been verified here
viewModel.handle(DiscoverySettingsAction.Refresh)

View file

@ -17,9 +17,12 @@ package im.vector.app.features.discovery.change
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import com.airbnb.mvrx.fragmentViewModel
@ -29,21 +32,23 @@ import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentSetIdentityServerBinding
import im.vector.app.features.discovery.DiscoverySharedViewModel
import kotlinx.android.synthetic.main.fragment_set_identity_server.*
import org.matrix.android.sdk.api.session.terms.TermsService
import javax.inject.Inject
class SetIdentityServerFragment @Inject constructor(
val viewModelFactory: SetIdentityServerViewModel.Factory,
val colorProvider: ColorProvider
) : VectorBaseFragment() {
) : VectorBaseFragment<FragmentSetIdentityServerBinding>() {
override fun getLayoutResId() = R.layout.fragment_set_identity_server
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSetIdentityServerBinding {
return FragmentSetIdentityServerBinding.inflate(inflater, container, false)
}
private val viewModel by fragmentViewModel(SetIdentityServerViewModel::class)
@ -52,11 +57,11 @@ class SetIdentityServerFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state ->
if (state.defaultIdentityServerUrl.isNullOrEmpty()) {
// No default
identityServerSetDefaultNotice.isVisible = false
identityServerSetDefaultSubmit.isVisible = false
identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice_no_default)
views.identityServerSetDefaultNotice.isVisible = false
views.identityServerSetDefaultSubmit.isVisible = false
views.identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice_no_default)
} else {
identityServerSetDefaultNotice.text = getString(
views.identityServerSetDefaultNotice.text = getString(
R.string.identity_server_set_default_notice,
state.homeServerUrl.toReducedUrl(),
state.defaultIdentityServerUrl.toReducedUrl()
@ -65,10 +70,10 @@ class SetIdentityServerFragment @Inject constructor(
.colorizeMatchingText(state.defaultIdentityServerUrl.toReducedUrl(),
colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
identityServerSetDefaultNotice.isVisible = true
identityServerSetDefaultSubmit.isVisible = true
identityServerSetDefaultSubmit.text = getString(R.string.identity_server_set_default_submit, state.defaultIdentityServerUrl.toReducedUrl())
identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice)
views.identityServerSetDefaultNotice.isVisible = true
views.identityServerSetDefaultSubmit.isVisible = true
views.identityServerSetDefaultSubmit.text = getString(R.string.identity_server_set_default_submit, state.defaultIdentityServerUrl.toReducedUrl())
views.identityServerSetDefaultAlternative.setText(R.string.identity_server_set_alternative_notice)
}
}
@ -77,28 +82,28 @@ class SetIdentityServerFragment @Inject constructor(
sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java)
identityServerSetDefaultAlternativeTextInput.setOnEditorActionListener { _, actionId, _ ->
views.identityServerSetDefaultAlternativeTextInput.setOnEditorActionListener { _, actionId, _ ->
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 false
}
identityServerSetDefaultAlternativeTextInput
views.identityServerSetDefaultAlternativeTextInput
.textChanges()
.subscribe {
identityServerSetDefaultAlternativeTil.error = null
identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
views.identityServerSetDefaultAlternativeTil.error = null
views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
}
.disposeOnDestroyView()
identityServerSetDefaultSubmit.debouncedClicks {
views.identityServerSetDefaultSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer)
}
identityServerSetDefaultAlternativeSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(identityServerSetDefaultAlternativeTextInput.text.toString()))
views.identityServerSetDefaultAlternativeSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseCustomIdentityServer(views.identityServerSetDefaultAlternativeTextInput.text.toString()))
}
viewModel.observeViewEvents {
@ -141,13 +146,13 @@ class SetIdentityServerFragment @Inject constructor(
.show()
} else {
// Display the error inlined
identityServerSetDefaultAlternativeTil.error = message
views.identityServerSetDefaultAlternativeTil.error = message
}
}
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.identity_server)
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.identity_server)
}
private val termsActivityResultLauncher = registerStartForActivityResult {

View file

@ -18,39 +18,44 @@
package im.vector.app.features.grouplist
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGroupListBinding
import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel
import kotlinx.android.synthetic.main.fragment_group_list.*
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import javax.inject.Inject
class GroupListFragment @Inject constructor(
val groupListViewModelFactory: GroupListViewModel.Factory,
private val groupController: GroupSummaryController
) : VectorBaseFragment(), GroupSummaryController.Callback {
) : VectorBaseFragment<FragmentGroupListBinding>(),
GroupSummaryController.Callback {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private val viewModel: GroupListViewModel by fragmentViewModel()
override fun getLayoutResId() = R.layout.fragment_group_list
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGroupListBinding {
return FragmentGroupListBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
groupController.callback = this
stateView.contentView = groupListView
groupListView.configureWith(groupController)
views.stateView.contentView = views.groupListView
views.groupListView.configureWith(groupController)
viewModel.observeViewEvents {
when (it) {
is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
@ -60,14 +65,14 @@ class GroupListFragment @Inject constructor(
override fun onDestroyView() {
groupController.callback = null
groupListView.cleanup()
views.groupListView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->
when (state.asyncGroups) {
is Incomplete -> stateView.state = StateView.State.Loading
is Success -> stateView.state = StateView.State.Content
is Incomplete -> views.stateView.state = StateView.State.Loading
is Success -> views.stateView.state = StateView.State.Content
}
groupController.update(state)
}

View file

@ -40,6 +40,7 @@ import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.notifications.NotificationDrawerManager
@ -56,9 +57,7 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import im.vector.app.push.fcm.FcmHelper
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.InitialSyncProgressService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.util.MatrixItem
@ -71,7 +70,11 @@ data class HomeActivityArgs(
val accountCreation: Boolean
) : Parcelable
class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory,
class HomeActivity :
VectorBaseActivity<ActivityHomeBinding>(),
ToolbarConfigurable,
UnknownDeviceDetectorSharedViewModel.Factory,
ServerBackupStatusViewModel.Factory,
NavigationInterceptor {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
@ -98,7 +101,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
}
}
override fun getLayoutRes() = R.layout.activity_home
override fun getBinding() = ActivityHomeBinding.inflate(layoutInflater)
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
@ -116,7 +119,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
super.onCreate(savedInstanceState)
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
drawerLayout.addDrawerListener(drawerListener)
views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) {
replaceFragment(R.id.homeDetailFragmentContainer, LoadingFragment::class.java)
replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
@ -126,10 +129,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START)
views.drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
}
}.exhaustive
@ -197,24 +200,24 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> {
waiting_view.isVisible = false
views.waitingView.root.isVisible = false
}
is InitialSyncProgressService.Status.Progressing -> {
Timber.v("${getString(status.statusText)} ${status.percentProgress}")
waiting_view.setOnClickListener {
views.waitingView.root.setOnClickListener {
// block interactions
}
waiting_view_status_horizontal_progress.apply {
views.waitingView.waitingHorizontalProgress.apply {
isIndeterminate = false
max = 100
progress = status.percentProgress
isVisible = true
}
waiting_view_status_text.apply {
views.waitingView.waitingStatusText.apply {
text = getString(status.statusText)
isVisible = true
}
waiting_view.isVisible = true
views.waitingView.root.isVisible = true
}
}.exhaustive
}
@ -269,7 +272,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
).apply {
colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
// action(it)
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS)
@ -282,7 +285,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
}, true)
addButton(getString(R.string.settings), Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
// action(it)
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS)
@ -292,7 +295,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
)
}
private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) {
private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity<*>) -> Unit)) {
popupAlertManager.postVectorAlert(
VerificationVectorAlert(
uid = "upgradeSecurity",
@ -303,7 +306,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
).apply {
colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
action(it)
}
}
@ -321,7 +324,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
}
override fun onDestroy() {
drawerLayout.removeDrawerListener(drawerListener)
views.drawerLayout.removeDrawerListener(drawerListener)
super.onDestroy()
}
@ -375,8 +378,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}

View file

@ -17,7 +17,9 @@
package im.vector.app.features.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import com.airbnb.mvrx.activityViewModel
@ -26,6 +28,7 @@ import com.airbnb.mvrx.withState
import com.google.android.material.badge.BadgeDrawable
import im.vector.app.R
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
@ -33,6 +36,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.ui.views.ActiveCallView
import im.vector.app.core.ui.views.ActiveCallViewHolder
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentHomeDetailBinding
import im.vector.app.features.call.SharedActiveCallViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.WebRtcPeerConnectionManager
@ -46,7 +50,7 @@ import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import kotlinx.android.synthetic.main.fragment_home_detail.*
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
@ -64,7 +68,10 @@ class HomeDetailFragment @Inject constructor(
private val alertManager: PopupAlertManager,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment(), KeysBackupBanner.Delegate, ActiveCallView.Callback, ServerBackupStatusViewModel.Factory {
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
KeysBackupBanner.Delegate,
ActiveCallView.Callback,
ServerBackupStatusViewModel.Factory {
private val viewModel: HomeDetailViewModel by fragmentViewModel()
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
@ -73,7 +80,9 @@ class HomeDetailFragment @Inject constructor(
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
override fun getLayoutResId() = R.layout.fragment_home_detail
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
return FragmentHomeDetailBinding.inflate(inflater, container, false)
}
private val activeCallViewHolder = ActiveCallViewHolder()
@ -89,7 +98,7 @@ class HomeDetailFragment @Inject constructor(
withState(viewModel) {
// Update the navigation view if needed (for when we restore the tabs)
bottomNavigationView.selectedItemId = it.displayMode.toMenuId()
views.bottomNavigationView.selectedItemId = it.displayMode.toMenuId()
}
viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary ->
@ -132,8 +141,8 @@ class HomeDetailFragment @Inject constructor(
}
private fun checkNotificationTabStatus() {
val wasVisible = bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible
bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
val wasVisible = views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
if (wasVisible && !vectorPreferences.labAddNotificationTab()) {
// As we hide it check if it's not the current item!
withState(viewModel) {
@ -156,7 +165,7 @@ class HomeDetailFragment @Inject constructor(
).apply {
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)
?.navigator
?.requestSessionVerification(requireContext(), newest.deviceId ?: "")
unknownDeviceDetectorSharedViewModel.handle(
@ -184,7 +193,7 @@ class HomeDetailFragment @Inject constructor(
).apply {
colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent)
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
// mark as ignored to avoid showing it again
unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
@ -204,7 +213,7 @@ class HomeDetailFragment @Inject constructor(
private fun onGroupChange(groupSummary: GroupSummary?) {
groupSummary?.let {
// Use GlideApp with activity context to avoid the glideRequests to be paused
avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
avatarRenderer.render(it.toMatrixItem(), views.groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
}
}
@ -212,20 +221,20 @@ class HomeDetailFragment @Inject constructor(
serverBackupStatusViewModel
.subscribe(this) {
when (val banState = it.bannerState.invoke()) {
is BannerState.Setup -> homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
BannerState.BackingUp -> homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
null,
BannerState.Hidden -> homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
}
}
homeKeysBackupBanner.delegate = this
views.homeKeysBackupBanner.delegate = this
}
private fun setupActiveCallView() {
activeCallViewHolder.bind(
activeCallPiP,
activeCallView,
activeCallPiPWrap,
views.activeCallPiP,
views.activeCallView,
views.activeCallPiPWrap,
this
)
}
@ -233,17 +242,17 @@ class HomeDetailFragment @Inject constructor(
private fun setupToolbar() {
val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(groupToolbar)
parentActivity.configure(views.groupToolbar)
}
groupToolbar.title = ""
groupToolbarAvatarImageView.debouncedClicks {
views.groupToolbar.title = ""
views.groupToolbarAvatarImageView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.OpenDrawer)
}
}
private fun setupBottomNavigationView() {
bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
bottomNavigationView.setOnNavigationItemSelectedListener {
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
views.bottomNavigationView.setOnNavigationItemSelectedListener {
val displayMode = when (it.itemId) {
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
@ -266,7 +275,7 @@ class HomeDetailFragment @Inject constructor(
}
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
groupToolbarTitleView.setText(displayMode.titleRes)
views.groupToolbarTitleView.setText(displayMode.titleRes)
updateSelectedFragment(displayMode)
}
@ -302,10 +311,10 @@ class HomeDetailFragment @Inject constructor(
override fun invalidate() = withState(viewModel) {
Timber.v(it.toString())
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
syncStateView.render(it.syncState)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
views.syncStateView.render(it.syncState)
}
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {

Some files were not shown because too many files have changed in this diff Show more