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: Other changes:
- - Migrate to ViewBindings (#1072)
Changes in Element 1.0.13 (2020-12-18) Changes in Element 1.0.13 (2020-12-18)
=================================================== ===================================================

View File

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

View File

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

View File

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

View File

@ -44,29 +44,29 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
override fun onResourceLoading(uid: String, placeholder: Drawable?) { override fun onResourceLoading(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = true holder.views.imageLoaderProgress.isVisible = true
} }
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) { override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false holder.views.imageLoaderProgress.isVisible = false
holder.touchImageView.setImageDrawable(errorDrawable) holder.views.imageView.setImageDrawable(errorDrawable)
} }
override fun onResourceCleared(uid: String, placeholder: Drawable?) { override fun onResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.touchImageView.setImageDrawable(placeholder) holder.views.imageView.setImageDrawable(placeholder)
} }
override fun onResourceReady(uid: String, resource: Drawable) { override fun onResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false holder.views.imageLoaderProgress.isVisible = false
// Glide mess up the view size :/ // Glide mess up the view size :/
holder.touchImageView.updateLayoutParams { holder.views.imageView.updateLayoutParams {
width = LinearLayout.LayoutParams.MATCH_PARENT width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.MATCH_PARENT
} }
holder.touchImageView.setImageDrawable(resource) holder.views.imageView.setImageDrawable(resource)
if (resource is Animatable) { if (resource is Animatable) {
resource.start() resource.start()
} }
@ -77,30 +77,30 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
override fun onResourceLoading(uid: String, placeholder: Drawable?) { override fun onResourceLoading(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = true holder.views.imageLoaderProgress.isVisible = true
holder.touchImageView.setImageDrawable(placeholder) holder.views.touchImageView.setImageDrawable(placeholder)
} }
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) { override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false holder.views.imageLoaderProgress.isVisible = false
holder.touchImageView.setImageDrawable(errorDrawable) holder.views.touchImageView.setImageDrawable(errorDrawable)
} }
override fun onResourceCleared(uid: String, placeholder: Drawable?) { override fun onResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.touchImageView.setImageDrawable(placeholder) holder.views.touchImageView.setImageDrawable(placeholder)
} }
override fun onResourceReady(uid: String, resource: Drawable) { override fun onResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.imageLoaderProgress.isVisible = false holder.views.imageLoaderProgress.isVisible = false
// Glide mess up the view size :/ // Glide mess up the view size :/
holder.touchImageView.updateLayoutParams { holder.views.touchImageView.updateLayoutParams {
width = LinearLayout.LayoutParams.MATCH_PARENT width = LinearLayout.LayoutParams.MATCH_PARENT
height = 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?) { override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.thumbnailImage.setImageDrawable(placeholder) holder.views.videoThumbnailImage.setImageDrawable(placeholder)
} }
override fun onThumbnailResourceReady(uid: String, resource: Drawable) { override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.thumbnailImage.setImageDrawable(resource) holder.views.videoThumbnailImage.setImageDrawable(resource)
} }
override fun onVideoFileLoading(uid: String) { override fun onVideoFileLoading(uid: String) {
if (holder.boundResourceUid != uid) return if (holder.boundResourceUid != uid) return
holder.thumbnailImage.isVisible = true holder.views.videoThumbnailImage.isVisible = true
holder.loaderProgressBar.isVisible = true holder.views.videoLoaderProgress.isVisible = true
holder.videoView.isVisible = false holder.views.videoView.isVisible = false
} }
override fun onVideoFileLoadFailed(uid: String) { override fun onVideoFileLoadFailed(uid: String) {
@ -82,8 +82,8 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
} }
private fun arrangeForVideoReady() { private fun arrangeForVideoReady() {
holder.thumbnailImage.isVisible = false holder.views.videoThumbnailImage.isVisible = false
holder.loaderProgressBar.isVisible = false holder.views.videoLoaderProgress.isVisible = false
holder.videoView.isVisible = true holder.views.videoView.isVisible = true
} }
} }

View File

@ -18,11 +18,8 @@ package im.vector.lib.attachmentviewer
import android.util.Log import android.util.Log
import android.view.View 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 androidx.core.view.isVisible
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -44,13 +41,9 @@ class VideoViewHolder constructor(itemView: View) :
var eventListener: WeakReference<AttachmentEventListener>? = null var eventListener: WeakReference<AttachmentEventListener>? = null
val thumbnailImage: ImageView = itemView.findViewById(R.id.videoThumbnailImage) val views = ItemVideoAttachmentBinding.bind(itemView)
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)
internal val target = DefaultVideoLoaderTarget(this, thumbnailImage) internal val target = DefaultVideoLoaderTarget(this, views.videoThumbnailImage)
override fun onRecycled() { override fun onRecycled() {
super.onRecycled() super.onRecycled()
@ -77,12 +70,12 @@ class VideoViewHolder constructor(itemView: View) :
} }
override fun entersBackground() { override fun entersBackground() {
if (videoView.isPlaying) { if (views.videoView.isPlaying) {
progress = videoView.currentPosition progress = views.videoView.currentPosition
progressDisposable?.dispose() progressDisposable?.dispose()
progressDisposable = null progressDisposable = null
videoView.stopPlayback() views.videoView.stopPlayback()
videoView.pause() views.videoView.pause()
} }
} }
@ -92,9 +85,9 @@ class VideoViewHolder constructor(itemView: View) :
override fun onSelected(selected: Boolean) { override fun onSelected(selected: Boolean) {
if (!selected) { if (!selected) {
if (videoView.isPlaying) { if (views.videoView.isPlaying) {
progress = videoView.currentPosition progress = views.videoView.currentPosition
videoView.stopPlayback() views.videoView.stopPlayback()
} else { } else {
progress = 0 progress = 0
} }
@ -109,34 +102,34 @@ class VideoViewHolder constructor(itemView: View) :
} }
private fun startPlaying() { private fun startPlaying() {
thumbnailImage.isVisible = false views.videoThumbnailImage.isVisible = false
loaderProgressBar.isVisible = false views.videoLoaderProgress.isVisible = false
videoView.isVisible = true views.videoView.isVisible = true
videoView.setOnPreparedListener { views.videoView.setOnPreparedListener {
progressDisposable?.dispose() progressDisposable?.dispose()
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS) progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
.timeInterval() .timeInterval()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
val duration = videoView.duration val duration = views.videoView.duration
val progress = videoView.currentPosition val progress = views.videoView.currentPosition
val isPlaying = videoView.isPlaying val isPlaying = views.videoView.isPlaying
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration") // Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
} }
} }
try { try {
videoView.setVideoPath(mVideoPath) views.videoView.setVideoPath(mVideoPath)
} catch (failure: Throwable) { } catch (failure: Throwable) {
// Couldn't open // Couldn't open
Log.v(VideoViewHolder::class.java.name, "Failed to start video") Log.v(VideoViewHolder::class.java.name, "Failed to start video")
} }
if (!wasPaused) { if (!wasPaused) {
videoView.start() views.videoView.start()
if (progress > 0) { if (progress > 0) {
videoView.seekTo(progress) views.videoView.seekTo(progress)
} }
} }
} }
@ -146,17 +139,17 @@ class VideoViewHolder constructor(itemView: View) :
when (commands) { when (commands) {
AttachmentCommands.StartVideo -> { AttachmentCommands.StartVideo -> {
wasPaused = false wasPaused = false
videoView.start() views.videoView.start()
} }
AttachmentCommands.PauseVideo -> { AttachmentCommands.PauseVideo -> {
wasPaused = true wasPaused = true
videoView.pause() views.videoView.pause()
} }
is AttachmentCommands.SeekTo -> { is AttachmentCommands.SeekTo -> {
val duration = videoView.duration val duration = views.videoView.duration
if (duration > 0) { if (duration > 0) {
val seekDuration = duration * (commands.percentProgress / 100f) 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 package im.vector.lib.attachmentviewer
import android.view.View import android.view.View
import android.widget.ProgressBar import im.vector.lib.attachmentviewer.databinding.ItemImageAttachmentBinding
import com.github.chrisbanes.photoview.PhotoView
class ZoomableImageViewHolder constructor(itemView: View) : class ZoomableImageViewHolder constructor(itemView: View) :
BaseViewHolder(itemView) { BaseViewHolder(itemView) {
val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView) val views = ItemImageAttachmentBinding.bind(itemView)
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
init { init {
touchImageView.setAllowParentInterceptOnEdge(false) views.touchImageView.setAllowParentInterceptOnEdge(false)
touchImageView.setOnScaleChangeListener { scaleFactor, _, _ -> views.touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
// Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor") // Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor")
// It's a bit annoying but when you pitch down the scaling // It's a bit annoying but when you pitch down the scaling
// is not exactly one :/ // is not exactly one :/
touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f) views.touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
} }
touchImageView.setScale(1.0f, true) views.touchImageView.setScale(1.0f, true)
touchImageView.setAllowParentInterceptOnEdge(true) views.touchImageView.setAllowParentInterceptOnEdge(true)
} }
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView) internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, views.touchImageView)
override fun onRecycled() { override fun onRecycled() {
super.onRecycled() super.onRecycled()
touchImageView.setImageDrawable(null) views.touchImageView.setImageDrawable(null)
} }
} }

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.auth.registration package org.matrix.android.sdk.internal.auth.registration
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
/** /**
* This class represent a localized privacy policy for registration Flow. * 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 android.os.Parcelable
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? { fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
// Check the validity of some fields // Check the validity of some fields

View File

@ -16,7 +16,7 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 29 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 ### This is generally not necessary, no need to reset the padding if there is no drawable
setCompoundDrawablePadding\(0\) setCompoundDrawablePadding\(0\)
### Deprecated use class form SDK API 26
ButterKnife\.findById\(
# Change thread with Rx # Change thread with Rx
# DISABLED # DISABLED
#runOnUiThread #runOnUiThread
@ -175,3 +172,6 @@ getSystemService\(Context
### Use DefaultSharedPreferences.getInstance() instead for better performance ### Use DefaultSharedPreferences.getInstance() instead for better performance
PreferenceManager\.getDefaultSharedPreferences==2 PreferenceManager\.getDefaultSharedPreferences==2
### Use ViewBindings
# findViewById

View File

@ -3,7 +3,7 @@ package ${escapeKotlinIdentifiers(packageName)}
import android.os.Bundle import android.os.Bundle
<#if createFragmentArgs> <#if createFragmentArgs>
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
</#if> </#if>
import android.view.View import android.view.View
@ -36,8 +36,8 @@ class ${fragmentClass} @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView()
// Clear your view, unsubscribe... // Clear your view, unsubscribe...
super.onDestroyView()
} }
override fun invalidate() = withState(viewModel) { state -> 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.android.application'
apply plugin: 'com.google.android.gms.oss-licenses-plugin' apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
kapt { kapt {
correctErrorTypes = true correctErrorTypes = true
} }
androidExtensions {
experimental = true
}
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 0 ext.versionMinor = 0
@ -280,6 +276,10 @@ android {
java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/sharedTest/java"
} }
} }
buildFeatures {
viewBinding true
}
} }
dependencies { dependencies {
@ -386,10 +386,6 @@ dependencies {
implementation 'com.otaliastudios:autocomplete:1.1.0' 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 // Shake detection
implementation 'com.squareup:seismic:1.0.2' implementation 'com.squareup:seismic:1.0.2'

View File

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

View File

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

View File

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

View File

@ -19,28 +19,18 @@ package im.vector.app.features.debug
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity 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.R
import im.vector.app.databinding.ActivityTestLinkifyBinding
import im.vector.app.databinding.ItemTestLinkifyBinding
class TestLinkifyActivity : AppCompatActivity() { 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_linkify) val views = ActivityTestLinkifyBinding.inflate(layoutInflater)
ButterKnife.bind(this) setContentView(views.root)
views.testLinkifyContentView.removeAllViews()
scrollContent.removeAllViews()
listOf( listOf(
"https://www.html5rocks.com/en/tutorials/webrtc/basics/ |", "https://www.html5rocks.com/en/tutorials/webrtc/basics/ |",
@ -89,43 +79,42 @@ class TestLinkifyActivity : AppCompatActivity() {
) )
.forEach { textContent -> .forEach { textContent ->
val item = LayoutInflater.from(this) val item = LayoutInflater.from(this)
.inflate(R.layout.item_test_linkify, scrollContent, false) .inflate(R.layout.item_test_linkify, views.testLinkifyContentView, false)
val subViews = ItemTestLinkifyBinding.bind(item)
item.findViewById<TextView>(R.id.test_linkify_auto_text) subViews.testLinkifyAutoText.apply {
?.apply { text = textContent
text = textContent /* TODO Use BetterLinkMovementMethod when the other PR is merged
/* TODO Use BetterLinkMovementMethod when the other PR is merged movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() {
movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() { override fun onURLClick(uri: Uri?) {
override fun onURLClick(uri: Uri?) { Snackbar.make(coordinatorLayout, "URI Clicked: $uri", Snackbar.LENGTH_LONG)
Snackbar.make(coordinatorLayout, "URI Clicked: $uri", Snackbar.LENGTH_LONG) .setAction("open") {
.setAction("open") { openUrlInExternalBrowser(this@TestLinkifyActivity, uri)
openUrlInExternalBrowser(this@TestLinkifyActivity, uri) }
} .show()
.show()
}
})
*/
} }
})
*/
}
item.findViewById<TextView>(R.id.test_linkify_custom_text) subViews.testLinkifyCustomText.apply {
?.apply { text = textContent
text = textContent /* TODO Use BetterLinkMovementMethod when the other PR is merged
/* TODO Use BetterLinkMovementMethod when the other PR is merged movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() {
movementMethod = MatrixLinkMovementMethod(object : MockMessageAdapterActionListener() { override fun onURLClick(uri: Uri?) {
override fun onURLClick(uri: Uri?) { Snackbar.make(coordinatorLayout, "URI Clicked: $uri", Snackbar.LENGTH_LONG)
Snackbar.make(coordinatorLayout, "URI Clicked: $uri", Snackbar.LENGTH_LONG) .setAction("open") {
.setAction("open") { openUrlInExternalBrowser(this@TestLinkifyActivity, uri)
openUrlInExternalBrowser(this@TestLinkifyActivity, uri) }
} .show()
.show()
}
})
*/
// TODO Call VectorLinkify.addLinks(text)
} }
})
*/
scrollContent.addView(item, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) // TODO Call VectorLinkify.addLinks(text)
}
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 android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis import org.matrix.android.sdk.api.crypto.getAllVerificationEmojis
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
class DebugSasEmojiActivity : AppCompatActivity() { class DebugSasEmojiActivity : AppCompatActivity() {
private lateinit var views: FragmentGenericRecyclerBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_generic_recycler) views = FragmentGenericRecyclerBinding.inflate(layoutInflater)
setContentView(views.root)
val controller = SasEmojiController() val controller = SasEmojiController()
genericRecyclerView.configureWith(controller) views.genericRecyclerView.configureWith(controller)
controller.setData(SasState(getAllVerificationEmojis())) controller.setData(SasState(getAllVerificationEmojis()))
} }
override fun onDestroy() { override fun onDestroy() {
genericRecyclerView.cleanup() views.genericRecyclerView.cleanup()
super.onDestroy() super.onDestroy()
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,12 +16,11 @@
package im.vector.app.core.dialogs package im.vector.app.core.dialogs
import android.app.Activity import android.app.Activity
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.DialogSslFingerprintBinding
import org.matrix.android.sdk.internal.network.ssl.Fingerprint import org.matrix.android.sdk.internal.network.ssl.Fingerprint
import timber.log.Timber import timber.log.Timber
import java.util.HashMap import java.util.HashMap
@ -95,30 +94,27 @@ class UnrecognizedCertificateDialog @Inject constructor(
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater val inflater = activity.layoutInflater
val layout: View = inflater.inflate(R.layout.dialog_ssl_fingerprint, null) val layout = inflater.inflate(R.layout.dialog_ssl_fingerprint, null)
val sslFingerprintTitle = layout.findViewById<TextView>(R.id.ssl_fingerprint_title) val views = DialogSslFingerprintBinding.bind(layout)
sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString()) views.sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString())
val sslFingerprint = layout.findViewById<TextView>(R.id.ssl_fingerprint) views.sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr
sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr
val sslUserId = layout.findViewById<TextView>(R.id.ssl_user_id)
if (userId != null) { 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), stringProvider.getString(R.string.username),
userId) userId)
} else { } 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), stringProvider.getString(R.string.hs_url),
homeServerUrl) homeServerUrl)
} }
val sslExpl = layout.findViewById<TextView>(R.id.ssl_explanation)
if (existing) { if (existing) {
if (homeServerConnectionConfigHasFingerprints) { if (homeServerConnectionConfigHasFingerprints) {
sslExpl.text = stringProvider.getString(R.string.ssl_expected_existing_expl) views.sslExplanation.text = stringProvider.getString(R.string.ssl_expected_existing_expl)
} else { } else {
sslExpl.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl) views.sslExplanation.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl)
} }
} else { } 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.setView(layout)
builder.setTitle(R.string.ssl_could_not_verify) 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.ActivityResult
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import im.vector.app.core.platform.VectorBaseActivity
fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> { fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult) return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
} }
fun VectorBaseActivity.addFragment( fun AppCompatActivity.addFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
allowStateLoss: Boolean = false allowStateLoss: Boolean = false
@ -39,7 +39,7 @@ fun VectorBaseActivity.addFragment(
supportFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) } supportFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
} }
fun <T : Fragment> VectorBaseActivity.addFragment( fun <T : Fragment> AppCompatActivity.addFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -51,7 +51,7 @@ fun <T : Fragment> VectorBaseActivity.addFragment(
} }
} }
fun VectorBaseActivity.replaceFragment( fun AppCompatActivity.replaceFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -60,7 +60,7 @@ fun VectorBaseActivity.replaceFragment(
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) } supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
} }
fun <T : Fragment> VectorBaseActivity.replaceFragment( fun <T : Fragment> AppCompatActivity.replaceFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -72,7 +72,7 @@ fun <T : Fragment> VectorBaseActivity.replaceFragment(
} }
} }
fun VectorBaseActivity.addFragmentToBackstack( fun AppCompatActivity.addFragmentToBackstack(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -81,19 +81,20 @@ fun VectorBaseActivity.addFragmentToBackstack(
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) } supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
} }
fun <T : Fragment> VectorBaseActivity.addFragmentToBackstack(frameId: Int, fun <T : Fragment> AppCompatActivity.addFragmentToBackstack(
fragmentClass: Class<T>, frameId: Int,
params: Parcelable? = null, fragmentClass: Class<T>,
tag: String? = null, params: Parcelable? = null,
allowStateLoss: Boolean = false, tag: String? = null,
option: ((FragmentTransaction) -> Unit)? = null) { allowStateLoss: Boolean = false,
option: ((FragmentTransaction) -> Unit)? = null) {
supportFragmentManager.commitTransaction(allowStateLoss) { supportFragmentManager.commitTransaction(allowStateLoss) {
option?.invoke(this) option?.invoke(this)
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag) replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
} }
} }
fun VectorBaseActivity.hideKeyboard() { fun AppCompatActivity.hideKeyboard() {
currentFocus?.hideKeyboard() currentFocus?.hideKeyboard()
} }

