mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 11:26:01 +03:00
WIP
This commit is contained in:
parent
0997d9abf4
commit
38906084d1
28 changed files with 833 additions and 37 deletions
|
@ -34,6 +34,8 @@ allprojects {
|
|||
includeGroupByRegex "com\\.github\\.jaiselrahman"
|
||||
// And monarchy
|
||||
includeGroupByRegex "com\\.github\\.Zhuinden"
|
||||
// And QR lib
|
||||
includeGroupByRegex "com\\.github\\.kenglxn\\.QRGen"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
|
|
|
@ -59,6 +59,8 @@ interface SasVerificationService {
|
|||
otherDeviceId: String,
|
||||
callback: MatrixCallback<String>?): String?
|
||||
|
||||
fun readyPendingVerificationInDMs(transactionId: String)
|
||||
|
||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||
|
||||
interface SasVerificationListener {
|
||||
|
|
|
@ -73,6 +73,7 @@ object EventType {
|
|||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
||||
const val KEY_VERIFICATION_READY = "m.key.verification.ready"
|
||||
|
||||
// Relation Events
|
||||
const val REACTION = "m.reaction"
|
||||
|
|
|
@ -75,6 +75,16 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||
// map [sender : [transaction]]
|
||||
private val txMap = HashMap<String, HashMap<String, VerificationTransaction>>()
|
||||
|
||||
/**
|
||||
* Map [sender: [PendingVerificationRequest]]
|
||||
*/
|
||||
private val incomingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
|
||||
|
||||
/**
|
||||
* Map [sender: [PendingVerificationRequest]]
|
||||
*/
|
||||
private val outgoingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
|
||||
|
||||
// Event received from the sync
|
||||
fun onToDeviceEvent(event: Event) {
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
|
@ -190,8 +200,32 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
fun onRoomRequestReceived(event: Event) {
|
||||
// TODO
|
||||
Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
|
||||
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
|
||||
?: return
|
||||
val senderId = event.senderId ?: return
|
||||
// Remember this request
|
||||
val requestsForUser = incomingRequests[senderId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
incomingRequests[event.senderId] = it
|
||||
}
|
||||
|
||||
val pendingVerificationRequest = PendingVerificationRequest(
|
||||
transactionId = event.eventId,
|
||||
requestInfo = requestInfo
|
||||
)
|
||||
requestsForUser.add(pendingVerificationRequest)
|
||||
|
||||
/*
|
||||
* After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event
|
||||
* to begin the verification.
|
||||
* If both parties send an m.key.verification.start event, and they both specify the same verification method,
|
||||
* then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start
|
||||
* event is ignored.
|
||||
* In the case of a single user verifying two of their devices, the device ID is compared instead.
|
||||
* If both parties send an m.key.verification.start event, but they specify different verification methods,
|
||||
* the verification should be cancelled with a code of m.unexpected_message.
|
||||
*/
|
||||
}
|
||||
|
||||
private suspend fun onRoomStartRequestReceived(event: Event) {
|
||||
|
@ -537,17 +571,29 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) {
|
||||
val requestsForUser = outgoingRequests[userId]
|
||||
?: ArrayList<PendingVerificationRequest>().also {
|
||||
outgoingRequests[userId] = it
|
||||
}
|
||||
|
||||
val params = requestVerificationDMTask.createParamsAndLocalEcho(
|
||||
roomId = roomId,
|
||||
from = credentials.deviceId ?: "",
|
||||
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
to = userId,
|
||||
cryptoService = cryptoService
|
||||
)
|
||||
requestVerificationDMTask.configureWith(
|
||||
requestVerificationDMTask.createParamsAndLocalEcho(
|
||||
roomId = roomId,
|
||||
from = credentials.deviceId ?: "",
|
||||
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||
to = userId,
|
||||
cryptoService = cryptoService
|
||||
)
|
||||
params
|
||||
) {
|
||||
this.callback = object : MatrixCallback<SendResponse> {
|
||||
override fun onSuccess(data: SendResponse) {
|
||||
params.event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
||||
requestsForUser.add(PendingVerificationRequest(
|
||||
transactionId = data.eventId,
|
||||
requestInfo = it
|
||||
))
|
||||
}
|
||||
callback?.onSuccess(data.eventId)
|
||||
}
|
||||
|
||||
|
@ -582,6 +628,9 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun readyPendingVerificationInDMs(transactionId: String) {
|
||||
//
|
||||
}
|
||||
/**
|
||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2019 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.matrix.android.internal.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
|
||||
/**
|
||||
* Stores current pending verification requests
|
||||
*/
|
||||
internal data class PendingVerificationRequest(
|
||||
val transactionId: String?,
|
||||
val requestInfo: MessageVerificationRequestContent?,
|
||||
val readyInfo: VerificationInfoReady? = null
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2019 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.matrix.android.internal.crypto.verification
|
||||
|
||||
/**
|
||||
* A new event type is added to the key verification framework: m.key.verification.ready,
|
||||
* which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event.
|
||||
*
|
||||
* The m.key.verification.ready event is optional; the recipient of the m.key.verification.request event may respond directly
|
||||
* with a m.key.verification.start event instead.
|
||||
*/
|
||||
internal interface VerificationInfoReady : VerificationInfo {
|
||||
|
||||
val transactionID: String?
|
||||
|
||||
/**
|
||||
* The ID of the device that sent the m.key.verification.ready message
|
||||
*/
|
||||
val fromDevice: String?
|
||||
|
||||
/**
|
||||
* An array of verification methods that the device supports
|
||||
*/
|
||||
val methods: List<String>?
|
||||
}
|
|
@ -17,6 +17,9 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||
|
||||
internal interface VerificationInfoStart : VerificationInfo {
|
||||
|
||||
/**
|
||||
* An array of verification methods that the device supports
|
||||
*/
|
||||
val method: String?
|
||||
/**
|
||||
* Alice’s device ID
|
||||
|
|
|
@ -46,6 +46,7 @@ internal interface EventRelationsAggregationTask : Task<EventRelationsAggregatio
|
|||
}
|
||||
|
||||
enum class VerificationState {
|
||||
UNKNOWN,
|
||||
REQUEST,
|
||||
WAITING,
|
||||
CANCELED_BY_ME,
|
||||
|
|
|
@ -21,4 +21,21 @@
|
|||
|
||||
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
|
||||
|
||||
<!-- Sender name of a message when it is send by you, e.g. You: Hello!-->
|
||||
<string name="you">You</string>
|
||||
|
||||
<string name="verify_by_scanning_title">Verify by scanning</string>
|
||||
<!-- the %s will be replaced by verify_open_camera_link that will be clickable -->
|
||||
<string name="verify_by_scanning_description">Ask the other user to scan this code, or %s to scan theirs</string>
|
||||
<!-- This part is inserted in verify_by_scanning_description-->
|
||||
<string name="verify_open_camera_link">open your camera</string>
|
||||
|
||||
<string name="verify_by_emoji_title">Verify by emoji</string>
|
||||
<string name="verify_by_emoji_description">If you can’t scan the code above, verify by comparing a short, unique selection of emoji.</string>
|
||||
|
||||
<string name="aria_qr_code_description">QR code image</string>
|
||||
|
||||
<string name="verification_request_alert_title">Verify %s</string>
|
||||
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
||||
<string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string>
|
||||
</resources>
|
||||
|
|
|
@ -345,6 +345,9 @@ dependencies {
|
|||
|
||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||
|
||||
// QR codes
|
||||
// implementation 'com.github.kenglxn.QRGen:javase:2.6.0'
|
||||
|
||||
// TESTS
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
|
|
|
@ -23,10 +23,7 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationShortCodeFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationStartFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragment
|
||||
import im.vector.riotx.features.crypto.verification.*
|
||||
import im.vector.riotx.features.home.HomeDetailFragment
|
||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
|
@ -272,4 +269,15 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(SoftLogoutFragment::class)
|
||||
fun bindSoftLogoutFragment(fragment: SoftLogoutFragment): Fragment
|
||||
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(OutgoingVerificationRequestFragment::class)
|
||||
fun bindVerificationRequestFragment(fragment: OutgoingVerificationRequestFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(VerificationChooseMethodFragment::class)
|
||||
fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.riotx.core.error.ErrorFormatter
|
|||
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||
import im.vector.riotx.features.MainActivity
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.home.HomeModule
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||
|
@ -133,6 +134,8 @@ interface ScreenComponent {
|
|||
|
||||
fun inject(activity: SoftLogoutActivity)
|
||||
|
||||
fun inject(verificationBottomSheet: VerificationBottomSheet)
|
||||
|
||||
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
||||
|
||||
@Component.Factory
|
||||
|
|
|
@ -21,17 +21,17 @@ import android.os.Bundle
|
|||
import android.os.Parcelable
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.MvRxView
|
||||
import com.airbnb.mvrx.MvRxViewId
|
||||
import com.airbnb.mvrx.*
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import kotlin.reflect.KClass
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
|||
override fun onAttach(context: Context) {
|
||||
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
||||
viewModelFactory = screenComponent.viewModelFactory()
|
||||
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
||||
super.onAttach(context)
|
||||
injectWith(screenComponent)
|
||||
}
|
||||
|
@ -121,3 +122,16 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
|||
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, reified VM : BaseMvRxViewModel<S>, reified S : MvRxState> T.parentFragmentViewModel(
|
||||
viewModelClass: KClass<VM> = VM::class,
|
||||
crossinline keyFactory: () -> String = { viewModelClass.java.name }
|
||||
) where T : Fragment, T : MvRxView = lifecycleAwareLazy(this) {
|
||||
MvRxViewModelProvider.get(
|
||||
viewModelClass.java,
|
||||
S::class.java,
|
||||
FragmentViewModelContext(this.requireActivity(), _fragmentArgsProvider(), this.parentFragment
|
||||
?: this),
|
||||
keyFactory()
|
||||
).apply { subscribe(this@parentFragmentViewModel, subscriber = { postInvalidate() }) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.riotx.core.utils
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StyleSpan
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable {
|
||||
if (match.isEmpty()) return this
|
||||
indexOf(match).takeIf { it != -1 }?.let { start ->
|
||||
this.setSpan(StyleSpan(typeFace), start, start + match.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun Spannable.colorizeMatchingText(match: String, @ColorInt color: Int): Spannable {
|
||||
if (match.isEmpty()) return this
|
||||
indexOf(match).takeIf { it != -1 }?.let { start ->
|
||||
this.setSpan(ForegroundColorSpan(color), start, start + match.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return this
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotx.features.crypto.verification
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.commitTransaction
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.platform.parentFragmentViewModel
|
||||
import im.vector.riotx.core.utils.colorizeMatchingText
|
||||
import im.vector.riotx.core.utils.styleMatchingText
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import kotlinx.android.synthetic.main.fragment_verification_request.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class OutgoingVerificationRequestFragment @Inject constructor(
|
||||
val outgoingVerificationRequestViewModelFactory: OutgoingVerificationRequestViewModel.Factory,
|
||||
val avatarRenderer: AvatarRenderer
|
||||
) : VectorBaseFragment() {
|
||||
|
||||
private val viewModel by fragmentViewModel(OutgoingVerificationRequestViewModel::class)
|
||||
|
||||
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_verification_request
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
state.matrixItem?.let {
|
||||
val styledText = getString(R.string.verification_request_alert_description, it.id)
|
||||
.toSpannable()
|
||||
.styleMatchingText(it.id, Typeface.BOLD)
|
||||
.colorizeMatchingText(it.id, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color))
|
||||
verificationRequestText.text = styledText
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@OnClick(R.id.verificationStartButton)
|
||||
fun onClickOnVerificationStart() = withState(viewModel) { state ->
|
||||
|
||||
sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.otherUserId))
|
||||
|
||||
getParentCoordinatorLayout()?.let {
|
||||
TransitionManager.beginDelayedTransition(it, AutoTransition().apply { duration = 150 })
|
||||
}
|
||||
parentFragmentManager.commitTransaction {
|
||||
replace(R.id.bottomSheetFragmentContainer,
|
||||
VerificationChooseMethodFragment::class.java,
|
||||
Bundle().apply { putString(MvRx.KEY_ARG, state.otherUserId) },
|
||||
"REQUEST"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2019 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.riotx.features.crypto.verification
|
||||
|
||||
import com.airbnb.mvrx.*
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
|
||||
data class VerificationRequestViewState(
|
||||
val otherUserId: String = "",
|
||||
val matrixItem: MatrixItem? = null,
|
||||
val started: Async<Boolean> = Success(false)
|
||||
) : MvRxState
|
||||
|
||||
sealed class VerificationAction : VectorViewModelAction {
|
||||
data class RequestVerificationByDM(val userID: String) : VerificationAction()
|
||||
}
|
||||
|
||||
class OutgoingVerificationRequestViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VerificationRequestViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<VerificationRequestViewState, VerificationAction>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: VerificationRequestViewState): OutgoingVerificationRequestViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
withState {
|
||||
val user = session.getUser(it.otherUserId)
|
||||
setState {
|
||||
copy(matrixItem = user?.toMatrixItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<OutgoingVerificationRequestViewModel, VerificationRequestViewState> {
|
||||
override fun create(viewModelContext: ViewModelContext, state: VerificationRequestViewState): OutgoingVerificationRequestViewModel? {
|
||||
val fragment: OutgoingVerificationRequestFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.outgoingVerificationRequestViewModelFactory.create(state)
|
||||
}
|
||||
|
||||
override fun initialState(viewModelContext: ViewModelContext): VerificationRequestViewState? {
|
||||
val userID: String = viewModelContext.args<String>()
|
||||
return VerificationRequestViewState(otherUserId = userID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun handle(action: VerificationAction) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package im.vector.riotx.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.fragment.app.Fragment
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.commitTransaction
|
||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.riotx.core.utils.colorizeMatchingText
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
|
||||
@Inject lateinit var outgoingVerificationRequestViewModelFactory: OutgoingVerificationRequestViewModel.Factory
|
||||
@Inject lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
|
||||
private val viewModel by fragmentViewModel(VerificationBottomSheetViewModel::class)
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
@BindView(R.id.verificationRequestName)
|
||||
lateinit var otherUserNameText: TextView
|
||||
|
||||
@BindView(R.id.verificationRequestAvatar)
|
||||
lateinit var otherUserAvatarImageView: ImageView
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.bottom_sheet_verification, container, false)
|
||||
ButterKnife.bind(this, view)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
when (it.verificationRequestEvent) {
|
||||
is Uninitialized -> {
|
||||
if (childFragmentManager.findFragmentByTag("REQUEST") == null) {
|
||||
//Verification not yet started, put outgoing verification
|
||||
childFragmentManager.commitTransaction {
|
||||
setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
|
||||
replace(R.id.bottomSheetFragmentContainer,
|
||||
OutgoingVerificationRequestFragment::class.java,
|
||||
Bundle().apply { putString(MvRx.KEY_ARG, it.userId) },
|
||||
"REQUEST"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it.otherUserId?.let { matrixItem ->
|
||||
val displayName = matrixItem.displayName ?: ""
|
||||
otherUserNameText.text = getString(R.string.verification_request_alert_title, displayName)
|
||||
.toSpannable()
|
||||
.colorizeMatchingText(displayName, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color))
|
||||
|
||||
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
|
||||
}
|
||||
|
||||
super.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Fragment.getParentCoordinatorLayout(): CoordinatorLayout? {
|
||||
var current = view?.parent as? View
|
||||
while (current != null) {
|
||||
if (current is CoordinatorLayout) return current
|
||||
current = current.parent as? View
|
||||
}
|
||||
return null
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package im.vector.riotx.features.crypto.verification
|
||||
|
||||
import com.airbnb.mvrx.*
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
|
||||
data class VerificationBottomSheetViewState(
|
||||
val userId: String = "",
|
||||
val otherUserId: MatrixItem? = null,
|
||||
val verificationRequestEvent: Async<TimelineEvent> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction>(initialState) {
|
||||
|
||||
init {
|
||||
withState {
|
||||
session.getUser(it.userId).let { user ->
|
||||
setState {
|
||||
copy(otherUserId = user?.toMatrixItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: VerificationBottomSheetViewState): VerificationBottomSheetViewModel
|
||||
}
|
||||
|
||||
|
||||
companion object : MvRxViewModelFactory<VerificationBottomSheetViewModel, VerificationBottomSheetViewState> {
|
||||
|
||||
override fun create(viewModelContext: ViewModelContext, state: VerificationBottomSheetViewState): VerificationBottomSheetViewModel? {
|
||||
val fragment: VerificationBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
val userId: String = viewModelContext.args()
|
||||
return fragment.verificationViewModelFactory.create(VerificationBottomSheetViewState(userId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: VerificationAction) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package im.vector.riotx.features.crypto.verification
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.ChangeBounds
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.OnClick
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.commitTransaction
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationChooseMethodFragment @Inject constructor() : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_verification_choose_method
|
||||
|
||||
// init {
|
||||
// sharedElementEnterTransition = ChangeBounds()
|
||||
// sharedElementReturnTransition = ChangeBounds()
|
||||
// }
|
||||
|
||||
@OnClick(R.id.verificationByEmojiButton)
|
||||
fun test() { //withState(viewModel) { state ->
|
||||
getParentCoordinatorLayout()?.let {
|
||||
TransitionManager.beginDelayedTransition(it, AutoTransition().apply { duration = 150 })
|
||||
}
|
||||
parentFragmentManager.commitTransaction {
|
||||
// setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
|
||||
replace(R.id.bottomSheetFragmentContainer,
|
||||
OutgoingVerificationRequestFragment::class.java,
|
||||
Bundle().apply { putString(MvRx.KEY_ARG, "@valere35:matrix.org") },
|
||||
"REQUEST"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,4 +66,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
|
||||
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
||||
data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction()
|
||||
|
||||
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
|||
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||
import im.vector.riotx.features.command.Command
|
||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.getColorFromUserId
|
||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
||||
|
@ -431,7 +432,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
|
||||
|
||||
avatarRenderer.render(
|
||||
MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
|
||||
MatrixItem.UserItem(event.root.senderId
|
||||
?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
|
||||
composerLayout.composerRelatedMessageAvatar
|
||||
)
|
||||
composerLayout.expand {
|
||||
|
@ -923,7 +925,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
is Success -> {
|
||||
when (val data = result.invoke()) {
|
||||
is RoomDetailAction.ReportContent -> {
|
||||
is RoomDetailAction.ReportContent -> {
|
||||
when {
|
||||
data.spam -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
|
@ -960,6 +962,11 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
is RoomDetailAction.RequestVerification -> {
|
||||
VerificationBottomSheet().apply {
|
||||
arguments = Bundle().apply { putString(MvRx.KEY_ARG, data.userId) }
|
||||
}.show(parentFragmentManager, "REQ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,8 +184,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,7 +398,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
popDraft()
|
||||
}
|
||||
is ParsedCommand.VerifyUser -> {
|
||||
session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null)
|
||||
//
|
||||
_requestLiveData.postValue(LiveEvent(Success(RoomDetailAction.RequestVerification(slashCommandResult.userId))))
|
||||
// session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
|
|
|
@ -23,13 +23,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import me.gujun.android.span.span
|
||||
import javax.inject.Inject
|
||||
|
||||
class DisplayableEventFormatter @Inject constructor(
|
||||
// private val sessionHolder: ActiveSessionHolder,
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val noticeEventFormatter: NoticeEventFormatter
|
||||
|
@ -41,32 +42,36 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
return stringProvider.getString(R.string.encrypted_message)
|
||||
}
|
||||
|
||||
val senderName = timelineEvent.getDisambiguatedDisplayName()
|
||||
sessionHolder.getActiveSession().myUserId
|
||||
val senderName = if (sessionHolder.getActiveSession().myUserId == timelineEvent.root.senderId)
|
||||
stringProvider.getString(R.string.you)
|
||||
else
|
||||
timelineEvent.getDisambiguatedDisplayName()
|
||||
|
||||
when (timelineEvent.root.getClearType()) {
|
||||
EventType.MESSAGE -> {
|
||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
||||
when (messageContent.type) {
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.verification_request), appendAuthor)
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.verification_request).italicSpan(), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_IMAGE -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image), appendAuthor)
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image).italicSpan(), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_AUDIO -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor)
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file).italicSpan(), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_VIDEO -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_video), appendAuthor)
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_video).italicSpan(), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_FILE -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_file), appendAuthor)
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_file).italicSpan(), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_TEXT -> {
|
||||
if (messageContent.isReply()) {
|
||||
// Skip reply prefix, and show important
|
||||
// TODO add a reply image span ?
|
||||
return simpleFormat(senderName, timelineEvent.getTextEditableContent()
|
||||
return simpleFormat(senderName, timelineEvent.getTextEditableContent()?.let { "↩︎ $it" }
|
||||
?: messageContent.body, appendAuthor)
|
||||
} else {
|
||||
return simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
|
@ -101,4 +106,11 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
body
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.italicSpan(): CharSequence {
|
||||
return span {
|
||||
text = this@italicSpan
|
||||
textStyle = "italic"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,14 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getEditedEventId
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageBody
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,7 @@ import javax.inject.Inject
|
|||
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
|
||||
*/
|
||||
class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val noticeEventFormatter: NoticeEventFormatter) {
|
||||
|
||||
// private val eventDisplay = RiotEventDisplay(context)
|
||||
|
@ -86,13 +87,11 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||
private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? {
|
||||
// The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
|
||||
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)
|
||||
|
||||
val body = displayableEventFormatter.format(event, false).toString()
|
||||
if (room == null) {
|
||||
Timber.e("## Unable to resolve room for eventId [$event]")
|
||||
// Ok room is not known in store, but we can still display something
|
||||
val body =
|
||||
event.getLastMessageBody()
|
||||
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
||||
|
||||
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
|
||||
val senderDisplayName = event.getDisambiguatedDisplayName()
|
||||
|
||||
|
@ -125,8 +124,6 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||
}
|
||||
}
|
||||
|
||||
val body = event.getLastMessageBody()
|
||||
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
||||
val roomName = room.roomSummary()?.displayName ?: ""
|
||||
val senderDisplayName = event.getDisambiguatedDisplayName()
|
||||
|
||||
|
|
55
vector/src/main/res/layout/bottom_sheet_verification.xml
Normal file
55
vector/src/main/res/layout/bottom_sheet_verification.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/bottomSheetScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/verificationRequestAvatar"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/circle"
|
||||
android:contentDescription="@string/avatar"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verificationRequestName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/verification_request_alert_title"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bottomSheetFragmentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -0,0 +1,116 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/verificationRequestAvatar"-->
|
||||
<!-- android:layout_width="32dp"-->
|
||||
<!-- android:layout_height="32dp"-->
|
||||
<!-- android:adjustViewBounds="true"-->
|
||||
<!-- android:background="@drawable/circle"-->
|
||||
<!-- android:contentDescription="@string/avatar"-->
|
||||
<!-- android:scaleType="centerCrop"-->
|
||||
<!-- android:transitionName="bottomSheetAvatar"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintVertical_bias="0"-->
|
||||
<!-- tools:src="@tools:sample/avatars" />-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/verificationRequestName"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_marginStart="16dp"-->
|
||||
<!-- android:text="@string/verification_request_alert_title"-->
|
||||
<!-- android:textColor="?riotx_text_primary"-->
|
||||
<!-- android:textSize="20sp"-->
|
||||
<!-- android:textStyle="bold"-->
|
||||
<!-- android:transitionName="bottomSheetDisplayName"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="@id/verificationRequestAvatar"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/verificationRequestAvatar"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="@id/verificationRequestAvatar" />-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verificationQRTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/verify_by_scanning_title"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verifyQRDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/verify_by_scanning_description"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/verificationQRTitle"
|
||||
tools:text="@string/verify_by_scanning_description" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/verifyQRImageView"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="180dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:contentDescription="@string/aria_qr_code_description"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/verifyQRDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verificationEmojiTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="@string/verify_by_emoji_title"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toBottomOf="@id/verifyQRImageView"
|
||||
app:layout_goneMarginTop="0dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verifyEmojiDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/verify_by_emoji_description"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/verificationEmojiTitle" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/verificationByEmojiButton"
|
||||
style="@style/VectorButtonStylePositive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/accept"
|
||||
app:layout_constraintTop_toBottomOf="@id/verifyEmojiDescription" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/verifyQRGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:constraint_referenced_ids="verifyQRDescription,verificationQRTitle,verifyQRImageView" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/verifyEmojiGroup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:constraint_referenced_ids="verifyEmojiDescription,verificationEmojiTitle,verificationByEmojiButton" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
73
vector/src/main/res/layout/fragment_verification_request.xml
Normal file
73
vector/src/main/res/layout/fragment_verification_request.xml
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/verificationRequestAvatar"-->
|
||||
<!-- android:layout_width="32dp"-->
|
||||
<!-- android:layout_height="32dp"-->
|
||||
<!-- android:adjustViewBounds="true"-->
|
||||
<!-- android:background="@drawable/circle"-->
|
||||
<!-- android:contentDescription="@string/avatar"-->
|
||||
<!-- android:scaleType="centerCrop"-->
|
||||
<!-- android:transitionName="bottomSheetAvatar"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintVertical_bias="0"-->
|
||||
<!-- tools:src="@tools:sample/avatars" />-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/verificationRequestName"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_marginStart="16dp"-->
|
||||
<!-- android:text="@string/verification_request_alert_title"-->
|
||||
<!-- android:textColor="?riotx_text_primary"-->
|
||||
<!-- android:textSize="20sp"-->
|
||||
<!-- android:textStyle="bold"-->
|
||||
<!-- android:transitionName="bottomSheetDisplayName"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="@id/verificationRequestAvatar"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/verificationRequestAvatar"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="@id/verificationRequestAvatar" />-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verificationRequestText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/verification_request_alert_description" />
|
||||
|
||||
<!-- app:layout_constraintTop_toBottomOf="@id/verificationRequestAvatar"-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/verificationStartButton"
|
||||
style="@style/VectorButtonStylePositive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/start_verification"
|
||||
app:layout_constraintTop_toBottomOf="@id/verificationRequestText"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verificationWaitingText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="?vctr_notice_secondary"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@id/verificationStartButton"
|
||||
app:layout_constraintTop_toTopOf="@id/verificationStartButton"
|
||||
tools:text="@string/verification_request_waiting_for" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1168,7 +1168,7 @@
|
|||
<string name="your_unverified_device_requesting">Your unverified device \'%s\' is requesting encryption keys.</string>
|
||||
<string name="your_unverified_device_requesting_with_info">An unverified device is requesting encryption keys.\nDevice name: %1$s\nLast seen: %2$s\nIf you didn’t log in on another device, ignore this request.</string>
|
||||
|
||||
<string name="start_verification">Start verification</string>
|
||||
<string name="start_verification">Start Verification</string>
|
||||
<!-- Keep the label as small as possible-->
|
||||
<string name="start_verification_short_label">Verify</string>
|
||||
<string name="share_without_verifying">Share without verifying</string>
|
||||
|
|
Loading…
Reference in a new issue