mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
qr scanner fragments merged into one
This commit is contained in:
parent
1efb6e162c
commit
b6eb27f8a1
20 changed files with 435 additions and 470 deletions
1
changelog.d/4873.misc
Normal file
1
changelog.d/4873.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Qr code scanning fragments merged into one
|
|
@ -60,6 +60,7 @@ import im.vector.app.features.login2.created.AccountCreatedViewModel
|
||||||
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
|
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
|
||||||
import im.vector.app.features.onboarding.OnboardingViewModel
|
import im.vector.app.features.onboarding.OnboardingViewModel
|
||||||
import im.vector.app.features.poll.create.CreatePollViewModel
|
import im.vector.app.features.poll.create.CreatePollViewModel
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerViewModel
|
||||||
import im.vector.app.features.rageshake.BugReportViewModel
|
import im.vector.app.features.rageshake.BugReportViewModel
|
||||||
import im.vector.app.features.reactions.EmojiSearchResultViewModel
|
import im.vector.app.features.reactions.EmojiSearchResultViewModel
|
||||||
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
||||||
|
@ -219,6 +220,11 @@ interface MavericksViewModelModule {
|
||||||
@MavericksViewModelKey(CreateDirectRoomViewModel::class)
|
@MavericksViewModelKey(CreateDirectRoomViewModel::class)
|
||||||
fun createDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun createDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(QrCodeScannerViewModel::class)
|
||||||
|
fun qrCodeViewModelFactory(factory: QrCodeScannerViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(RoomNotificationSettingsViewModel::class)
|
@MavericksViewModelKey(RoomNotificationSettingsViewModel::class)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.R
|
||||||
|
|
||||||
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)
|
||||||
|
@ -66,8 +67,12 @@ fun <T : Fragment> AppCompatActivity.replaceFragment(
|
||||||
fragmentClass: Class<T>,
|
fragmentClass: Class<T>,
|
||||||
params: Parcelable? = null,
|
params: Parcelable? = null,
|
||||||
tag: String? = null,
|
tag: String? = null,
|
||||||
allowStateLoss: Boolean = false) {
|
allowStateLoss: Boolean = false,
|
||||||
|
useCustomAnimation: Boolean = false) {
|
||||||
supportFragmentManager.commitTransaction(allowStateLoss) {
|
supportFragmentManager.commitTransaction(allowStateLoss) {
|
||||||
|
if (useCustomAnimation) {
|
||||||
|
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||||
|
}
|
||||||
replace(container.id, fragmentClass, params.toMvRxBundle(), tag)
|
replace(container.id, fragmentClass, params.toMvRxBundle(), tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.platform
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
|
||||||
|
data class VectorDummyViewState(
|
||||||
|
val isDummy: Unit = Unit
|
||||||
|
) : MavericksState
|
|
@ -17,7 +17,6 @@
|
||||||
package im.vector.app.features.analytics.accountdata
|
package im.vector.app.features.analytics.accountdata
|
||||||
|
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import com.airbnb.mvrx.MavericksState
|
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
@ -26,6 +25,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyAction
|
import im.vector.app.core.platform.EmptyAction
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorDummyViewState
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.analytics.VectorAnalytics
|
import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.analytics.log.analyticsTag
|
import im.vector.app.features.analytics.log.analyticsTag
|
||||||
|
@ -42,24 +42,20 @@ import org.matrix.android.sdk.flow.flow
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
data class DummyState(
|
|
||||||
val dummy: Boolean = false
|
|
||||||
) : MavericksState
|
|
||||||
|
|
||||||
class AnalyticsAccountDataViewModel @AssistedInject constructor(
|
class AnalyticsAccountDataViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DummyState,
|
@Assisted initialState: VectorDummyViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val analytics: VectorAnalytics
|
private val analytics: VectorAnalytics
|
||||||
) : VectorViewModel<DummyState, EmptyAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<VectorDummyViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
private var checkDone: Boolean = false
|
private var checkDone: Boolean = false
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<AnalyticsAccountDataViewModel, DummyState> {
|
interface Factory : MavericksAssistedViewModelFactory<AnalyticsAccountDataViewModel, VectorDummyViewState> {
|
||||||
override fun create(initialState: DummyState): AnalyticsAccountDataViewModel
|
override fun create(initialState: VectorDummyViewState): AnalyticsAccountDataViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<AnalyticsAccountDataViewModel, DummyState> by hiltMavericksViewModelFactory() {
|
companion object : MavericksViewModelFactory<AnalyticsAccountDataViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory() {
|
||||||
private const val ANALYTICS_EVENT_TYPE = "im.vector.analytics"
|
private const val ANALYTICS_EVENT_TYPE = "im.vector.analytics"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,8 @@ sealed class CreateDirectRoomAction : VectorViewModelAction {
|
||||||
data class CreateRoomAndInviteSelectedUsers(
|
data class CreateRoomAndInviteSelectedUsers(
|
||||||
val selections: Set<PendingSelection>
|
val selections: Set<PendingSelection>
|
||||||
) : CreateDirectRoomAction()
|
) : CreateDirectRoomAction()
|
||||||
|
|
||||||
|
data class QrScannedAction(
|
||||||
|
val result: String
|
||||||
|
) : CreateDirectRoomAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
|
@ -44,6 +45,10 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.features.analytics.plan.Screen
|
import im.vector.app.features.analytics.plan.Screen
|
||||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerEvents
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerViewModel
|
||||||
|
import im.vector.app.features.qrcode.QrScannerArgs
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerFragment
|
||||||
import im.vector.app.features.userdirectory.UserListFragment
|
import im.vector.app.features.userdirectory.UserListFragment
|
||||||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||||
|
@ -59,6 +64,8 @@ import javax.inject.Inject
|
||||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
||||||
|
private val qrViewModel: QrCodeScannerViewModel by viewModel()
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
@ -93,11 +100,37 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
viewModel.onEach(CreateDirectRoomViewState::createAndInviteState) {
|
viewModel.onEach(CreateDirectRoomViewState::createAndInviteState) {
|
||||||
renderCreateAndInviteState(it)
|
renderCreateAndInviteState(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
CreateDirectRoomViewEvents.InvalidCode -> {
|
||||||
|
Toast.makeText(this, R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
CreateDirectRoomViewEvents.DmSelf -> {
|
||||||
|
Toast.makeText(this, R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qrViewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is QrCodeScannerEvents.CodeParsed -> {
|
||||||
|
viewModel.handle(CreateDirectRoomAction.QrScannedAction(it.result))
|
||||||
|
}
|
||||||
|
is QrCodeScannerEvents.ParseFailed -> {
|
||||||
|
Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openAddByQrCode() {
|
private fun openAddByQrCode() {
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
|
||||||
addFragment(views.container, CreateDirectRoomByQrCodeFragment::class.java)
|
val args = QrScannerArgs(showExtraButtons = false, R.string.add_by_qr_code)
|
||||||
|
addFragment(views.container, QrCodeScannerFragment::class.java, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +151,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
if (allGranted) {
|
if (allGranted) {
|
||||||
addFragment(views.container, CreateDirectRoomByQrCodeFragment::class.java)
|
addFragment(views.container, QrCodeScannerFragment::class.java)
|
||||||
} else if (deniedPermanently) {
|
} else if (deniedPermanently) {
|
||||||
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 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.features.createdirect
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.airbnb.mvrx.activityViewModel
|
|
||||||
import com.google.zxing.Result
|
|
||||||
import com.google.zxing.ResultMetadataType
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
|
||||||
import im.vector.app.core.utils.checkPermissions
|
|
||||||
import im.vector.app.core.utils.onPermissionDeniedDialog
|
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
|
||||||
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
|
||||||
import im.vector.app.features.userdirectory.PendingSelection
|
|
||||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment<FragmentQrCodeScannerBinding>(), ZXingScannerView.ResultHandler {
|
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
|
|
||||||
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
|
||||||
if (allGranted) {
|
|
||||||
startCamera()
|
|
||||||
} else if (deniedPermanently) {
|
|
||||||
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCamera() {
|
|
||||||
// Start camera on resume
|
|
||||||
views.scannerView.startCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
setupToolbar(views.qrScannerToolbar)
|
|
||||||
.setTitle(R.string.add_by_qr_code)
|
|
||||||
.allowBack(useCross = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
view?.hideKeyboard()
|
|
||||||
// Register ourselves as a handler for scan results.
|
|
||||||
views.scannerView.setResultHandler(this)
|
|
||||||
// Start camera on resume
|
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
|
||||||
startCamera()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
// Unregister ourselves as a handler for scan results.
|
|
||||||
views.scannerView.setResultHandler(null)
|
|
||||||
// Stop camera on pause
|
|
||||||
views.scannerView.stopCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
|
||||||
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
|
||||||
private fun getRawBytes(result: Result): ByteArray? {
|
|
||||||
val metadata = result.resultMetadata ?: return null
|
|
||||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
|
||||||
var bytes = ByteArray(0)
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
for (seg in segments as Iterable<ByteArray>) {
|
|
||||||
bytes += seg
|
|
||||||
}
|
|
||||||
// byte segments can never be shorter than the text.
|
|
||||||
// Zxing cuts off content prefixes like "WIFI:"
|
|
||||||
return if (bytes.size >= result.text.length) bytes else null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addByQrCode(value: String) {
|
|
||||||
val mxid = (PermalinkParser.parse(value) as? PermalinkData.UserLink)?.userId
|
|
||||||
|
|
||||||
if (mxid === null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show()
|
|
||||||
requireActivity().finish()
|
|
||||||
} else {
|
|
||||||
// The following assumes MXIDs are case insensitive
|
|
||||||
if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
|
|
||||||
Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
|
|
||||||
requireActivity().finish()
|
|
||||||
} else {
|
|
||||||
// Try to get user from known users and fall back to creating a User object from MXID
|
|
||||||
val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
|
|
||||||
|
|
||||||
viewModel.handle(
|
|
||||||
CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingSelection.UserPendingSelection(qrInvitee)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleResult(result: Result?) {
|
|
||||||
if (result === null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
|
||||||
requireActivity().finish()
|
|
||||||
} else {
|
|
||||||
val rawBytes = getRawBytes(result)
|
|
||||||
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
|
||||||
val value = rawBytesStr ?: result.text
|
|
||||||
addByQrCode(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,6 +16,10 @@
|
||||||
|
|
||||||
package im.vector.app.features.createdirect
|
package im.vector.app.features.createdirect
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
sealed class CreateDirectRoomViewEvents : VectorViewEvents
|
sealed class CreateDirectRoomViewEvents : VectorViewEvents {
|
||||||
|
object InvalidCode: CreateDirectRoomViewEvents()
|
||||||
|
object DmSelf: CreateDirectRoomViewEvents()
|
||||||
|
}
|
||||||
|
|
|
@ -34,13 +34,16 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
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.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
|
|
||||||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: CreateDirectRoomViewState,
|
initialState: CreateDirectRoomViewState,
|
||||||
private val rawService: RawService,
|
private val rawService: RawService,
|
||||||
val session: Session) :
|
val session: Session) :
|
||||||
VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||||
|
@ -51,15 +54,33 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
override fun handle(action: CreateDirectRoomAction) {
|
override fun handle(action: CreateDirectRoomAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action)
|
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action.selections)
|
||||||
|
is CreateDirectRoomAction.QrScannedAction -> onCodeParsed(action)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onCodeParsed(action: CreateDirectRoomAction.QrScannedAction) {
|
||||||
|
val mxid = (PermalinkParser.parse(action.result) as? PermalinkData.UserLink)?.userId
|
||||||
|
|
||||||
|
if (mxid === null) {
|
||||||
|
_viewEvents.post(CreateDirectRoomViewEvents.InvalidCode)
|
||||||
|
} else {
|
||||||
|
// The following assumes MXIDs are case insensitive
|
||||||
|
if (mxid.equals(other = session.myUserId, ignoreCase = true)) {
|
||||||
|
_viewEvents.post(CreateDirectRoomViewEvents.DmSelf)
|
||||||
|
} else {
|
||||||
|
// Try to get user from known users and fall back to creating a User object from MXID
|
||||||
|
val qrInvitee = if (session.getUser(mxid) != null) session.getUser(mxid)!! else User(mxid, null, null)
|
||||||
|
onSubmitInvitees(setOf(PendingSelection.UserPendingSelection(qrInvitee)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If users already have a DM room then navigate to it instead of creating a new room.
|
* If users already have a DM room then navigate to it instead of creating a new room.
|
||||||
*/
|
*/
|
||||||
private fun onSubmitInvitees(action: CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers) {
|
private fun onSubmitInvitees(selections: Set<PendingSelection>) {
|
||||||
val existingRoomId = action.selections.singleOrNull()?.getMxId()?.let { userId ->
|
val existingRoomId = selections.singleOrNull()?.getMxId()?.let { userId ->
|
||||||
session.getExistingDirectRoomWithUser(userId)
|
session.getExistingDirectRoomWithUser(userId)
|
||||||
}
|
}
|
||||||
if (existingRoomId != null) {
|
if (existingRoomId != null) {
|
||||||
|
@ -69,7 +90,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create the DM
|
// Create the DM
|
||||||
createRoomAndInviteSelectedUsers(action.selections)
|
createRoomAndInviteSelectedUsers(selections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
@ -25,6 +24,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyAction
|
import im.vector.app.core.platform.EmptyAction
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorDummyViewState
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -37,22 +37,18 @@ import org.matrix.android.sdk.flow.flow
|
||||||
import org.matrix.android.sdk.flow.unwrap
|
import org.matrix.android.sdk.flow.unwrap
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
data class DummyState(
|
|
||||||
val dummy: Boolean = false
|
|
||||||
) : MavericksState
|
|
||||||
|
|
||||||
class UserColorAccountDataViewModel @AssistedInject constructor(
|
class UserColorAccountDataViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DummyState,
|
@Assisted initialState: VectorDummyViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val matrixItemColorProvider: MatrixItemColorProvider
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
) : VectorViewModel<DummyState, EmptyAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<VectorDummyViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<UserColorAccountDataViewModel, DummyState> {
|
interface Factory : MavericksAssistedViewModelFactory<UserColorAccountDataViewModel, VectorDummyViewState> {
|
||||||
override fun create(initialState: DummyState): UserColorAccountDataViewModel
|
override fun create(initialState: VectorDummyViewState): UserColorAccountDataViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<UserColorAccountDataViewModel, DummyState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<UserColorAccountDataViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeAccountData()
|
observeAccountData()
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.features.qrcode
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class QrCodeScannerAction : VectorViewModelAction {
|
||||||
|
data class CodeDecoded(
|
||||||
|
val result: String,
|
||||||
|
val isQrCode: Boolean
|
||||||
|
) : QrCodeScannerAction()
|
||||||
|
|
||||||
|
object ScanFailed : QrCodeScannerAction()
|
||||||
|
|
||||||
|
object SwitchMode : QrCodeScannerAction()
|
||||||
|
}
|
|
@ -19,56 +19,53 @@ package im.vector.app.features.qrcode
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.airbnb.mvrx.viewModel
|
||||||
import com.google.zxing.Result
|
|
||||||
import com.google.zxing.ResultMetadataType
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.replaceFragment
|
import im.vector.app.core.extensions.replaceFragment
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class QrCodeScannerActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
class QrCodeScannerActivity(): VectorBaseActivity<ActivitySimpleBinding>() {
|
||||||
|
|
||||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||||
|
|
||||||
|
private val qrViewModel: QrCodeScannerViewModel by viewModel()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
qrViewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is QrCodeScannerEvents.CodeParsed -> {
|
||||||
|
setResultAndFinish(it.result, it.isQrCode)
|
||||||
|
}
|
||||||
|
is QrCodeScannerEvents.ParseFailed -> {
|
||||||
|
Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
replaceFragment(views.simpleFragmentContainer, QrCodeScannerFragment::class.java)
|
val args = QrScannerArgs(showExtraButtons = false, R.string.verification_scan_their_code)
|
||||||
|
replaceFragment(views.simpleFragmentContainer, QrCodeScannerFragment::class.java, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setResultAndFinish(result: Result?) {
|
private fun setResultAndFinish(result: String, isQrCode: Boolean) {
|
||||||
if (result != null) {
|
setResult(RESULT_OK, Intent().apply {
|
||||||
val rawBytes = getRawBytes(result)
|
putExtra(EXTRA_OUT_TEXT, result)
|
||||||
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
putExtra(EXTRA_OUT_IS_QR_CODE, isQrCode)
|
||||||
|
})
|
||||||
setResult(RESULT_OK, Intent().apply {
|
|
||||||
putExtra(EXTRA_OUT_TEXT, rawBytesStr ?: result.text)
|
|
||||||
putExtra(EXTRA_OUT_IS_QR_CODE, result.barcodeFormat == BarcodeFormat.QR_CODE)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
|
||||||
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
|
||||||
private fun getRawBytes(result: Result): ByteArray? {
|
|
||||||
val metadata = result.resultMetadata ?: return null
|
|
||||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
|
||||||
var bytes = ByteArray(0)
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
for (seg in segments as Iterable<ByteArray>) {
|
|
||||||
bytes += seg
|
|
||||||
}
|
|
||||||
// byte segments can never be shorter than the text.
|
|
||||||
// Zxing cuts off content prefixes like "WIFI:"
|
|
||||||
return if (bytes.size >= result.text.length) bytes else null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
|
private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.features.qrcode
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class QrCodeScannerEvents : VectorViewEvents {
|
||||||
|
data class CodeParsed(val result: String, val isQrCode: Boolean): QrCodeScannerEvents()
|
||||||
|
object ParseFailed: QrCodeScannerEvents()
|
||||||
|
object SwitchMode: QrCodeScannerEvents()
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,50 +16,157 @@
|
||||||
|
|
||||||
package im.vector.app.features.qrcode
|
package im.vector.app.features.qrcode
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.google.zxing.Result
|
import com.google.zxing.Result
|
||||||
|
import com.google.zxing.ResultMetadataType
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
|
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.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.app.core.utils.checkPermissions
|
||||||
|
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||||
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
||||||
|
import im.vector.app.features.usercode.QRCodeBitmapDecodeHelper
|
||||||
|
import im.vector.lib.multipicker.MultiPicker
|
||||||
|
import im.vector.lib.multipicker.utils.ImageUtils
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class QrCodeScannerFragment @Inject constructor() :
|
@Parcelize
|
||||||
VectorBaseFragment<FragmentQrCodeScannerBinding>(),
|
data class QrScannerArgs(
|
||||||
ZXingScannerView.ResultHandler {
|
val showExtraButtons: Boolean,
|
||||||
|
@StringRes val titleRes: Int
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
open class QrCodeScannerFragment @Inject constructor(): VectorBaseFragment<FragmentQrCodeScannerBinding>(), ZXingScannerView.ResultHandler {
|
||||||
|
|
||||||
|
private val qrViewModel: QrCodeScannerViewModel by activityViewModel()
|
||||||
|
private val scannerArgs: QrScannerArgs? by args()
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
|
||||||
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
|
if (allGranted) {
|
||||||
|
startCamera()
|
||||||
|
} else if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
MultiPicker
|
||||||
|
.get(MultiPicker.IMAGE)
|
||||||
|
.getSelectedFiles(requireActivity(), activityResult.data)
|
||||||
|
.firstOrNull()
|
||||||
|
?.contentUri
|
||||||
|
?.let { uri ->
|
||||||
|
// try to see if it is a valid matrix code
|
||||||
|
val bitmap = ImageUtils.getBitmap(requireContext(), uri)
|
||||||
|
?: return@let Unit.also {
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var autoFocus = true
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val title = scannerArgs?.titleRes?.let { getString(it) }
|
||||||
|
|
||||||
setupToolbar(views.qrScannerToolbar)
|
setupToolbar(views.qrScannerToolbar)
|
||||||
.setTitle(R.string.verification_scan_their_code)
|
.setTitle(title)
|
||||||
.allowBack(useCross = true)
|
.allowBack(useCross = true)
|
||||||
|
|
||||||
|
scannerArgs?.showExtraButtons?.let { showButtons ->
|
||||||
|
views.userCodeMyCodeButton.isVisible = showButtons
|
||||||
|
views.userCodeOpenGalleryButton.isVisible = showButtons
|
||||||
|
|
||||||
|
if (showButtons) {
|
||||||
|
views.userCodeOpenGalleryButton.debouncedClicks {
|
||||||
|
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
|
||||||
|
}
|
||||||
|
views.userCodeMyCodeButton.debouncedClicks {
|
||||||
|
qrViewModel.handle(QrCodeScannerAction.SwitchMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCamera() {
|
||||||
|
with(views.qrScannerView) {
|
||||||
|
startCamera()
|
||||||
|
setAutoFocus(autoFocus)
|
||||||
|
debouncedClicks {
|
||||||
|
autoFocus = !autoFocus
|
||||||
|
setAutoFocus(autoFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
view?.hideKeyboard()
|
||||||
|
|
||||||
// Register ourselves as a handler for scan results.
|
// Register ourselves as a handler for scan results.
|
||||||
views.scannerView.setResultHandler(this)
|
views.qrScannerView.setResultHandler(this)
|
||||||
// Start camera on resume
|
|
||||||
views.scannerView.startCamera()
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
// Stop camera on pause
|
views.qrScannerView.setResultHandler(null)
|
||||||
views.scannerView.stopCamera()
|
views.qrScannerView.stopCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
||||||
|
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
||||||
|
private fun getRawBytes(result: Result): ByteArray? {
|
||||||
|
val metadata = result.resultMetadata ?: return null
|
||||||
|
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||||
|
var bytes = ByteArray(0)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
for (seg in segments as Iterable<ByteArray>) {
|
||||||
|
bytes += seg
|
||||||
|
}
|
||||||
|
// byte segments can never be shorter than the text.
|
||||||
|
// Zxing cuts off content prefixes like "WIFI:"
|
||||||
|
return if (bytes.size >= result.text.length) bytes else null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleResult(rawResult: Result?) {
|
override fun handleResult(rawResult: Result?) {
|
||||||
// Do something with the result here
|
if (rawResult == null) {
|
||||||
// This is not intended to be used outside of QrCodeScannerActivity for the moment
|
qrViewModel.handle(QrCodeScannerAction.ScanFailed)
|
||||||
(requireActivity() as? QrCodeScannerActivity)?.setResultAndFinish(rawResult)
|
} else {
|
||||||
|
val rawBytes = getRawBytes(rawResult)
|
||||||
|
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
||||||
|
val result = rawBytesStr ?: rawResult.text
|
||||||
|
val isQrCode = rawResult.barcodeFormat == BarcodeFormat.QR_CODE
|
||||||
|
qrViewModel.handle(QrCodeScannerAction.CodeDecoded(result, isQrCode))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.features.qrcode
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.VectorDummyViewState
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
|
class QrCodeScannerViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: VectorDummyViewState,
|
||||||
|
val session: Session
|
||||||
|
) : VectorViewModel<VectorDummyViewState, QrCodeScannerAction, QrCodeScannerEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<QrCodeScannerViewModel, VectorDummyViewState> {
|
||||||
|
override fun create(initialState: VectorDummyViewState): QrCodeScannerViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<QrCodeScannerViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
override fun handle(action: QrCodeScannerAction) {
|
||||||
|
when (action) {
|
||||||
|
is QrCodeScannerAction.CodeDecoded -> _viewEvents.post(QrCodeScannerEvents.CodeParsed(action.result, action.isQrCode))
|
||||||
|
is QrCodeScannerAction.SwitchMode -> _viewEvents.post(QrCodeScannerEvents.SwitchMode)
|
||||||
|
is QrCodeScannerAction.ScanFailed -> _viewEvents.post(QrCodeScannerEvents.ParseFailed)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.features.usercode
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.airbnb.mvrx.activityViewModel
|
|
||||||
import com.google.zxing.Result
|
|
||||||
import com.google.zxing.ResultMetadataType
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
|
||||||
import im.vector.app.core.utils.checkPermissions
|
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
|
||||||
import im.vector.app.databinding.FragmentQrCodeScannerWithButtonBinding
|
|
||||||
import im.vector.lib.multipicker.MultiPicker
|
|
||||||
import im.vector.lib.multipicker.utils.ImageUtils
|
|
||||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ScanUserCodeFragment @Inject constructor() :
|
|
||||||
VectorBaseFragment<FragmentQrCodeScannerWithButtonBinding>(),
|
|
||||||
ZXingScannerView.ResultHandler {
|
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerWithButtonBinding {
|
|
||||||
return FragmentQrCodeScannerWithButtonBinding.inflate(inflater, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
|
|
||||||
|
|
||||||
var autoFocus = true
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
setupToolbar(views.qrScannerToolbar)
|
|
||||||
.allowBack(useCross = true)
|
|
||||||
|
|
||||||
views.userCodeMyCodeButton.debouncedClicks {
|
|
||||||
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
|
||||||
}
|
|
||||||
|
|
||||||
views.userCodeOpenGalleryButton.debouncedClicks {
|
|
||||||
MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
|
|
||||||
if (allGranted) {
|
|
||||||
startCamera()
|
|
||||||
} else {
|
|
||||||
// For now just go back
|
|
||||||
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
|
||||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
|
||||||
MultiPicker
|
|
||||||
.get(MultiPicker.IMAGE)
|
|
||||||
.getSelectedFiles(requireActivity(), activityResult.data)
|
|
||||||
.firstOrNull()
|
|
||||||
?.contentUri
|
|
||||||
?.let { uri ->
|
|
||||||
// try to see if it is a valid matrix code
|
|
||||||
val bitmap = ImageUtils.getBitmap(requireContext(), uri)
|
|
||||||
?: return@let Unit.also {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCamera() {
|
|
||||||
views.userCodeScannerView.startCamera()
|
|
||||||
views.userCodeScannerView.setAutoFocus(autoFocus)
|
|
||||||
views.userCodeScannerView.debouncedClicks {
|
|
||||||
this.autoFocus = !autoFocus
|
|
||||||
views.userCodeScannerView.setAutoFocus(autoFocus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
|
||||||
startCamera()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
// Register ourselves as a handler for scan results.
|
|
||||||
views.userCodeScannerView.setResultHandler(this)
|
|
||||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
startCamera()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
views.userCodeScannerView.setResultHandler(null)
|
|
||||||
// Stop camera on pause
|
|
||||||
views.userCodeScannerView.stopCamera()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleResult(result: Result?) {
|
|
||||||
if (result === null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
|
||||||
requireActivity().finish()
|
|
||||||
} else {
|
|
||||||
val rawBytes = getRawBytes(result)
|
|
||||||
val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
|
|
||||||
val value = rawBytesStr ?: result.text
|
|
||||||
sharedViewModel.handle(UserCodeActions.DecodedQRCode(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from https://github.com/markusfisch/BinaryEye/blob/
|
|
||||||
// 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
|
|
||||||
private fun getRawBytes(result: Result): ByteArray? {
|
|
||||||
val metadata = result.resultMetadata ?: return null
|
|
||||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
|
||||||
var bytes = ByteArray(0)
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
for (seg in segments as Iterable<ByteArray>) {
|
|
||||||
bytes += seg
|
|
||||||
}
|
|
||||||
// byte segments can never be shorter than the text.
|
|
||||||
// Zxing cuts off content prefixes like "WIFI:"
|
|
||||||
return if (bytes.size >= result.text.length) bytes else null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,12 +30,16 @@ import com.airbnb.mvrx.viewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
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.extensions.replaceFragment
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerEvents
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerFragment
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerViewModel
|
||||||
|
import im.vector.app.features.qrcode.QrScannerArgs
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@ -44,6 +48,7 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
MatrixToBottomSheet.InteractionListener {
|
MatrixToBottomSheet.InteractionListener {
|
||||||
|
|
||||||
val sharedViewModel: UserCodeSharedViewModel by viewModel()
|
val sharedViewModel: UserCodeSharedViewModel by viewModel()
|
||||||
|
private val qrViewModel: QrCodeScannerViewModel by viewModel()
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Args(
|
data class Args(
|
||||||
|
@ -81,10 +86,13 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
|
|
||||||
sharedViewModel.onEach(UserCodeState::mode) { mode ->
|
sharedViewModel.onEach(UserCodeState::mode) { mode ->
|
||||||
when (mode) {
|
when (mode) {
|
||||||
UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
|
UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class)
|
||||||
UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY)
|
UserCodeState.Mode.SCAN -> {
|
||||||
|
val args = QrScannerArgs(showExtraButtons = true, R.string.user_code_scan)
|
||||||
|
showFragment(QrCodeScannerFragment::class, args)
|
||||||
|
}
|
||||||
is UserCodeState.Mode.RESULT -> {
|
is UserCodeState.Mode.RESULT -> {
|
||||||
showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
|
showFragment(ShowUserCodeFragment::class)
|
||||||
MatrixToBottomSheet.withLink(mode.rawLink).show(supportFragmentManager, "MatrixToBottomSheet")
|
MatrixToBottomSheet.withLink(mode.rawLink).show(supportFragmentManager, "MatrixToBottomSheet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +114,21 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qrViewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is QrCodeScannerEvents.CodeParsed -> {
|
||||||
|
sharedViewModel.handle(UserCodeActions.DecodedQRCode(it.result))
|
||||||
|
}
|
||||||
|
QrCodeScannerEvents.SwitchMode -> {
|
||||||
|
sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
|
||||||
|
}
|
||||||
|
is QrCodeScannerEvents.ParseFailed -> {
|
||||||
|
Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -113,16 +136,9 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
|
private fun showFragment(fragmentClass: KClass<out Fragment>, params: Parcelable? = null) {
|
||||||
if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
|
if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
|
||||||
supportFragmentManager.commitTransaction {
|
replaceFragment(views.simpleFragmentContainer, fragmentClass.java, params, fragmentClass.simpleName, useCustomAnimation = true)
|
||||||
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
|
||||||
replace(views.simpleFragmentContainer.id,
|
|
||||||
fragmentClass.java,
|
|
||||||
bundle,
|
|
||||||
fragmentClass.simpleName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
android:id="@+id/scannerView"
|
android:id="@+id/qrScannerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
@ -29,30 +29,42 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
||||||
|
|
||||||
<!-- TODO In the future we could add a toggle to switch the flash, and other possible settings -->
|
<Button
|
||||||
|
android:id="@+id/userCodeMyCodeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
||||||
|
android:maxWidth="160dp"
|
||||||
|
android:text="@string/user_code_my_code"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<!-- TODO add take from album option.. -->
|
<Button
|
||||||
<!-- <Button-->
|
android:id="@+id/userCodeOpenGalleryButton"
|
||||||
<!-- android:id="@+id/openAlbumButton"-->
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
<!-- style="@style/Widget.MaterialComponents.Button.Icon"-->
|
android:layout_width="34dp"
|
||||||
<!-- android:layout_width="34dp"-->
|
android:layout_height="34dp"
|
||||||
<!-- android:layout_height="34dp"-->
|
android:layout_marginTop="2dp"
|
||||||
<!-- android:layout_marginEnd="@dimen/layout_horizontal_margin"-->
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
<!-- android:layout_marginBottom="@dimen/layout_vertical_margin_big"-->
|
android:backgroundTint="?colorSurface"
|
||||||
<!-- android:backgroundTint="?vctr_bottom_nav_background_color"-->
|
android:elevation="0dp"
|
||||||
<!-- android:elevation="0dp"-->
|
android:insetLeft="0dp"
|
||||||
<!-- android:insetLeft="0dp"-->
|
android:insetTop="0dp"
|
||||||
<!-- android:insetTop="0dp"-->
|
android:insetRight="0dp"
|
||||||
<!-- android:insetRight="0dp"-->
|
android:insetBottom="0dp"
|
||||||
<!-- android:insetBottom="0dp"-->
|
android:padding="0dp"
|
||||||
<!-- android:padding="0dp"-->
|
android:visibility="gone"
|
||||||
<!-- app:cornerRadius="17dp"-->
|
app:cornerRadius="17dp"
|
||||||
<!-- app:icon="@drawable/ic_picture_icon"-->
|
app:icon="@drawable/ic_picture_icon"
|
||||||
<!-- app:iconGravity="textStart"-->
|
app:iconGravity="textStart"
|
||||||
<!-- app:iconPadding="0dp"-->
|
app:iconPadding="0dp"
|
||||||
<!-- app:iconSize="20dp"-->
|
app:iconSize="20dp"
|
||||||
<!-- app:iconTint="?colorPrimary"-->
|
app:iconTint="?colorPrimary"
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
app:layout_constraintBottom_toBottomOf="@id/userCodeMyCodeButton"
|
||||||
<!-- app:layout_constraintEnd_toEndOf="parent"/>-->
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/userCodeMyCodeButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/userCodeMyCodeButton" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,67 +0,0 @@
|
||||||
<?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"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/appBarLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/qrScannerToolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?actionBarSize"
|
|
||||||
app:title="@string/user_code_scan" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
|
||||||
android:id="@+id/userCodeScannerView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/userCodeMyCodeButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
|
|
||||||
android:maxWidth="160dp"
|
|
||||||
android:text="@string/user_code_my_code"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/userCodeOpenGalleryButton"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
|
||||||
android:layout_width="34dp"
|
|
||||||
android:layout_height="34dp"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
|
||||||
android:backgroundTint="?colorSurface"
|
|
||||||
android:elevation="0dp"
|
|
||||||
android:insetLeft="0dp"
|
|
||||||
android:insetTop="0dp"
|
|
||||||
android:insetRight="0dp"
|
|
||||||
android:insetBottom="0dp"
|
|
||||||
android:padding="0dp"
|
|
||||||
app:cornerRadius="17dp"
|
|
||||||
app:icon="@drawable/ic_picture_icon"
|
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:iconPadding="0dp"
|
|
||||||
app:iconSize="20dp"
|
|
||||||
app:iconTint="?colorPrimary"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/userCodeMyCodeButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/userCodeMyCodeButton"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/userCodeMyCodeButton" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
Loading…
Reference in a new issue