View File

@ -24,7 +24,6 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.core.utils.selectTxtFileToWrite
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -34,7 +33,7 @@ fun Fragment.registerStartForActivityResult(onResult: (ActivityResult) -> Unit):
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult) return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
} }
fun VectorBaseFragment.addFragment( fun Fragment.addFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
allowStateLoss: Boolean = false allowStateLoss: Boolean = false
@ -42,7 +41,7 @@ fun VectorBaseFragment.addFragment(
parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) } parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
} }
fun <T : Fragment> VectorBaseFragment.addFragment( fun <T : Fragment> Fragment.addFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -54,7 +53,7 @@ fun <T : Fragment> VectorBaseFragment.addFragment(
} }
} }
fun VectorBaseFragment.replaceFragment( fun Fragment.replaceFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
allowStateLoss: Boolean = false allowStateLoss: Boolean = false
@ -62,7 +61,7 @@ fun VectorBaseFragment.replaceFragment(
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment) } parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment) }
} }
fun <T : Fragment> VectorBaseFragment.replaceFragment( fun <T : Fragment> Fragment.replaceFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -74,7 +73,7 @@ fun <T : Fragment> VectorBaseFragment.replaceFragment(
} }
} }
fun VectorBaseFragment.addFragmentToBackstack( fun Fragment.addFragmentToBackstack(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -83,7 +82,7 @@ fun VectorBaseFragment.addFragmentToBackstack(
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag).addToBackStack(tag) } parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag).addToBackStack(tag) }
} }
fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack( fun <T : Fragment> Fragment.addFragmentToBackstack(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -95,7 +94,7 @@ fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(
} }
} }
fun VectorBaseFragment.addChildFragment( fun Fragment.addChildFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -104,7 +103,7 @@ fun VectorBaseFragment.addChildFragment(
childFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment, tag) } childFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment, tag) }
} }
fun <T : Fragment> VectorBaseFragment.addChildFragment( fun <T : Fragment> Fragment.addChildFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -116,7 +115,7 @@ fun <T : Fragment> VectorBaseFragment.addChildFragment(
} }
} }
fun VectorBaseFragment.replaceChildFragment( fun Fragment.replaceChildFragment(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -125,7 +124,7 @@ fun VectorBaseFragment.replaceChildFragment(
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) } childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
} }
fun <T : Fragment> VectorBaseFragment.replaceChildFragment( fun <T : Fragment> Fragment.replaceChildFragment(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, params: Parcelable? = null,
@ -137,7 +136,7 @@ fun <T : Fragment> VectorBaseFragment.replaceChildFragment(
} }
} }
fun VectorBaseFragment.addChildFragmentToBackstack( fun Fragment.addChildFragmentToBackstack(
frameId: Int, frameId: Int,
fragment: Fragment, fragment: Fragment,
tag: String? = null, tag: String? = null,
@ -146,7 +145,7 @@ fun VectorBaseFragment.addChildFragmentToBackstack(
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) } childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
} }
fun <T : Fragment> VectorBaseFragment.addChildFragmentToBackstack( fun <T : Fragment> Fragment.addChildFragmentToBackstack(
frameId: Int, frameId: Int,
fragmentClass: Class<T>, fragmentClass: Class<T>,
params: Parcelable? = null, 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.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R 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) class ButtonStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) { : FrameLayout(context, attrs, defStyle) {
@ -47,11 +46,15 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
// Big or Flat button // Big or Flat button
var button: Button var button: Button
private val views: ViewButtonStateBinding
init { 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) layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
buttonStateRetry.setOnClickListener { views.buttonStateRetry.setOnClickListener {
callback?.onRetryClicked() callback?.onRetryClicked()
} }
@ -63,15 +66,15 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
.apply { .apply {
try { try {
if (getBoolean(R.styleable.ButtonStateView_bsv_use_flat_button, true)) { if (getBoolean(R.styleable.ButtonStateView_bsv_use_flat_button, true)) {
button = buttonStateButtonFlat button = views.buttonStateButtonFlat
buttonStateButtonBig.isVisible = false views.buttonStateButtonBig.isVisible = false
} else { } else {
button = buttonStateButtonBig button = views.buttonStateButtonBig
buttonStateButtonFlat.isVisible = false views.buttonStateButtonFlat.isVisible = false
} }
button.text = getString(R.styleable.ButtonStateView_bsv_button_text) 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 { } finally {
recycle() recycle()
} }
@ -90,8 +93,8 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu
button.isInvisible = true button.isInvisible = true
} }
buttonStateLoading.isVisible = newState == State.Loading views.buttonStateLoading.isVisible = newState == State.Loading
buttonStateLoaded.isVisible = newState == State.Loaded views.buttonStateLoaded.isVisible = newState == State.Loaded
buttonStateRetry.isVisible = newState == State.Error views.buttonStateRetry.isVisible = newState == State.Error
} }
} }

View File

@ -15,37 +15,25 @@
*/ */
package im.vector.app.core.platform package im.vector.app.core.platform
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible 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.di.ScreenComponent
import im.vector.app.core.extensions.hideKeyboard 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 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. * 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) final override fun getCoordinatorLayout() = views.coordinatorLayout
lateinit var waitingCircularProgress: View
@BindView(R.id.waiting_view_status_text) lateinit var session: Session
lateinit var waitingStatusText: TextView
@BindView(R.id.waiting_view_status_horizontal_progress)
lateinit var waitingHorizontalProgress: ProgressBar
@Inject lateinit var session: Session
@CallSuper @CallSuper
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
@ -53,8 +41,8 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
} }
override fun initUiAndData() { override fun initUiAndData() {
configureToolbar(toolbar) configureToolbar(views.toolbar)
waitingView = findViewById(R.id.waiting_view) waitingView = views.waitingView.waitingView
} }
/** /**
@ -63,21 +51,21 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
*/ */
fun updateWaitingView(data: WaitingViewData?) { fun updateWaitingView(data: WaitingViewData?) {
data?.let { data?.let {
waitingStatusText.text = data.message views.waitingView.waitingStatusText.text = data.message
if (data.progress != null && data.progressTotal != null) { if (data.progress != null && data.progressTotal != null) {
waitingHorizontalProgress.isIndeterminate = false views.waitingView.waitingHorizontalProgress.isIndeterminate = false
waitingHorizontalProgress.progress = data.progress views.waitingView.waitingHorizontalProgress.progress = data.progress
waitingHorizontalProgress.max = data.progressTotal views.waitingView.waitingHorizontalProgress.max = data.progressTotal
waitingHorizontalProgress.isVisible = true views.waitingView.waitingHorizontalProgress.isVisible = true
waitingCircularProgress.isVisible = false views.waitingView.waitingCircularProgress.isVisible = false
} else if (data.isIndeterminate) { } else if (data.isIndeterminate) {
waitingHorizontalProgress.isIndeterminate = true views.waitingView.waitingHorizontalProgress.isIndeterminate = true
waitingHorizontalProgress.isVisible = true views.waitingView.waitingHorizontalProgress.isVisible = true
waitingCircularProgress.isVisible = false views.waitingView.waitingCircularProgress.isVisible = false
} else { } else {
waitingHorizontalProgress.isVisible = false views.waitingView.waitingHorizontalProgress.isVisible = false
waitingCircularProgress.isVisible = true views.waitingView.waitingCircularProgress.isVisible = true
} }
showWaitingView() showWaitingView()
@ -86,17 +74,17 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
} }
} }
override fun showWaitingView() { override fun showWaitingView(text: String?) {
hideKeyboard() hideKeyboard()
waitingStatusText.isGone = waitingStatusText.text.isNullOrBlank() views.waitingView.waitingStatusText.isGone = views.waitingView.waitingStatusText.text.isNullOrBlank()
super.showWaitingView() super.showWaitingView(text)
} }
override fun hideWaitingView() { override fun hideWaitingView() {
waitingStatusText.text = null views.waitingView.waitingStatusText.text = null
waitingStatusText.isGone = true views.waitingView.waitingStatusText.isGone = true
waitingHorizontalProgress.progress = 0 views.waitingView.waitingHorizontalProgress.progress = 0
waitingHorizontalProgress.isVisible = false views.waitingView.waitingHorizontalProgress.isVisible = false
super.hideWaitingView() super.hideWaitingView()
} }

View File

@ -24,7 +24,7 @@ import android.widget.FrameLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.updateConstraintSet 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) class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) { : FrameLayout(context, attrs, defStyle) {
@ -42,6 +42,8 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
data class Error(val message: CharSequence? = null) : State() data class Error(val message: CharSequence? = null) : State()
} }
private val views: ViewStateBinding
var eventCallback: EventCallback? = null var eventCallback: EventCallback? = null
var contentView: View? = null var contentView: View? = null
@ -58,33 +60,34 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
} }
init { 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) layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
errorRetryView.setOnClickListener { views.errorRetryView.setOnClickListener {
eventCallback?.onRetryClicked() eventCallback?.onRetryClicked()
} }
state = State.Content state = State.Content
} }
private fun update(newState: State) { private fun update(newState: State) {
progressBar.isVisible = newState is State.Loading views.progressBar.isVisible = newState is State.Loading
errorView.isVisible = newState is State.Error views.errorView.isVisible = newState is State.Error
emptyView.isVisible = newState is State.Empty views.emptyView.isVisible = newState is State.Empty
contentView?.isVisible = newState is State.Content contentView?.isVisible = newState is State.Content
when (newState) { when (newState) {
is State.Content -> Unit is State.Content -> Unit
is State.Loading -> Unit is State.Loading -> Unit
is State.Empty -> { is State.Empty -> {
emptyImageView.setImageDrawable(newState.image) views.emptyImageView.setImageDrawable(newState.image)
emptyView.updateConstraintSet { views.emptyView.updateConstraintSet {
it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f) it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f)
} }
emptyMessageView.text = newState.message views.emptyMessageView.text = newState.message
emptyTitleView.text = newState.title views.emptyTitleView.text = newState.title
} }
is State.Error -> { 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.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes import androidx.annotation.CallSuper
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -40,10 +39,7 @@ import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import butterknife.BindView import androidx.viewbinding.ViewBinding
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.app.BuildConfig 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.observeNotNull
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart 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.extensions.vectorComponent
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity 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.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber import timber.log.Timber
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
/* ========================================================================================== /* ==========================================================================================
* UI * View
* ========================================================================================== */ * ========================================================================================== */
@Nullable protected lateinit var views: VB
@JvmField
@BindView(R.id.vector_coordinator_layout)
var coordinatorLayout: CoordinatorLayout? = null
/* ========================================================================================== /* ==========================================================================================
* View model * View model
@ -138,8 +133,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
// Filter for multiple invalid token error // Filter for multiple invalid token error
private var mainActivityStarted = false private var mainActivityStarted = false
private var unBinder: Unbinder? = null
private var savedInstanceState: Bundle? = null private var savedInstanceState: Bundle? = null
// For debug only // For debug only
@ -176,6 +169,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
uiDisposables.add(this) uiDisposables.add(this)
} }
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("onCreate Activity ${javaClass.simpleName}") Timber.i("onCreate Activity ${javaClass.simpleName}")
val vectorComponent = getVectorComponent() val vectorComponent = getVectorComponent()
@ -223,11 +217,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
// Hack for font size // Hack for font size
applyFontSize() applyFontSize()
if (getLayoutRes() != -1) { views = getBinding()
setContentView(getLayoutRes()) setContentView(views.root)
}
unBinder = ButterKnife.bind(this)
this.savedInstanceState = savedInstanceState this.savedInstanceState = savedInstanceState
@ -306,8 +297,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
Timber.i("onDestroy Activity ${javaClass.simpleName}") Timber.i("onDestroy Activity ${javaClass.simpleName}")
unBinder?.unbind()
unBinder = null
uiDisposables.dispose() uiDisposables.dispose()
} }
@ -467,7 +456,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
} }
private fun recursivelyDispatchOnBackPressed(fm: FragmentManager, fromToolbar: Boolean): Boolean { 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) { for (f in reverseOrder) {
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager, fromToolbar) val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager, fromToolbar)
if (handledByChildFragments) { 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) // Handle loading view (also called waiting view or spinner view)
// ============================================================================================== // ==============================================================================================
@ -537,10 +522,13 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
fun isWaitingViewVisible() = waitingView?.isVisible == true 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 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 * OPEN METHODS
* ========================================================================================== */ * ========================================================================================== */
@LayoutRes abstract fun getBinding(): VB
open fun getLayoutRes() = -1
open fun displayInFullscreen() = false open fun displayInFullscreen() = false
@ -582,13 +569,13 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
* ========================================================================================== */ * ========================================================================================== */
fun showSnackbar(message: String) { fun showSnackbar(message: String) {
coordinatorLayout?.let { getCoordinatorLayout()?.let {
Snackbar.make(it, message, Snackbar.LENGTH_SHORT).show() Snackbar.make(it, message, Snackbar.LENGTH_SHORT).show()
} }
} }
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) { fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
coordinatorLayout?.let { getCoordinatorLayout()?.let {
Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply { Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
withActionTitle?.let { withActionTitle?.let {
setAction(withActionTitle, { action?.invoke() }) setAction(withActionTitle, { action?.invoke() })
@ -597,6 +584,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
} }
} }
open fun getCoordinatorLayout(): CoordinatorLayout? = null
/* ========================================================================================== /* ==========================================================================================
* User Consent * User Consent
* ========================================================================================== */ * ========================================================================================== */

View File

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

View File

@ -28,15 +28,12 @@ import android.view.MenuInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import butterknife.ButterKnife import androidx.viewbinding.ViewBinding
import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding3.view.clicks 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.di.ScreenComponent
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScreenInjector {
// Butterknife unbinder protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
private var mUnBinder: Unbinder? = null activity as VectorBaseActivity<*>
protected val vectorBaseActivity: VectorBaseActivity by lazy {
activity as VectorBaseActivity
} }
/* ========================================================================================== /* ==========================================================================================
@ -86,6 +82,16 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
protected val fragmentViewModelProvider protected val fragmentViewModelProvider
get() = ViewModelProvider(this, viewModelFactory) 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 * Life cycle
* ========================================================================================== */ * ========================================================================================== */
@ -110,11 +116,11 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Timber.i("onCreateView Fragment ${javaClass.simpleName}") Timber.i("onCreateView Fragment ${javaClass.simpleName}")
return inflater.inflate(getLayoutResId(), container, false) _binding = getBinding(inflater, container)
return views.root
} }
@LayoutRes abstract fun getBinding(inflater: LayoutInflater, container: ViewGroup?): VB
abstract fun getLayoutResId(): Int
@CallSuper @CallSuper
override fun onResume() { override fun onResume() {
@ -125,7 +131,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper @CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mUnBinder = ButterKnife.bind(this, view) Timber.i("onViewCreated Fragment ${javaClass.simpleName}")
} }
open fun showLoading(message: CharSequence?) { open fun showLoading(message: CharSequence?) {
@ -138,11 +144,10 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView()
Timber.i("onDestroyView Fragment ${javaClass.simpleName}") Timber.i("onDestroyView Fragment ${javaClass.simpleName}")
mUnBinder?.unbind()
mUnBinder = null
uiDisposables.clear() uiDisposables.clear()
_binding = null
super.onDestroyView()
} }
override fun onDestroy() { override fun onDestroy() {
@ -180,10 +185,6 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
arguments = args.toMvRxBundle() arguments = args.toMvRxBundle()
} }
fun Parcelable?.toMvRxBundle(): Bundle? {
return this?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}
@MainThread @MainThread
protected fun <T : Restorable> T.register(): T { protected fun <T : Restorable> T.register(): T {
assertMainThread() assertMainThread()
@ -192,7 +193,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
} }
protected fun showErrorInSnackbar(throwable: Throwable) { protected fun showErrorInSnackbar(throwable: Throwable) {
vectorBaseActivity.coordinatorLayout?.let { vectorBaseActivity.getCoordinatorLayout()?.let {
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
.show() .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 package im.vector.app.core.ui.bottomsheet
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView 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.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetGenericListBinding
import javax.inject.Inject import javax.inject.Inject
/** /**
* Generic Bottom sheet with actions * Generic Bottom sheet with actions
*/ */
abstract class BottomSheetGeneric<STATE : BottomSheetGenericState, ACTION : BottomSheetGenericAction> : abstract class BottomSheetGeneric<STATE : BottomSheetGenericState, ACTION : BottomSheetGenericAction> :
VectorBaseBottomSheetDialogFragment(), VectorBaseBottomSheetDialogFragment<BottomSheetGenericListBinding>(),
BottomSheetGenericController.Listener<ACTION> { BottomSheetGenericController.Listener<ACTION> {
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
final override val showExpanded = true 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> abstract fun getController(): BottomSheetGenericController<STATE, ACTION>
@CallSuper @CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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 getController().listener = this
} }
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
recyclerView.cleanup() views.bottomSheetRecyclerView.cleanup()
getController().listener = null getController().listener = null
super.onDestroyView() super.onDestroyView()
} }

View File

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

View File

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

View File

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

View File

@ -19,15 +19,13 @@ package im.vector.app.core.ui.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.databinding.ViewKeysBackupBannerBinding
import timber.log.Timber import timber.log.Timber
/** /**
@ -40,21 +38,11 @@ class KeysBackupBanner @JvmOverloads constructor(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { ) : 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 var delegate: Delegate? = null
private var state: State = State.Initial private var state: State = State.Initial
private lateinit var views: ViewKeysBackupBannerBinding
init { init {
setupView() setupView()
DefaultSharedPreferences.getInstance(context).edit { DefaultSharedPreferences.getInstance(context).edit {
@ -100,8 +88,7 @@ class KeysBackupBanner @JvmOverloads constructor(
} }
} }
@OnClick(R.id.view_keys_backup_banner_close) private fun onCloseClicked() {
internal fun onCloseClicked() {
state.let { state.let {
when (it) { when (it) {
is State.Setup -> { is State.Setup -> {
@ -133,11 +120,12 @@ class KeysBackupBanner @JvmOverloads constructor(
private fun setupView() { private fun setupView() {
inflate(context, R.layout.view_keys_backup_banner, this) inflate(context, R.layout.view_keys_backup_banner, this)
ButterKnife.bind(this)
setOnClickListener(this) setOnClickListener(this)
textView1.setOnClickListener(this) views = ViewKeysBackupBannerBinding.bind(this)
textView2.setOnClickListener(this) views.viewKeysBackupBannerText1.setOnClickListener(this)
views.viewKeysBackupBannerText2.setOnClickListener(this)
views.viewKeysBackupBannerClose.setOnClickListener { onCloseClicked() }
} }
private fun renderInitial() { private fun renderInitial() {
@ -156,10 +144,10 @@ class KeysBackupBanner @JvmOverloads constructor(
} else { } else {
isVisible = true isVisible = true
textView1.setText(R.string.secure_backup_banner_setup_line1) views.viewKeysBackupBannerText1.setText(R.string.secure_backup_banner_setup_line1)
textView2.isVisible = true views.viewKeysBackupBannerText2.isVisible = true
textView2.setText(R.string.secure_backup_banner_setup_line2) views.viewKeysBackupBannerText2.setText(R.string.secure_backup_banner_setup_line2)
close.isVisible = true views.viewKeysBackupBannerCloseGroup.isVisible = true
} }
} }
@ -169,10 +157,10 @@ class KeysBackupBanner @JvmOverloads constructor(
} else { } else {
isVisible = true isVisible = true
textView1.setText(R.string.keys_backup_banner_recover_line1) views.viewKeysBackupBannerText1.setText(R.string.keys_backup_banner_recover_line1)
textView2.isVisible = true views.viewKeysBackupBannerText2.isVisible = true
textView2.setText(R.string.keys_backup_banner_recover_line2) views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_recover_line2)
close.isVisible = true views.viewKeysBackupBannerCloseGroup.isVisible = true
} }
} }
@ -182,28 +170,28 @@ class KeysBackupBanner @JvmOverloads constructor(
} else { } else {
isVisible = true isVisible = true
textView1.setText(R.string.keys_backup_banner_update_line1) views.viewKeysBackupBannerText1.setText(R.string.keys_backup_banner_update_line1)
textView2.isVisible = true views.viewKeysBackupBannerText2.isVisible = true
textView2.setText(R.string.keys_backup_banner_update_line2) views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_update_line2)
close.isVisible = true views.viewKeysBackupBannerCloseGroup.isVisible = true
} }
} }
private fun renderBackingUp() { private fun renderBackingUp() {
isVisible = true isVisible = true
textView1.setText(R.string.secure_backup_banner_setup_line1) views.viewKeysBackupBannerText1.setText(R.string.secure_backup_banner_setup_line1)
textView2.isVisible = true views.viewKeysBackupBannerText2.isVisible = true
textView2.setText(R.string.keys_backup_banner_in_progress) views.viewKeysBackupBannerText2.setText(R.string.keys_backup_banner_in_progress)
loading.isVisible = true views.viewKeysBackupBannerLoading.isVisible = true
} }
/** /**
* Hide all views that are not visible in all state * Hide all views that are not visible in all state
*/ */
private fun hideAll() { private fun hideAll() {
textView2.isVisible = false views.viewKeysBackupBannerText2.isVisible = false
close.isVisible = false views.viewKeysBackupBannerCloseGroup.isVisible = false
loading.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.R
import im.vector.app.core.error.ResourceLimitErrorFormatter import im.vector.app.core.error.ResourceLimitErrorFormatter
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewNotificationAreaBinding
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import kotlinx.android.synthetic.main.view_notification_area.view.*
import me.gujun.android.span.span import me.gujun.android.span.span
import me.saket.bettermovementmethod.BetterLinkMovementMethod import me.saket.bettermovementmethod.BetterLinkMovementMethod
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
@ -48,6 +49,8 @@ class NotificationAreaView @JvmOverloads constructor(
var delegate: Delegate? = null var delegate: Delegate? = null
private var state: State = State.Initial private var state: State = State.Initial
private lateinit var views : ViewNotificationAreaBinding
init { init {
setupView() setupView()
} }
@ -78,27 +81,28 @@ class NotificationAreaView @JvmOverloads constructor(
private fun setupView() { private fun setupView() {
inflate(context, R.layout.view_notification_area, this) inflate(context, R.layout.view_notification_area, this)
views = ViewNotificationAreaBinding.bind(this)
minimumHeight = DimensionConverter(resources).dpToPx(48) minimumHeight = DimensionConverter(resources).dpToPx(48)
} }
private fun cleanUp() { private fun cleanUp() {
roomNotificationMessage.setOnClickListener(null) views.roomNotificationMessage.setOnClickListener(null)
roomNotificationIcon.setOnClickListener(null) views.roomNotificationIcon.setOnClickListener(null)
setBackgroundColor(Color.TRANSPARENT) setBackgroundColor(Color.TRANSPARENT)
roomNotificationMessage.text = null views.roomNotificationMessage.text = null
roomNotificationIcon.setImageResource(0) views.roomNotificationIcon.setImageResource(0)
} }
private fun renderNoPermissionToPost() { private fun renderNoPermissionToPost() {
visibility = View.VISIBLE visibility = View.VISIBLE
roomNotificationIcon.setImageDrawable(null) views.roomNotificationIcon.setImageDrawable(null)
val message = span { val message = span {
italic { italic {
+resources.getString(R.string.room_do_not_have_permission_to_post) +resources.getString(R.string.room_do_not_have_permission_to_post)
} }
} }
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) views.roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary))
} }
private fun renderResourceLimitExceededError(state: State.ResourceLimitExceededError) { private fun renderResourceLimitExceededError(state: State.ResourceLimitExceededError) {
@ -114,16 +118,16 @@ class NotificationAreaView @JvmOverloads constructor(
formatterMode = ResourceLimitErrorFormatter.Mode.Hard formatterMode = ResourceLimitErrorFormatter.Mode.Hard
} }
val message = resourceLimitErrorFormatter.format(state.matrixError, formatterMode, clickable = true) val message = resourceLimitErrorFormatter.format(state.matrixError, formatterMode, clickable = true)
roomNotificationMessage.setTextColor(Color.WHITE) views.roomNotificationMessage.setTextColor(Color.WHITE)
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
roomNotificationMessage.movementMethod = LinkMovementMethod.getInstance() views.roomNotificationMessage.movementMethod = LinkMovementMethod.getInstance()
roomNotificationMessage.setLinkTextColor(Color.WHITE) views.roomNotificationMessage.setLinkTextColor(Color.WHITE)
setBackgroundColor(ContextCompat.getColor(context, backgroundColor)) setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
} }
private fun renderTombstone(state: State.Tombstone) { private fun renderTombstone(state: State.Tombstone) {
visibility = View.VISIBLE visibility = View.VISIBLE
roomNotificationIcon.setImageResource(R.drawable.error) views.roomNotificationIcon.setImageResource(R.drawable.error)
val message = span { val message = span {
+resources.getString(R.string.room_tombstone_versioned_description) +resources.getString(R.string.room_tombstone_versioned_description)
+"\n" +"\n"
@ -132,8 +136,8 @@ class NotificationAreaView @JvmOverloads constructor(
onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) } onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) }
} }
} }
roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance() views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance()
roomNotificationMessage.text = message views.roomNotificationMessage.text = message
} }
private fun renderDefault() { private fun renderDefault() {

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.deleteAllFiles 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.HomeActivity
import im.vector.app.features.home.ShortcutsHandler import im.vector.app.features.home.ShortcutsHandler
import im.vector.app.features.login.LoginActivity 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.hard.SignedOutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch 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, * 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 * clears cache, is logged out, or is soft logged out
*/ */
class MainActivity : VectorBaseActivity(), UnlockedActivity { class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActivity {
companion object { companion object {
private const val EXTRA_ARGS = "EXTRA_ARGS" 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 private lateinit var args: MainActivityArgs
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @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.OvershootInterpolator
import android.view.animation.ScaleAnimation import android.view.animation.ScaleAnimation
import android.view.animation.TranslateAnimation import android.view.animation.TranslateAnimation
import android.widget.FrameLayout
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.PopupWindow 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_EMPTY
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO 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 im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
import kotlin.math.max import kotlin.math.max
@ -62,25 +62,19 @@ class AttachmentTypeSelectorView(context: Context,
private val iconColorGenerator = ColorGenerator.MATERIAL private val iconColorGenerator = ColorGenerator.MATERIAL
private var galleryButton: ImageButton private val views: ViewAttachmentTypeSelectorBinding
private var cameraButton: ImageButton
private var fileButton: ImageButton
private var stickersButton: ImageButton
private var audioButton: ImageButton
private var contactButton: ImageButton
private var anchor: View? = null private var anchor: View? = null
init { init {
val root = FrameLayout(context) contentView = inflater.inflate(R.layout.view_attachment_type_selector, null, false)
val layout = inflater.inflate(R.layout.view_attachment_type_selector, root, true) views = ViewAttachmentTypeSelectorBinding.bind(contentView)
galleryButton = layout.findViewById<ImageButton>(R.id.attachmentGalleryButton).configure(Type.GALLERY) views.attachmentGalleryButton.configure(Type.GALLERY)
cameraButton = layout.findViewById<ImageButton>(R.id.attachmentCameraButton).configure(Type.CAMERA) views.attachmentCameraButton.configure(Type.CAMERA)
fileButton = layout.findViewById<ImageButton>(R.id.attachmentFileButton).configure(Type.FILE) views.attachmentFileButton.configure(Type.FILE)
stickersButton = layout.findViewById<ImageButton>(R.id.attachmentStickersButton).configure(Type.STICKER) views.attachmentStickersButton.configure(Type.STICKER)
audioButton = layout.findViewById<ImageButton>(R.id.attachmentAudioButton).configure(Type.AUDIO) views.attachmentAudioButton.configure(Type.AUDIO)
contactButton = layout.findViewById<ImageButton>(R.id.attachmentContactButton).configure(Type.CONTACT) views.attachmentContactButton.configure(Type.CONTACT)
contentView = root
width = LinearLayout.LayoutParams.MATCH_PARENT width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.WRAP_CONTENT height = LinearLayout.LayoutParams.WRAP_CONTENT
animationStyle = 0 animationStyle = 0
@ -108,12 +102,12 @@ class AttachmentTypeSelectorView(context: Context,
contentView.doOnNextLayout { contentView.doOnNextLayout {
animateWindowInCircular(anchor, contentView) animateWindowInCircular(anchor, contentView)
} }
animateButtonIn(galleryButton, ANIMATION_DURATION / 2) animateButtonIn(views.attachmentGalleryButton, ANIMATION_DURATION / 2)
animateButtonIn(cameraButton, ANIMATION_DURATION / 2) animateButtonIn(views.attachmentCameraButton, ANIMATION_DURATION / 2)
animateButtonIn(fileButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentFileButton, ANIMATION_DURATION / 4)
animateButtonIn(audioButton, ANIMATION_DURATION / 2) animateButtonIn(views.attachmentAudioButton, ANIMATION_DURATION / 2)
animateButtonIn(contactButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4)
animateButtonIn(stickersButton, 0) animateButtonIn(views.attachmentStickersButton, 0)
} }
override fun dismiss() { 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.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarConfigurable {
companion object { companion object {
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS" private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
@ -51,7 +52,9 @@ class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun getOtherThemes() = ActivityOtherThemes.AttachmentsPreview 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() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {

View File

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

View File

@ -17,18 +17,23 @@
package im.vector.app.features.call package im.vector.app.features.call
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment 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 import me.gujun.android.span.span
class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() { class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallControlsBinding>() {
override fun getLayoutResId() = R.layout.bottom_sheet_call_controls override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetCallControlsBinding {
return BottomSheetCallControlsBinding.inflate(inflater, container, false)
}
private val callViewModel: VectorCallViewModel by activityViewModel() private val callViewModel: VectorCallViewModel by activityViewModel()
@ -39,16 +44,16 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
renderState(it) renderState(it)
} }
callControlsSoundDevice.clickableView.debouncedClicks { views.callControlsSoundDevice.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.SwitchSoundDevice) callViewModel.handle(VectorCallViewActions.SwitchSoundDevice)
} }
callControlsSwitchCamera.clickableView.debouncedClicks { views.callControlsSwitchCamera.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleCamera) callViewModel.handle(VectorCallViewActions.ToggleCamera)
dismiss() dismiss()
} }
callControlsToggleSDHD.clickableView.debouncedClicks { views.callControlsToggleSDHD.views.itemVerificationClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleHDSD) callViewModel.handle(VectorCallViewActions.ToggleHDSD)
dismiss() dismiss()
} }
@ -109,30 +114,30 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
} }
private fun renderState(state: VectorCallViewState) { private fun renderState(state: VectorCallViewState) {
callControlsSoundDevice.title = getString(R.string.call_select_sound_device) views.callControlsSoundDevice.title = getString(R.string.call_select_sound_device)
callControlsSoundDevice.subTitle = when (state.soundDevice) { views.callControlsSoundDevice.subTitle = when (state.soundDevice) {
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone) CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker) CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset) CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset) CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
} }
callControlsSwitchCamera.isVisible = state.isVideoCall && state.canSwitchCamera views.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.subTitle = getString(if (state.isFrontCamera) R.string.call_camera_front else R.string.call_camera_back)
if (state.isVideoCall) { if (state.isVideoCall) {
callControlsToggleSDHD.isVisible = true views.callControlsToggleSDHD.isVisible = true
if (state.isHD) { if (state.isHD) {
callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_off) views.callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_off)
callControlsToggleSDHD.subTitle = null views.callControlsToggleSDHD.subTitle = null
callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd_disabled) views.callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd_disabled)
} else { } else {
callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_on) views.callControlsToggleSDHD.title = getString(R.string.call_format_turn_hd_on)
callControlsToggleSDHD.subTitle = null views.callControlsToggleSDHD.subTitle = null
callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd) views.callControlsToggleSDHD.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_hd)
} }
} else { } 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.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import im.vector.app.R 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.matrix.android.sdk.api.session.call.CallState
import org.webrtc.PeerConnection import org.webrtc.PeerConnection
@ -35,115 +30,100 @@ class CallControlsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
private val views: ViewCallControlsBinding
var interactionListener: InteractionListener? = null 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 { 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) // 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) private fun acceptIncomingCall() {
fun acceptIncomingCall() {
interactionListener?.didAcceptIncomingCall() interactionListener?.didAcceptIncomingCall()
} }
@OnClick(R.id.iv_icr_end_call) private fun declineIncomingCall() {
fun declineIncomingCall() {
interactionListener?.didDeclineIncomingCall() interactionListener?.didDeclineIncomingCall()
} }
@OnClick(R.id.iv_end_call) private fun endOngoingCall() {
fun endOngoingCall() {
interactionListener?.didEndCall() interactionListener?.didEndCall()
} }
@OnClick(R.id.iv_mute_toggle) private fun toggleMute() {
fun toggleMute() {
interactionListener?.didTapToggleMute() interactionListener?.didTapToggleMute()
} }
@OnClick(R.id.iv_video_toggle) private fun toggleVideo() {
fun toggleVideo() {
interactionListener?.didTapToggleVideo() interactionListener?.didTapToggleVideo()
} }
@OnClick(R.id.iv_leftMiniControl) private fun returnToChat() {
fun returnToChat() {
interactionListener?.returnToChat() interactionListener?.returnToChat()
} }
@OnClick(R.id.iv_more) private fun moreControlOption() {
fun moreControlOption() {
interactionListener?.didTapMore() interactionListener?.didTapMore()
} }
fun updateForState(state: VectorCallViewState) { fun updateForState(state: VectorCallViewState) {
val callState = state.callState.invoke() val callState = state.callState.invoke()
if (state.isAudioMuted) { if (state.isAudioMuted) {
muteIcon.setImageResource(R.drawable.ic_microphone_off) views.muteIcon.setImageResource(R.drawable.ic_microphone_off)
muteIcon.contentDescription = resources.getString(R.string.a11y_unmute_microphone) views.muteIcon.contentDescription = resources.getString(R.string.a11y_unmute_microphone)
} else { } else {
muteIcon.setImageResource(R.drawable.ic_microphone_on) views.muteIcon.setImageResource(R.drawable.ic_microphone_on)
muteIcon.contentDescription = resources.getString(R.string.a11y_mute_microphone) views.muteIcon.contentDescription = resources.getString(R.string.a11y_mute_microphone)
} }
if (state.isVideoEnabled) { if (state.isVideoEnabled) {
videoToggleIcon.setImageResource(R.drawable.ic_video) views.videoToggleIcon.setImageResource(R.drawable.ic_video)
videoToggleIcon.contentDescription = resources.getString(R.string.a11y_stop_camera) views.videoToggleIcon.contentDescription = resources.getString(R.string.a11y_stop_camera)
} else { } else {
videoToggleIcon.setImageResource(R.drawable.ic_video_off) views.videoToggleIcon.setImageResource(R.drawable.ic_video_off)
videoToggleIcon.contentDescription = resources.getString(R.string.a11y_start_camera) views.videoToggleIcon.contentDescription = resources.getString(R.string.a11y_start_camera)
} }
when (callState) { when (callState) {
is CallState.Idle, is CallState.Idle,
is CallState.Dialing, is CallState.Dialing,
is CallState.Answering -> { is CallState.Answering -> {
ringingControls.isVisible = true views.ringingControls.isVisible = true
ringingControlAccept.isVisible = false views.ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true views.ringingControlDecline.isVisible = true
connectedControls.isVisible = false views.connectedControls.isVisible = false
} }
is CallState.LocalRinging -> { is CallState.LocalRinging -> {
ringingControls.isVisible = true views.ringingControls.isVisible = true
ringingControlAccept.isVisible = true views.ringingControlAccept.isVisible = true
ringingControlDecline.isVisible = true views.ringingControlDecline.isVisible = true
connectedControls.isVisible = false views.connectedControls.isVisible = false
} }
is CallState.Connected -> { is CallState.Connected -> {
if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) { if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
ringingControls.isVisible = false views.ringingControls.isVisible = false
connectedControls.isVisible = true views.connectedControls.isVisible = true
iv_video_toggle.isVisible = state.isVideoCall views.videoToggleIcon.isVisible = state.isVideoCall
} else { } else {
ringingControls.isVisible = true views.ringingControls.isVisible = true
ringingControlAccept.isVisible = false views.ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true views.ringingControlDecline.isVisible = true
connectedControls.isVisible = false views.connectedControls.isVisible = false
} }
} }
is CallState.Terminated, is CallState.Terminated,
null -> { null -> {
ringingControls.isVisible = false views.ringingControls.isVisible = false
connectedControls.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.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import butterknife.BindView
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel 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.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions 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.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.android.synthetic.main.activity_call.*
import org.matrix.android.sdk.api.session.call.CallState 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.EglUtils
import org.matrix.android.sdk.api.session.call.MxCallDetail 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.EglBase
import org.webrtc.PeerConnection import org.webrtc.PeerConnection
import org.webrtc.RendererCommon import org.webrtc.RendererCommon
import org.webrtc.SurfaceViewRenderer
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -72,9 +70,9 @@ data class CallArgs(
val isVideoCall: Boolean val isVideoCall: Boolean
) : Parcelable ) : 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 @Inject lateinit var avatarRenderer: AvatarRenderer
@ -90,15 +88,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
@Inject lateinit var viewModelFactory: VectorCallViewModel.Factory @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 private var rootEglBase: EglBase? = null
var systemUiVisibility = false var systemUiVisibility = false
@ -158,7 +147,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// This will need to be refined // 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) v.updatePadding(bottom = if (systemUiVisibility) insets.systemWindowInsetBottom else 0)
insets insets
} }
@ -178,7 +167,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
turnScreenOnAndKeyguardOff() turnScreenOnAndKeyguardOff()
} }
constraintLayout.clicks() views.constraintLayout.clicks()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { toggleUiSystemVisibility() } .subscribe { toggleUiSystemVisibility() }
@ -210,10 +199,10 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
} }
override fun onDestroy() { override fun onDestroy() {
peerConnectionManager.detachRenderers(listOf(pipRenderer, fullscreenRenderer)) peerConnectionManager.detachRenderers(listOf(views.pipRenderer, views.fullscreenRenderer))
if (surfaceRenderersAreInitialized) { if (surfaceRenderersAreInitialized) {
pipRenderer.release() views.pipRenderer.release()
fullscreenRenderer.release() views.fullscreenRenderer.release()
} }
turnScreenOffAndKeyguardOn() turnScreenOffAndKeyguardOn()
super.onDestroy() super.onDestroy()
@ -228,54 +217,54 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
return return
} }
callControlsView.updateForState(state) views.callControlsView.updateForState(state)
val callState = state.callState.invoke() val callState = state.callState.invoke()
callConnectingProgress.isVisible = false views.callConnectingProgress.isVisible = false
when (callState) { when (callState) {
is CallState.Idle, is CallState.Idle,
is CallState.Dialing -> { is CallState.Dialing -> {
callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_ring) views.callStatusText.setText(R.string.call_ring)
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.LocalRinging -> { is CallState.LocalRinging -> {
callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
callStatusText.text = null views.callStatusText.text = null
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.Answering -> { is CallState.Answering -> {
callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting) views.callStatusText.setText(R.string.call_connecting)
callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.Connected -> { is CallState.Connected -> {
if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) { if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
if (callArgs.isVideoCall) { if (callArgs.isVideoCall) {
callVideoGroup.isVisible = true views.callVideoGroup.isVisible = true
callInfoGroup.isVisible = false views.callInfoGroup.isVisible = false
pip_video_view.isVisible = !state.isVideoCaptureInError views.pipRenderer.isVisible = !state.isVideoCaptureInError
} else { } else {
callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
configureCallInfo(state) configureCallInfo(state)
callStatusText.text = null views.callStatusText.text = null
} }
} else { } else {
// This state is not final, if you change network, new candidates will be sent // This state is not final, if you change network, new candidates will be sent
callVideoGroup.isInvisible = true views.callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true views.callInfoGroup.isVisible = true
configureCallInfo(state) configureCallInfo(state)
callStatusText.setText(R.string.call_connecting) views.callStatusText.setText(R.string.call_connecting)
callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
} }
// ensure all attached? // ensure all attached?
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null) peerConnectionManager.attachViewRenderers(views.pipRenderer, views.fullscreenRenderer, null)
} }
is CallState.Terminated -> { is CallState.Terminated -> {
finish() finish()
@ -287,14 +276,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private fun configureCallInfo(state: VectorCallViewState) { private fun configureCallInfo(state: VectorCallViewState) {
state.otherUserMatrixItem.invoke()?.let { state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar) avatarRenderer.render(it, views.otherMemberAvatar)
participantNameText.text = it.getBestName() views.participantNameText.text = it.getBestName()
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call) views.callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
} }
} }
private fun configureCallViews() { private fun configureCallViews() {
callControlsView.interactionListener = this views.callControlsView.interactionListener = this
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { 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 // Init Picture in Picture renderer
pipRenderer.init(rootEglBase!!.eglBaseContext, null) views.pipRenderer.init(rootEglBase!!.eglBaseContext, null)
pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) views.pipRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
// Init Full Screen renderer // Init Full Screen renderer
fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null) views.fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null)
fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) views.fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
pipRenderer.setZOrderMediaOverlay(true) views.pipRenderer.setZOrderMediaOverlay(true)
pipRenderer.setEnableHardwareScaler(true /* enabled */) views.pipRenderer.setEnableHardwareScaler(true /* enabled */)
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */) views.fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, peerConnectionManager.attachViewRenderers(
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() }) views.pipRenderer,
views.fullscreenRenderer,
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() }
)
pipRenderer.setOnClickListener { views.pipRenderer.setOnClickListener {
callViewModel.handle(VectorCallViewActions.ToggleCamera) callViewModel.handle(VectorCallViewActions.ToggleCamera)
} }
surfaceRenderersAreInitialized = true surfaceRenderersAreInitialized = true

View File

@ -20,7 +20,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
@ -28,11 +27,10 @@ import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.facebook.react.modules.core.PermissionListener import com.facebook.react.modules.core.PermissionListener
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import kotlinx.android.parcel.Parcelize import im.vector.app.databinding.ActivityJitsiBinding
import kotlinx.android.synthetic.main.activity_jitsi.* import kotlinx.parcelize.Parcelize
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
import org.jitsi.meet.sdk.JitsiMeetActivityInterface import org.jitsi.meet.sdk.JitsiMeetActivityInterface
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
@ -42,7 +40,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import java.net.URL import java.net.URL
import javax.inject.Inject import javax.inject.Inject
class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, JitsiMeetViewListener { class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMeetActivityInterface, JitsiMeetViewListener {
@Parcelize @Parcelize
data class Args( data class Args(
@ -51,7 +49,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
val enableVideo: Boolean val enableVideo: Boolean
) : Parcelable ) : Parcelable
override fun getLayoutRes() = R.layout.activity_jitsi override fun getBinding() = ActivityJitsiBinding.inflate(layoutInflater)
@Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory @Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory
@ -76,7 +74,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
super.initUiAndData() super.initUiAndData()
jitsiMeetView = JitsiMeetView(this) jitsiMeetView = JitsiMeetView(this)
val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) 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 jitsiMeetView?.listener = this
} }
@ -84,13 +82,13 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
when (viewState.widget) { when (viewState.widget) {
is Fail -> finish() is Fail -> finish()
is Success -> { is Success -> {
findViewById<View>(R.id.jitsi_progress_layout).isVisible = false views.jitsiProgressLayout.isVisible = false
jitsiMeetView?.isVisible = true jitsiMeetView?.isVisible = true
configureJitsiView(viewState) configureJitsiView(viewState)
} }
else -> { else -> {
jitsiMeetView?.isVisible = false 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 package im.vector.app.features.contactsbook
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel 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.configureWith
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment 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.PendingInvitee
import im.vector.app.features.userdirectory.UserListAction import im.vector.app.features.userdirectory.UserListAction
import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel 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.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -43,9 +46,12 @@ import javax.inject.Inject
class ContactsBookFragment @Inject constructor( class ContactsBookFragment @Inject constructor(
val contactsBookViewModelFactory: ContactsBookViewModel.Factory, val contactsBookViewModelFactory: ContactsBookViewModel.Factory,
private val contactsBookController: ContactsBookController 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() private val viewModel: UserListViewModel by activityViewModel()
// Use activityViewModel to avoid loading several times the data // Use activityViewModel to avoid loading several times the data
@ -64,7 +70,7 @@ class ContactsBookFragment @Inject constructor(
} }
private fun setupConsentView() { private fun setupConsentView() {
phoneBookSearchForMatrixContacts.setOnClickListener { views.phoneBookSearchForMatrixContacts.setOnClickListener {
withState(contactsBookViewModel) { state -> withState(contactsBookViewModel) { state ->
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
.setTitle(R.string.identity_server_consent_dialog_title) .setTitle(R.string.identity_server_consent_dialog_title)
@ -79,7 +85,7 @@ class ContactsBookFragment @Inject constructor(
} }
private fun setupOnlyBoundContactsView() { private fun setupOnlyBoundContactsView() {
phoneBookOnlyBoundContacts.checkedChanges() views.phoneBookOnlyBoundContacts.checkedChanges()
.subscribe { .subscribe {
contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it)) contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it))
} }
@ -87,7 +93,7 @@ class ContactsBookFragment @Inject constructor(
} }
private fun setupFilterView() { private fun setupFilterView() {
phoneBookFilter views.phoneBookFilter
.textChanges() .textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300, TimeUnit.MILLISECONDS)
@ -98,25 +104,25 @@ class ContactsBookFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
phoneBookRecyclerView.cleanup() views.phoneBookRecyclerView.cleanup()
contactsBookController.callback = null contactsBookController.callback = null
super.onDestroyView() super.onDestroyView()
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
contactsBookController.callback = this contactsBookController.callback = this
phoneBookRecyclerView.configureWith(contactsBookController) views.phoneBookRecyclerView.configureWith(contactsBookController)
} }
private fun setupCloseView() { private fun setupCloseView() {
phoneBookClose.debouncedClicks { views.phoneBookClose.debouncedClicks {
sharedActionViewModel.post(UserListSharedAction.GoBack) sharedActionViewModel.post(UserListSharedAction.GoBack)
} }
} }
override fun invalidate() = withState(contactsBookViewModel) { state -> override fun invalidate() = withState(contactsBookViewModel) { state ->
phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent views.phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved views.phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
contactsBookController.setData(state) 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.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel import im.vector.app.features.userdirectory.UserListViewModel
import im.vector.app.features.userdirectory.UserListViewState 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.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -77,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE views.toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel

View File

@ -16,6 +16,8 @@
package im.vector.app.features.createdirect package im.vector.app.features.createdirect
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.google.zxing.Result 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.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding
import im.vector.app.features.userdirectory.PendingInvitee import im.vector.app.features.userdirectory.PendingInvitee
import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
import org.matrix.android.sdk.api.session.permalinks.PermalinkData 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.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import javax.inject.Inject 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() 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 -> private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
if (allGranted) { if (allGranted) {
@ -48,14 +53,14 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
private fun startCamera() { private fun startCamera() {
// Start camera on resume // Start camera on resume
scannerView.startCamera() views.scannerView.startCamera()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
view?.hideKeyboard() view?.hideKeyboard()
// Register ourselves as a handler for scan results. // Register ourselves as a handler for scan results.
scannerView.setResultHandler(this) views.scannerView.setResultHandler(this)
// Start camera on resume // Start camera on resume
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
startCamera() startCamera()
@ -65,9 +70,9 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// Unregister ourselves as a handler for scan results. // Unregister ourselves as a handler for scan results.
scannerView.setResultHandler(null) views.scannerView.setResultHandler(null)
// Stop camera on pause // Stop camera on pause
scannerView.stopCamera() views.scannerView.stopCamera()
} }
// Copied from https://github.com/markusfisch/BinaryEye/blob/ // 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.app.Activity
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.EditText import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer 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.R
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentKeysBackupRestoreFromKeyBinding
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import javax.inject.Inject import javax.inject.Inject
class KeysBackupRestoreFromKeyFragment @Inject constructor() 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 viewModel: KeysBackupRestoreFromKeyViewModel
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromKeyViewModel::class.java) viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromKeyViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java) sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
mKeyTextEdit.setText(viewModel.recoveryCode.value) views.keyTextEdit.setText(viewModel.recoveryCode.value)
mKeyTextEdit.setOnEditorActionListener { _, actionId, _ -> views.keyTextEdit.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
onRestoreFromKey() onRestoreFromKey()
return@setOnEditorActionListener true return@setOnEditorActionListener true
@ -60,21 +55,23 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
return@setOnEditorActionListener false return@setOnEditorActionListener false
} }
mKeyInputLayout.error = viewModel.recoveryCodeErrorText.value views.keyInputLayout.error = viewModel.recoveryCodeErrorText.value
viewModel.recoveryCodeErrorText.observe(viewLifecycleOwner, Observer { newValue -> 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) private fun onRestoreKeyTextEditChange(s: CharSequence?) {
fun onRestoreKeyTextEditChange(s: Editable?) {
s?.toString()?.let { s?.toString()?.let {
viewModel.updateCode(it) viewModel.updateCode(it)
} }
} }
@OnClick(R.id.keys_restore_button) private fun onRestoreFromKey() {
fun onRestoreFromKey() {
val value = viewModel.recoveryCode.value val value = viewModel.recoveryCode.value
if (value.isNullOrBlank()) { if (value.isNullOrBlank()) {
viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message) 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) private fun onImport() {
fun onImport() {
startImportTextFromFileIntent(requireContext(), textFileStartForActivityResult) startImportTextFromFileIntent(requireContext(), textFileStartForActivityResult)
} }
@ -98,8 +94,8 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
?.bufferedReader() ?.bufferedReader()
?.use { it.readText() } ?.use { it.readText() }
?.let { ?.let {
mKeyTextEdit.setText(it) views.keyTextEdit.setText(it)
mKeyTextEdit.setSelection(it.length) views.keyTextEdit.setSelection(it.length)
} }
} }
} }

View File

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

View File

@ -16,25 +16,22 @@
package im.vector.app.features.crypto.keysbackup.restore package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.TextView import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.BindView
import butterknife.OnClick
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.LiveEvent
import im.vector.app.databinding.FragmentKeysBackupRestoreSuccessBinding
import javax.inject.Inject 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 override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreSuccessBinding {
return FragmentKeysBackupRestoreSuccessBinding.inflate(inflater, container, false)
@BindView(R.id.keys_backup_restore_success) }
lateinit var mSuccessText: TextView
@BindView(R.id.keys_backup_restore_success_info)
lateinit var mSuccessDetailsText: TextView
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
@ -48,18 +45,18 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
it.totalNumberOfKeys, it.totalNumberOfKeys) it.totalNumberOfKeys, it.totalNumberOfKeys)
val part2 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part2, val part2 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part2,
it.successfullyNumberOfImportedKeys, it.successfullyNumberOfImportedKeys) 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 // 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 { } else {
mSuccessText.text = context?.getString(R.string.keys_backup_restore_success_title_already_up_to_date) views.successText.text = context?.getString(R.string.keys_backup_restore_success_title_already_up_to_date)
mSuccessDetailsText.isVisible = false views.successDetailsText.isVisible = false
} }
views.keysBackupSetupDoneButton.setOnClickListener { onDone() }
} }
@OnClick(R.id.keys_backup_setup_done_button) private fun onDone() {
fun onDone() {
sharedViewModel.importRoomKeysFinishWithResult.value = LiveEvent(sharedViewModel.importKeyResult!!) sharedViewModel.importRoomKeysFinishWithResult.value = LiveEvent(sharedViewModel.importKeyResult!!)
} }
} }

View File

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

View File

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

View File

@ -16,64 +16,40 @@
package im.vector.app.features.crypto.keysbackup.setup package im.vector.app.features.crypto.keysbackup.setup
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.EditText import androidx.core.widget.doOnTextChanged
import android.widget.ImageView
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.transition.TransitionManager 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 com.nulabinc.zxcvbn.Zxcvbn
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment 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 im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject 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 override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep2Binding {
return FragmentKeysBackupSetupStep2Binding.inflate(inflater, container, false)
@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
private val zxcvbn = Zxcvbn() private val zxcvbn = Zxcvbn()
@OnTextChanged(R.id.keys_backup_passphrase_enter_edittext) private fun onPassphraseChanged() {
fun onPassphraseChanged() { viewModel.passphrase.value = views.keysBackupSetupStep2PassphraseEnterEdittext.text.toString()
viewModel.passphrase.value = mPassphraseTextEdit.text.toString()
viewModel.confirmPassphraseError.value = null viewModel.confirmPassphraseError.value = null
} }
@OnTextChanged(R.id.keys_backup_passphrase_confirm_edittext) private fun onConfirmPassphraseChanged() {
fun onConfirmPassphraseChanged() { viewModel.confirmPassphrase.value = views.keysBackupSetupStep2PassphraseConfirmEditText.text.toString()
viewModel.confirmPassphrase.value = mPassphraseConfirmTextEdit.text.toString()
} }
private lateinit var viewModel: KeysBackupSetupSharedViewModel private lateinit var viewModel: KeysBackupSetupSharedViewModel
@ -85,6 +61,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
viewModel.shouldPromptOnBack = true viewModel.shouldPromptOnBack = true
bindViewToViewModel() bindViewToViewModel()
setupViews()
} }
/* ========================================================================================== /* ==========================================================================================
@ -94,24 +71,24 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
private fun bindViewToViewModel() { private fun bindViewToViewModel() {
viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength -> viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength ->
if (strength == null) { if (strength == null) {
mPassphraseProgressLevel.strength = 0 views.keysBackupSetupStep2PassphraseStrengthLevel.strength = 0
mPassphraseInputLayout.error = null views.keysBackupSetupStep2PassphraseEnterTil.error = null
} else { } else {
val score = strength.score val score = strength.score
mPassphraseProgressLevel.strength = score views.keysBackupSetupStep2PassphraseStrengthLevel.strength = score
if (score in 1..3) { if (score in 1..3) {
val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale) val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale)
if (warning != null) { if (warning != null) {
mPassphraseInputLayout.error = warning views.keysBackupSetupStep2PassphraseEnterTil.error = warning
} }
val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale) val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale)
if (suggestions != null) { if (suggestions != null) {
mPassphraseInputLayout.error = suggestions.firstOrNull() views.keysBackupSetupStep2PassphraseEnterTil.error = suggestions.firstOrNull()
} }
} else { } 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 { viewModel.passphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(rootGroup) TransitionManager.beginDelayedTransition(views.keysBackupRoot)
mPassphraseInputLayout.error = it views.keysBackupSetupStep2PassphraseEnterTil.error = it
}) })
mPassphraseConfirmTextEdit.setText(viewModel.confirmPassphrase.value) views.keysBackupSetupStep2PassphraseConfirmEditText.setText(viewModel.confirmPassphrase.value)
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer { viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
val shouldBeVisible = it ?: false val shouldBeVisible = it ?: false
mPassphraseTextEdit.showPassword(shouldBeVisible) views.keysBackupSetupStep2PassphraseEnterEdittext.showPassword(shouldBeVisible)
mPassphraseConfirmTextEdit.showPassword(shouldBeVisible) views.keysBackupSetupStep2PassphraseConfirmEditText.showPassword(shouldBeVisible)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.keysBackupSetupStep2ShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}) })
viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer { viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer {
TransitionManager.beginDelayedTransition(rootGroup) TransitionManager.beginDelayedTransition(views.keysBackupRoot)
mPassphraseConfirmInputLayout.error = it views.keysBackupSetupStep2PassphraseConfirmTil.error = it
}) })
mPassphraseConfirmTextEdit.setOnEditorActionListener { _, actionId, _ -> views.keysBackupSetupStep2PassphraseConfirmEditText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
doNext() doNext()
return@setOnEditorActionListener true return@setOnEditorActionListener true
@ -159,13 +136,20 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
} }
} }
@OnClick(R.id.keys_backup_view_show_password) private fun setupViews() {
fun toggleVisibilityMode() { 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) viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
} }
@OnClick(R.id.keys_backup_setup_step2_button) private fun doNext() {
fun doNext() {
when { when {
viewModel.passphrase.value.isNullOrEmpty() -> { viewModel.passphrase.value.isNullOrEmpty() -> {
viewModel.passphraseError.value = context?.getString(R.string.passphrase_empty_error_message) 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) private fun skipPassphrase() {
fun skipPassphrase() {
when { when {
viewModel.passphrase.value.isNullOrEmpty() -> { viewModel.passphrase.value.isNullOrEmpty() -> {
// Generate a recovery key for the user // 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.app.Activity
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Button import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import arrow.core.Try import arrow.core.Try
import butterknife.BindView
import butterknife.OnClick
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult 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.copyToClipboard
import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.core.utils.selectTxtFileToWrite
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentKeysBackupSetupStep3Binding
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -46,18 +47,11 @@ import java.util.Date
import java.util.Locale import java.util.Locale
import javax.inject.Inject 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 override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep3Binding {
return FragmentKeysBackupSetupStep3Binding.inflate(inflater, container, false)
@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
private lateinit var viewModel: KeysBackupSetupSharedViewModel private lateinit var viewModel: KeysBackupSetupSharedViewModel
@ -70,10 +64,10 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
viewModel.passphrase.observe(viewLifecycleOwner, Observer { viewModel.passphrase.observe(viewLifecycleOwner, Observer {
if (it.isNullOrBlank()) { if (it.isNullOrBlank()) {
// Recovery was generated, so show key and options to save // Recovery was generated, so show key and options to save
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase) views.keysBackupSetupStep3Label2.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.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(" ", "") .replace(" ", "")
.chunked(16) .chunked(16)
.joinToString("\n") { .joinToString("\n") {
@ -81,17 +75,24 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
.chunked(4) .chunked(4)
.joinToString(" ") .joinToString(" ")
} }
mRecoveryKeyTextView.isVisible = true views.keysBackupSetupStep3RecoveryKeyText.isVisible = true
} else { } else {
mRecoveryKeyLabel2TextView.text = getString(R.string.keys_backup_setup_step3_text_line2) views.keysBackupSetupStep3Label2.text = getString(R.string.keys_backup_setup_step3_text_line2)
mFinishButton.text = getString(R.string.keys_backup_setup_step3_button_title) views.keysBackupSetupStep3FinishButton.text = getString(R.string.keys_backup_setup_step3_button_title)
mRecoveryKeyTextView.isVisible = false views.keysBackupSetupStep3RecoveryKeyText.isVisible = false
} }
}) })
setupViews()
} }
@OnClick(R.id.keys_backup_setup_step3_button) private fun setupViews() {
fun onFinishButtonClicked() { views.keysBackupSetupStep3FinishButton.setOnClickListener { onFinishButtonClicked() }
views.keysBackupSetupStep3CopyButton.setOnClickListener { onCopyButtonClicked() }
views.keysBackupSetupStep3RecoveryKeyText.setOnClickListener { onRecoveryKeyClicked() }
}
private fun onFinishButtonClicked() {
if (viewModel.megolmBackupCreationInfo == null) { if (viewModel.megolmBackupCreationInfo == null) {
// nothing // nothing
} else { } else {
@ -103,8 +104,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
} }
} }
@OnClick(R.id.keys_backup_setup_step3_copy_button) private fun onCopyButtonClicked() {
fun onCopyButtonClicked() {
val dialog = BottomSheetDialog(requireActivity()) val dialog = BottomSheetDialog(requireActivity())
dialog.setContentView(R.layout.bottom_sheet_save_recovery_key) dialog.setContentView(R.layout.bottom_sheet_save_recovery_key)
dialog.setCanceledOnTouchOutside(true) dialog.setCanceledOnTouchOutside(true)
@ -155,8 +155,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
dialog.show() dialog.show()
} }
@OnClick(R.id.keys_backup_recovery_key_text) private fun onRecoveryKeyClicked() {
fun onRecoveryKeyClicked() {
viewModel.recoveryKey.value?.let { viewModel.recoveryKey.value?.let {
viewModel.copyHasBeenMade = true 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.SimpleFragmentActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.recover.SetupMode
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.android.synthetic.main.activity.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -65,7 +64,7 @@ class SharedSecureStorageActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.addFragmentOnAttachListener(this) supportFragmentManager.addFragmentOnAttachListener(this)
toolbar.visibility = View.GONE views.toolbar.visibility = View.GONE
viewModel.observeViewEvents { observeViewEvents(it) } viewModel.observeViewEvents { observeViewEvents(it) }
@ -132,7 +131,7 @@ class SharedSecureStorageActivity :
} }
override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) { override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) {
if (fragment is VectorBaseBottomSheetDialogFragment) { if (fragment is VectorBaseBottomSheetDialogFragment<*>) {
fragment.resultListener = this fragment.resultListener = this
} }
} }

View File

@ -18,7 +18,9 @@ package im.vector.app.features.crypto.quads
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents 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.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
import io.reactivex.android.schedulers.AndroidSchedulers 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 org.matrix.android.sdk.api.extensions.tryOrNull
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject 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() val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ssss_restore_with_key_text.text = getString(R.string.enter_secret_storage_input_key) views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key)
ssss_key_enter_edittext.editorActionEvents() views.ssssKeyEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -53,35 +58,35 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_key_enter_edittext.textChanges() views.ssssKeyEnterEdittext.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .subscribe {
ssss_key_enter_til.error = null views.ssssKeyEnterTil.error = null
ssss_key_submit.isEnabled = it.isNotBlank() views.ssssKeySubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
ssss_key_reset.clickableView.debouncedClicks { views.ssssKeyReset.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
} }
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
when (it) { when (it) {
is SharedSecureStorageViewEvent.KeyInlineError -> { is SharedSecureStorageViewEvent.KeyInlineError -> {
ssss_key_enter_til.error = it.message views.ssssKeyEnterTil.error = it.message
} }
} }
} }
ssss_key_submit.debouncedClicks { submit() } views.ssssKeySubmit.debouncedClicks { submit() }
} }
fun submit() { fun submit() {
val text = ssss_key_enter_edittext.text.toString() val text = views.ssssKeyEnterEdittext.text.toString()
if (text.isBlank()) return // Should not reach this point as button disabled if (text.isBlank()) return // Should not reach this point as button disabled
ssss_key_submit.isEnabled = false views.ssssKeySubmit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text)) sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
} }
@ -93,7 +98,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
?.bufferedReader() ?.bufferedReader()
?.use { it.readText() } ?.use { it.readText() }
?.let { ?.let {
ssss_key_enter_edittext.setText(it) views.ssssKeyEnterEdittext.setText(it)
} }
} }
} }

View File

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.quads package im.vector.app.features.crypto.quads
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import com.airbnb.mvrx.activityViewModel 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.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class SharedSecuredStoragePassphraseFragment @Inject constructor( class SharedSecuredStoragePassphraseFragment @Inject constructor(
private val colorProvider: ColorProvider 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() val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
@ -48,7 +53,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
// If has passphrase // If has passphrase
val pass = getString(R.string.recovery_passphrase) val pass = getString(R.string.recovery_passphrase)
val key = getString(R.string.recovery_key) 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, R.string.enter_secret_storage_passphrase_or_key,
pass, pass,
key key
@ -57,7 +62,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
.colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) .colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
.colorizeMatchingText(key, 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) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -67,40 +72,40 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .subscribe {
ssss_passphrase_enter_til.error = null views.ssssPassphraseEnterTil.error = null
ssss_passphrase_submit.isEnabled = it.isNotBlank() views.ssssPassphraseSubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_passphrase_reset.clickableView.debouncedClicks { views.ssssPassphraseReset.views.itemVerificationClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
} }
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
when (it) { when (it) {
is SharedSecureStorageViewEvent.InlineError -> { is SharedSecureStorageViewEvent.InlineError -> {
ssss_passphrase_enter_til.error = it.message views.ssssPassphraseEnterTil.error = it.message
} }
} }
} }
ssss_passphrase_submit.debouncedClicks { submit() } views.ssssPassphraseSubmit.debouncedClicks { submit() }
ssss_passphrase_use_key.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) } views.ssssPassphraseUseKey.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) } views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
} }
fun submit() { 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 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)) sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text))
} }
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
val shouldBeVisible = state.passphraseVisible val shouldBeVisible = state.passphraseVisible
ssss_passphrase_enter_edittext.showPassword(shouldBeVisible) views.ssssPassphraseEnterEdittext.showPassword(shouldBeVisible)
ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) 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 package im.vector.app.features.crypto.quads
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSsssResetAllBinding
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import kotlinx.android.synthetic.main.fragment_ssss_reset_all.*
import javax.inject.Inject 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() val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ssss_reset_button_reset.debouncedClicks { views.ssssResetButtonReset.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.DoResetAll) sharedViewModel.handle(SharedSecureStorageAction.DoResetAll)
} }
ssss_reset_button_cancel.debouncedClicks { views.ssssResetButtonCancel.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.Back) sharedViewModel.handle(SharedSecureStorageAction.Back)
} }
ssss_reset_other_devices.debouncedClicks { views.ssssResetOtherDevices.debouncedClicks {
withState(sharedViewModel) { withState(sharedViewModel) {
DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST") DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST")
} }
} }
sharedViewModel.subscribe(this) { state -> sharedViewModel.subscribe(this) { state ->
ssss_reset_other_devices.setTextOrHide( views.ssssResetOtherDevices.setTextOrHide(
state.activeDeviceCount state.activeDeviceCount
.takeIf { it > 0 } .takeIf { it > 0 }
?.let { resources.getQuantityString(R.plurals.secure_backup_reset_devices_you_can_verify, it, it) } ?.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 package im.vector.app.features.crypto.recover
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel 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.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentBootstrapEnterAccountPasswordBinding
import io.reactivex.android.schedulers.AndroidSchedulers 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 java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class BootstrapAccountPasswordFragment @Inject constructor( class BootstrapAccountPasswordFragment @Inject constructor(
private val colorProvider: ColorProvider 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
@ -49,13 +52,13 @@ class BootstrapAccountPasswordFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val recPassPhrase = getString(R.string.account_password) val recPassPhrase = getString(R.string.account_password)
bootstrapDescriptionText.text = getString(R.string.enter_account_password, recPassPhrase) views.bootstrapDescriptionText.text = getString(R.string.enter_account_password, recPassPhrase)
.toSpannable() .toSpannable()
.colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
bootstrapAccountPasswordEditText.hint = getString(R.string.account_password) views.bootstrapAccountPasswordEditText.hint = getString(R.string.account_password)
bootstrapAccountPasswordEditText.editorActionEvents() views.bootstrapAccountPasswordEditText.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -65,21 +68,21 @@ class BootstrapAccountPasswordFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
bootstrapAccountPasswordEditText.textChanges() views.bootstrapAccountPasswordEditText.textChanges()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .subscribe {
if (!it.isNullOrBlank()) { if (!it.isNullOrBlank()) {
bootstrapAccountPasswordTil.error = null views.bootstrapAccountPasswordTil.error = null
} }
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapPasswordButton.debouncedClicks { submit() } views.bootstrapPasswordButton.debouncedClicks { submit() }
withState(sharedViewModel) { state -> withState(sharedViewModel) { state ->
(state.step as? BootstrapStep.AccountPassword)?.failure?.let { (state.step as? BootstrapStep.AccountPassword)?.failure?.let {
bootstrapAccountPasswordTil.error = it views.bootstrapAccountPasswordTil.error = it
} }
} }
} }
@ -88,9 +91,9 @@ class BootstrapAccountPasswordFragment @Inject constructor(
if (state.step !is BootstrapStep.AccountPassword) { if (state.step !is BootstrapStep.AccountPassword) {
return@withState return@withState
} }
val accountPassword = bootstrapAccountPasswordEditText.text?.toString() val accountPassword = views.bootstrapAccountPasswordEditText.text?.toString()
if (accountPassword.isNullOrBlank()) { if (accountPassword.isNullOrBlank()) {
bootstrapAccountPasswordTil.error = getString(R.string.error_empty_field_your_password) views.bootstrapAccountPasswordTil.error = getString(R.string.error_empty_field_your_password)
} else { } else {
view?.hideKeyboard() view?.hideKeyboard()
sharedViewModel.handle(BootstrapActions.ReAuth(accountPassword)) sharedViewModel.handle(BootstrapActions.ReAuth(accountPassword))
@ -100,8 +103,8 @@ class BootstrapAccountPasswordFragment @Inject constructor(
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.AccountPassword) { if (state.step is BootstrapStep.AccountPassword) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false) views.bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
} }
} }

View File

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

View File

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover package im.vector.app.features.crypto.recover
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState 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.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText 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 import javax.inject.Inject
class BootstrapConclusionFragment @Inject constructor( class BootstrapConclusionFragment @Inject constructor(
private val colorProvider: ColorProvider 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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 -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step !is BootstrapStep.DoneSuccess) return@withState if (state.step !is BootstrapStep.DoneSuccess) return@withState
bootstrapConclusionText.text = getString( views.bootstrapConclusionText.text = getString(
R.string.bootstrap_cross_signing_success, R.string.bootstrap_cross_signing_success,
getString(R.string.recovery_passphrase), getString(R.string.recovery_passphrase),
getString(R.string.message_key) getString(R.string.message_key)

View File

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

View File

@ -17,7 +17,9 @@
package im.vector.app.features.crypto.recover package im.vector.app.features.crypto.recover
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -26,29 +28,33 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_notice) views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_notice)
ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint) views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
withState(sharedViewModel) { withState(sharedViewModel) {
// set initial value (useful when coming back) // set initial value (useful when coming back)
ssss_passphrase_enter_edittext.setText(it.passphrase ?: "") views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "")
} }
ssss_passphrase_enter_edittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -58,7 +64,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
} }
.disposeOnDestroyView() .disposeOnDestroyView()
ssss_passphrase_enter_edittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .subscribe {
// ssss_passphrase_enter_til.error = null // ssss_passphrase_enter_til.error = null
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
@ -74,8 +80,8 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
// } // }
} }
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapSubmit.debouncedClicks { submit() } views.bootstrapSubmit.debouncedClicks { submit() }
} }
private fun submit() = withState(sharedViewModel) { state -> private fun submit() = withState(sharedViewModel) { state ->
@ -83,11 +89,11 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
return@withState return@withState
} }
val score = state.passphraseStrength.invoke()?.score val score = state.passphraseStrength.invoke()?.score
val passphrase = ssss_passphrase_enter_edittext.text?.toString() val passphrase = views.ssssPassphraseEnterEdittext.text?.toString()
if (passphrase.isNullOrBlank()) { 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) { } 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 { } else {
sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase)) sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase))
} }
@ -96,21 +102,21 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragmen
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.SetupPassphrase) { if (state.step is BootstrapStep.SetupPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
state.passphraseStrength.invoke()?.let { strength -> state.passphraseStrength.invoke()?.let { strength ->
val score = strength.score val score = strength.score
ssss_passphrase_security_progress.strength = score views.ssssPassphraseSecurityProgress.strength = score
if (score in 1..3) { if (score in 1..3) {
val hint = val hint =
strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() } strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() }
?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull() ?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull()
if (hint != null && hint != ssss_passphrase_enter_til.error.toString()) { if (hint != null && hint != views.ssssPassphraseEnterTil.error.toString()) {
ssss_passphrase_enter_til.error = hint views.ssssPassphraseEnterTil.error = hint
} }
} else { } 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_CLASS_TEXT
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible 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.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
import io.reactivex.android.schedulers.AndroidSchedulers 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.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -47,9 +49,11 @@ import javax.inject.Inject
class BootstrapMigrateBackupFragment @Inject constructor( class BootstrapMigrateBackupFragment @Inject constructor(
private val colorProvider: ColorProvider 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
@ -58,9 +62,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
withState(sharedViewModel) { withState(sharedViewModel) {
// set initial value (useful when coming back) // set initial value (useful when coming back)
bootstrapMigrateEditText.setText(it.passphrase ?: "") views.bootstrapMigrateEditText.setText(it.passphrase ?: "")
} }
bootstrapMigrateEditText.editorActionEvents() views.bootstrapMigrateEditText.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
@ -70,19 +74,19 @@ class BootstrapMigrateBackupFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
bootstrapMigrateEditText.textChanges() views.bootstrapMigrateEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .subscribe {
bootstrapRecoveryKeyEnterTil.error = null views.bootstrapRecoveryKeyEnterTil.error = null
// sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) // sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
} }
.disposeOnDestroyView() .disposeOnDestroyView()
// sharedViewModel.observeViewEvents {} // sharedViewModel.observeViewEvents {}
bootstrapMigrateContinueButton.debouncedClicks { submit() } views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } views.bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) } views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
} }
private fun submit() = withState(sharedViewModel) { state -> private fun submit() = withState(sharedViewModel) { state ->
@ -90,12 +94,12 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey() val isEnteringKey = getBackupSecretForMigration.useKey()
val secret = bootstrapMigrateEditText.text?.toString() val secret = views.bootstrapMigrateEditText.text?.toString()
if (secret.isNullOrEmpty()) { if (secret.isNullOrEmpty()) {
val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message
bootstrapRecoveryKeyEnterTil.error = getString(errRes) views.bootstrapRecoveryKeyEnterTil.error = getString(errRes)
} else if (isEnteringKey && !isValidRecoveryKey(secret)) { } else if (isEnteringKey && !isValidRecoveryKey(secret)) {
bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key) views.bootstrapRecoveryKeyEnterTil.error = getString(R.string.bootstrap_invalid_recovery_key)
} else { } else {
view?.hideKeyboard() view?.hideKeyboard()
if (isEnteringKey) { if (isEnteringKey) {
@ -112,38 +116,38 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey() val isEnteringKey = getBackupSecretForMigration.useKey()
if (isEnteringKey) { if (isEnteringKey) {
bootstrapMigrateShowPassword.isVisible = false views.bootstrapMigrateShowPassword.isVisible = false
bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE views.bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE
val recKey = getString(R.string.bootstrap_migration_backup_recovery_key) val recKey = getString(R.string.bootstrap_migration_backup_recovery_key)
bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey) views.bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey)
bootstrapMigrateEditText.hint = recKey views.bootstrapMigrateEditText.hint = recKey
bootstrapMigrateEditText.hint = recKey views.bootstrapMigrateEditText.hint = recKey
bootstrapMigrateForgotPassphrase.isVisible = false views.bootstrapMigrateForgotPassphrase.isVisible = false
bootstrapMigrateUseFile.isVisible = true views.bootstrapMigrateUseFile.isVisible = true
} else { } else {
bootstrapMigrateShowPassword.isVisible = true views.bootstrapMigrateShowPassword.isVisible = true
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) { if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
val isPasswordVisible = state.step.isPasswordVisible val isPasswordVisible = state.step.isPasswordVisible
bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false) views.bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye) views.bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
} }
bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password) views.bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)
bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase) views.bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase)
bootstrapMigrateForgotPassphrase.isVisible = true views.bootstrapMigrateForgotPassphrase.isVisible = true
val recKey = getString(R.string.bootstrap_migration_use_recovery_key) val recKey = getString(R.string.bootstrap_migration_use_recovery_key)
bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey) views.bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey)
.toSpannable() .toSpannable()
.colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
bootstrapMigrateUseFile.isVisible = false views.bootstrapMigrateUseFile.isVisible = false
} }
} }
@ -155,7 +159,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
?.bufferedReader() ?.bufferedReader()
?.use { it.readText() } ?.use { it.readText() }
?.let { ?.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.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState 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.resources.ColorProvider
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.core.utils.toast 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.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -38,18 +41,20 @@ import javax.inject.Inject
class BootstrapSaveRecoveryKeyFragment @Inject constructor( class BootstrapSaveRecoveryKeyFragment @Inject constructor(
private val colorProvider: ColorProvider 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() } views.recoverySave.views.itemVerificationClickableZone.debouncedClicks { downloadRecoveryKey() }
recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() } views.recoveryCopy.views.itemVerificationClickableZone.debouncedClicks { shareRecoveryKey() }
recoveryContinue.clickableView.debouncedClicks { views.recoveryContinue.views.itemVerificationClickableZone.debouncedClicks {
// We do not display the final Fragment anymore // We do not display the final Fragment anymore
// TODO Do some cleanup // TODO Do some cleanup
// sharedViewModel.handle(BootstrapActions.GoToCompleted) // sharedViewModel.handle(BootstrapActions.GoToCompleted)
@ -112,7 +117,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
val step = state.step val step = state.step
if (step !is BootstrapStep.SaveRecoveryKey) return@withState if (step !is BootstrapStep.SaveRecoveryKey) return@withState
recoveryContinue.isVisible = step.isSaved views.recoveryContinue.isVisible = step.isSaved
bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() views.bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
} }
} }

View File

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

View File

@ -16,26 +16,31 @@
package im.vector.app.features.crypto.recover package im.vector.app.features.crypto.recover
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment 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 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() val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
when (state.step) { when (state.step) {
is BootstrapStep.Initializing -> { is BootstrapStep.Initializing -> {
bootstrapLoadingStatusText.isVisible = true views.bootstrapLoadingStatusText.isVisible = true
bootstrapDescriptionText.isVisible = true views.bootstrapDescriptionText.isVisible = true
bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message views.bootstrapLoadingStatusText.text = state.initializationWaitingViewData?.message
} }
// is BootstrapStep.CheckingMigration -> { // is BootstrapStep.CheckingMigration -> {
// bootstrapLoadingStatusText.isVisible = false // bootstrapLoadingStatusText.isVisible = false
@ -43,8 +48,8 @@ class BootstrapWaitingFragment @Inject constructor() : VectorBaseFragment() {
// } // }
else -> { else -> {
// just show the spinner // just show the spinner
bootstrapLoadingStatusText.isVisible = false views.bootstrapLoadingStatusText.isVisible = false
bootstrapDescriptionText.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.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.view.KeyEvent import android.view.KeyEvent
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.DialogRecoveryKeySavedInfoBinding
import me.gujun.android.span.image import me.gujun.android.span.image
import me.gujun.android.span.span import me.gujun.android.span.span
@ -30,10 +30,9 @@ class KeepItSafeDialog {
fun show(activity: Activity) { fun show(activity: Activity) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_recovery_key_saved_info, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_recovery_key_saved_info, null)
val views = DialogRecoveryKeySavedInfoBinding.bind(dialogLayout)
val descriptionText = dialogLayout.findViewById<TextView>(R.id.keepItSafeText) views.keepItSafeText.text = span {
descriptionText.text = span {
span { span {
image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!) image(ContextCompat.getDrawable(activity, R.drawable.ic_check_on)!!)
+" " +" "

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,19 +18,20 @@ package im.vector.app.features.crypto.verification.qrconfirmation
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import kotlinx.android.parcel.Parcelize import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* import kotlinx.parcelize.Parcelize
import javax.inject.Inject import javax.inject.Inject
class VerificationQRWaitingFragment @Inject constructor( class VerificationQRWaitingFragment @Inject constructor(
val controller: VerificationQRWaitingController val controller: VerificationQRWaitingController
) : VectorBaseFragment() { ) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>() {
@Parcelize @Parcelize
data class Args( data class Args(
@ -38,7 +39,9 @@ class VerificationQRWaitingFragment @Inject constructor(
val otherUserName: String val otherUserName: String
) : Parcelable ) : 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -49,11 +52,11 @@ class VerificationQRWaitingFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup() views.bottomSheetVerificationRecyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
} }
private fun setupRecyclerView() { 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 package im.vector.app.features.crypto.verification.qrconfirmation
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment 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.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject import javax.inject.Inject
class VerificationQrScannedByOtherFragment @Inject constructor( class VerificationQrScannedByOtherFragment @Inject constructor(
val controller: VerificationQrScannedByOtherController val controller: VerificationQrScannedByOtherController
) : VectorBaseFragment(), VerificationQrScannedByOtherController.Listener { ) : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
VerificationQrScannedByOtherController.Listener {
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -46,13 +51,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor(
} }
override fun onDestroyView() { override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup() views.bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null controller.listener = null
super.onDestroyView() super.onDestroyView()
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this controller.listener = this
} }

View File

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

View File

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

View File

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

View File

@ -18,39 +18,44 @@
package im.vector.app.features.grouplist package im.vector.app.features.grouplist
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGroupListBinding
import im.vector.app.features.home.HomeActivitySharedAction import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel 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 org.matrix.android.sdk.api.session.group.model.GroupSummary
import javax.inject.Inject import javax.inject.Inject
class GroupListFragment @Inject constructor( class GroupListFragment @Inject constructor(
val groupListViewModelFactory: GroupListViewModel.Factory, val groupListViewModelFactory: GroupListViewModel.Factory,
private val groupController: GroupSummaryController private val groupController: GroupSummaryController
) : VectorBaseFragment(), GroupSummaryController.Callback { ) : VectorBaseFragment<FragmentGroupListBinding>(),
GroupSummaryController.Callback {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private val viewModel: GroupListViewModel by fragmentViewModel() 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
groupController.callback = this groupController.callback = this
stateView.contentView = groupListView views.stateView.contentView = views.groupListView
groupListView.configureWith(groupController) views.groupListView.configureWith(groupController)
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
@ -60,14 +65,14 @@ class GroupListFragment @Inject constructor(
override fun onDestroyView() { override fun onDestroyView() {
groupController.callback = null groupController.callback = null
groupListView.cleanup() views.groupListView.cleanup()
super.onDestroyView() super.onDestroyView()
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
when (state.asyncGroups) { when (state.asyncGroups) {
is Incomplete -> stateView.state = StateView.State.Loading is Incomplete -> views.stateView.state = StateView.State.Loading
is Success -> stateView.state = StateView.State.Content is Success -> views.stateView.state = StateView.State.Content
} }
groupController.update(state) 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.platform.VectorBaseActivity
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.utils.toast 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.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.notifications.NotificationDrawerManager 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.features.workers.signout.ServerBackupStatusViewState
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.matrix.android.sdk.api.session.InitialSyncProgressService import org.matrix.android.sdk.api.session.InitialSyncProgressService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -71,7 +70,11 @@ data class HomeActivityArgs(
val accountCreation: Boolean val accountCreation: Boolean
) : Parcelable ) : Parcelable
class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory, class HomeActivity :
VectorBaseActivity<ActivityHomeBinding>(),
ToolbarConfigurable,
UnknownDeviceDetectorSharedViewModel.Factory,
ServerBackupStatusViewModel.Factory,
NavigationInterceptor { NavigationInterceptor {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel 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) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -116,7 +119,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice()) FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
drawerLayout.addDrawerListener(drawerListener) views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) { if (isFirstCreation()) {
replaceFragment(R.id.homeDetailFragmentContainer, LoadingFragment::class.java) replaceFragment(R.id.homeDetailFragmentContainer, LoadingFragment::class.java)
replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java) replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
@ -126,10 +129,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
.observe() .observe()
.subscribe { sharedAction -> .subscribe { sharedAction ->
when (sharedAction) { when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> { is HomeActivitySharedAction.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
} }
}.exhaustive }.exhaustive
@ -197,24 +200,24 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) { when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> { is InitialSyncProgressService.Status.Idle -> {
waiting_view.isVisible = false views.waitingView.root.isVisible = false
} }
is InitialSyncProgressService.Status.Progressing -> { is InitialSyncProgressService.Status.Progressing -> {
Timber.v("${getString(status.statusText)} ${status.percentProgress}") Timber.v("${getString(status.statusText)} ${status.percentProgress}")
waiting_view.setOnClickListener { views.waitingView.root.setOnClickListener {
// block interactions // block interactions
} }
waiting_view_status_horizontal_progress.apply { views.waitingView.waitingHorizontalProgress.apply {
isIndeterminate = false isIndeterminate = false
max = 100 max = 100
progress = status.percentProgress progress = status.percentProgress
isVisible = true isVisible = true
} }
waiting_view_status_text.apply { views.waitingView.waitingStatusText.apply {
text = getString(status.statusText) text = getString(status.statusText)
isVisible = true isVisible = true
} }
waiting_view.isVisible = true views.waitingView.root.isVisible = true
} }
}.exhaustive }.exhaustive
} }
@ -269,7 +272,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
).apply { ).apply {
colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary) colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
// action(it) // action(it)
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS) it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS)
@ -282,7 +285,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
}, true) }, true)
addButton(getString(R.string.settings), Runnable { addButton(getString(R.string.settings), Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
// action(it) // action(it)
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS) 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( popupAlertManager.postVectorAlert(
VerificationVectorAlert( VerificationVectorAlert(
uid = "upgradeSecurity", uid = "upgradeSecurity",
@ -303,7 +306,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
).apply { ).apply {
colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
action(it) action(it)
} }
} }
@ -321,7 +324,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
} }
override fun onDestroy() { override fun onDestroy() {
drawerLayout.removeDrawerListener(drawerListener) views.drawerLayout.removeDrawerListener(drawerListener)
super.onDestroy() super.onDestroy()
} }
@ -375,8 +378,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
} }
override fun onBackPressed() { override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) { if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
} else { } else {
super.onBackPressed() super.onBackPressed()
} }

View File

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

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