diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cacdeb494a..6944d456dc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -96,6 +96,8 @@ dependencies { implementation(projects.features.manageTokens.api) implementation(projects.features.manageTokens.impl) implementation(projects.features.send.impl) + implementation(projects.features.qrScanning.api) + implementation(projects.features.qrScanning.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) @@ -143,6 +145,7 @@ dependencies { /** DI */ implementation(deps.hilt.android) + kapt(deps.hilt.kapt) /** Other libraries */ @@ -155,7 +158,6 @@ dependencies { implementation(deps.timber) implementation(deps.reKotlin) implementation(deps.zxing.qrCore) - implementation(deps.otaliastudiosCameraView) implementation(deps.coil) implementation(deps.appsflyer) implementation(deps.amplitude) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c0cbec6b1b..32e44932c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -137,10 +137,6 @@ - - @@ -206,6 +210,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac manageTokensRouter = manageTokensRouter, cardSdkConfigRepository = cardSdkConfigRepository, sendRouter = sendRouter, + qrScanningRouter = qrScanningRouter, ), ) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index f9f50d8d05..df5762ec97 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -14,7 +14,6 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRe import com.tangem.tap.features.details.ui.details.DetailsFragment import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment -import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment @@ -171,7 +170,11 @@ private fun fragmentFactory(screen: AppScreen): Fragment { .getEntryFragment() } AppScreen.WalletConnectSessions -> WalletConnectFragment() - AppScreen.QrScan -> QrScanFragment() + AppScreen.QrScanning -> { + store.state.daggerGraphState + .get(getDependency = DaggerGraphState::qrScanningRouter) + .getEntryFragment() + } AppScreen.ReferralProgram -> ReferralFragment() AppScreen.Swap -> SwapFragment() AppScreen.Welcome -> WelcomeFragment() diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt deleted file mode 100644 index c568b3a7c3..0000000000 --- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.tangem.tap.common.qrCodeScan - -import android.Manifest -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import by.kirich1409.viewbindingdelegate.viewBinding -import com.google.common.util.concurrent.ListenableFuture -import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE -import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder -import com.tangem.wallet.R -import com.tangem.wallet.databinding.LayoutQrScanningBinding -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - -/** -[REDACTED_AUTHOR] - */ -class ScanQrCodeActivity : AppCompatActivity() { - - private val binding: LayoutQrScanningBinding by viewBinding(LayoutQrScanningBinding::bind) - - private var cameraProviderFuture: ListenableFuture? = null - private var cameraExecutor: ExecutorService? = null - - private val binder = PreviewBinder() - - override fun onCreate(state: Bundle?) { - super.onCreate(state) - - if (!permissionIsGranted()) requestPermission() - - setContentView(R.layout.layout_qr_scanning) - - cameraProviderFuture = ProcessCameraProvider.getInstance(this) - cameraExecutor = Executors.newSingleThreadExecutor() - - cameraProviderFuture?.addListener( - { - val cameraProvider = cameraProviderFuture?.get() - binder.bindPreview( - context = this, - binding = binding, - lifecycleOwner = this, - cameraProvider = requireNotNull(cameraProvider), - cameraExecutor = requireNotNull(cameraExecutor), - onScanned = { result -> - setResult(SCAN_QR_REQUEST_CODE, Intent().apply { putExtra(SCAN_RESULT, result) }) - finish() - }, - ) - }, - ContextCompat.getMainExecutor(this), - ) - - binding.overlay.post { - binding.overlay.setViewFinder() - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode != PERMISSION_REQUEST_CODE) return - - if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - finish() - } - } - - private fun permissionIsGranted(): Boolean { - val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) - return cameraPermission == PackageManager.PERMISSION_GRANTED - } - - private fun requestPermission() { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) - } - - companion object { - const val SCAN_QR_REQUEST_CODE = 1001 - const val SCAN_RESULT = "scanResult" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/QrScanningDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/QrScanningDomainModule.kt new file mode 100644 index 0000000000..ba2c69965b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/QrScanningDomainModule.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.di.domain + +import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository +import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object QrScanningDomainModule { + + @Provides + @Singleton + fun provideListenToQrScanUseCase(repository: QrScanningEventsRepository): ListenToQrScanningUseCase { + return ListenToQrScanningUseCase(repository) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 24c41042ba..60c2e73559 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect +import androidx.core.os.bundleOf import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.derivation.DerivationStyle @@ -16,6 +17,8 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.feature.qrscanning.SourceType import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -98,7 +101,14 @@ class WalletConnectMiddleware { if (uri != null && isWalletConnectUri(uri)) { store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) } else { - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan)) + store.dispatchOnMain( + NavigationAction.NavigateTo( + screen = AppScreen.QrScanning, + bundle = bundleOf( + QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT, + ), + ), + ) } } is WalletConnectAction.SelectNetwork -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 6100201d98..1733d8d946 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.screen.ComposeFragment @@ -23,22 +24,25 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber = - mutableStateOf(viewModel.updateState(store.state.walletConnectState)) + private val viewModel: WalletConnectViewModel by viewModels() + + private var screenState: MutableState? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Analytics.send(WalletConnect.ScreenOpened()) + lifecycle.addObserver(viewModel) + screenState = mutableStateOf(viewModel.updateState(store.state.walletConnectState)) } @Composable override fun ScreenContent(modifier: Modifier) { + val state = screenState?.value ?: return WalletConnectScreen( modifier = modifier, - state = screenState.value, + state = state, onBackClick = { - if (screenState.value.isLoading) { + if (state.isLoading) { store.dispatch( WalletConnectAction.FailureEstablishingSession( store.state.walletConnectState.newSessionData?.session?.session, @@ -66,6 +70,6 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber) { fun updateState(state: WalletConnectState): WalletConnectScreenState { Timber.d("WC2 Sessions: ${state.wc2Sessions}") val sessions = state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 301435433b..d3f92e4286 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -2,9 +2,12 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.feature.qrscanning.SourceType import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.store @@ -19,7 +22,14 @@ object ClipboardOrScanQrDialog { store.dispatch(WalletConnectAction.OpenSession(wcUri)) } setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan)) + store.dispatch( + NavigationAction.NavigateTo( + screen = AppScreen.QrScanning, + bundle = bundleOf( + QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT, + ), + ), + ) } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index e491b1c0e5..3e8b23a5d3 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -3,30 +3,38 @@ package com.tangem.tap.features.send.ui import android.content.Context -import android.content.Intent import android.os.Bundle import android.text.method.DigitsKeyListener import android.view.View import android.view.inputmethod.EditorInfo import android.widget.EditText +import androidx.core.os.bundleOf import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.textfield.TextInputEditText import com.tangem.Message import com.tangem.core.analytics.Analytics +import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.feature.qrscanning.SourceType +import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase import com.tangem.sdk.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.analytics.events.Token +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.extensions.setOnImeActionListener -import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.snackBar.MaxAmountSnackbar import com.tangem.tap.common.text.truncateMiddleWith @@ -49,8 +57,11 @@ import com.tangem.wallet.databinding.FragmentSendBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import java.text.DecimalFormatSymbols +import javax.inject.Inject private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L @@ -73,11 +84,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind) + @Inject + lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(viewModel) sendSubscriber.initViewModel(viewModel) Analytics.send(Token.Send.ScreenOpened()) + listenToQrCode() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -145,13 +160,34 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } imvQrCode.setOnClickListener { Analytics.send(Token.Send.ButtonQRCode()) - startActivityForResult( - Intent(requireContext(), ScanQrCodeActivity::class.java), - ScanQrCodeActivity.SCAN_QR_REQUEST_CODE, + + store.dispatchOnMain( + NavigationAction.NavigateTo( + screen = AppScreen.QrScanning, + bundle = bundleOf( + QrScanningRouter.SOURCE_KEY to SourceType.SEND, + ), + ), ) } } + private fun listenToQrCode() { + lifecycleScope.launch { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED) + .collect { + delay(200) + + // Delayed launch is needed in order for the UI to be drawn and to process the sent events. + // If do not use the delay, then etAmount error field is not displayed when + // inserting an incorrect amount by shareUri + onCodeScanned(it) + } + } + } + private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) { // TODO: [REDACTED_TASK_KEY] etXlmMemo.inputtedTextAsFlow() @@ -200,27 +236,16 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .launchIn(mainScope) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return - - val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: "" + private fun onCodeScanned(scannedCode: String) { if (scannedCode.isEmpty()) return - // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then etAmount error field is not displayed when - // inserting an incorrect amount by shareUri - binding.lSendAddress.imvQrCode.postDelayed( - { - store.dispatch( - PasteAddress( - data = scannedCode, - sourceType = Token.Send.AddressEntered.SourceType.QRCode, - ), - ) - store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused)) - }, - 200, + store.dispatch( + PasteAddress( + data = scannedCode, + sourceType = Token.Send.AddressEntered.SourceType.QRCode, + ), ) + store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused)) } private fun setupAmountLayout() { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 3392c82127..9dab1b838d 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,5 +1,6 @@ package com.tangem.tap.proxy.redux +import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.features.managetokens.navigation.ManageTokensRouter @@ -21,5 +22,6 @@ sealed interface DaggerGraphAction : Action { val manageTokensRouter: ManageTokensRouter, val cardSdkConfigRepository: CardSdkConfigRepository, val sendRouter: SendRouter, + val qrScanningRouter: QrScanningRouter, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index df08899bf0..5f2030f390 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -21,6 +21,7 @@ object DaggerGraphReducer { manageTokensRouter = action.manageTokensRouter, cardSdkConfigRepository = action.cardSdkConfigRepository, sendRouter = action.sendRouter, + qrScanningRouter = action.qrScanningRouter, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 540e3351e6..6bd651f70c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -1,5 +1,6 @@ package com.tangem.tap.proxy.redux +import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -50,6 +51,7 @@ data class DaggerGraphState( val networksRepository: NetworksRepository? = null, val sendFeatureToggles: SendFeatureToggles? = null, val sendRouter: SendRouter? = null, + val qrScanningRouter: QrScanningRouter? = null, // FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList val currenciesRepository: CurrenciesRepository? = null, diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 6e6b5d9c56..09fa3bdc17 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -36,7 +36,5 @@ 16dp 38dp - 4dp - 8dp diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt index 63bd6084ce..7ca534e15c 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt @@ -29,7 +29,7 @@ enum class AppScreen(val isDialogFragment: Boolean = false) { ManageTokens, AddCustomToken, WalletConnectSessions, - QrScan, + QrScanning, ReferralProgram, Swap, Welcome, diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt index 7feba2b807..7dd58d2a5b 100644 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt @@ -3,5 +3,6 @@ package com.tangem.features.managetokens.navigation import androidx.fragment.app.Fragment interface ManageTokensRouter { + fun getEntryFragment(): Fragment } \ No newline at end of file diff --git a/features/qr-scanning/api/.gitignore b/features/qr-scanning/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/qr-scanning/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/qr-scanning/api/build.gradle.kts b/features/qr-scanning/api/build.gradle.kts new file mode 100644 index 0000000000..d88f0c6f03 --- /dev/null +++ b/features/qr-scanning/api/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.feature.qrscanning.api" +} + +dependencies { + + /** AndroidX */ + implementation(deps.androidx.fragment.ktx) +} \ No newline at end of file diff --git a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt new file mode 100644 index 0000000000..ce7ea43935 --- /dev/null +++ b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt @@ -0,0 +1,12 @@ +package com.tangem.feature.qrscanning + +import androidx.fragment.app.Fragment + +interface QrScanningRouter { + + fun getEntryFragment(): Fragment + + companion object { + const val SOURCE_KEY = "source" + } +} \ No newline at end of file diff --git a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/SourceType.kt b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/SourceType.kt new file mode 100644 index 0000000000..96640d74e6 --- /dev/null +++ b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/SourceType.kt @@ -0,0 +1,6 @@ +package com.tangem.feature.qrscanning + +enum class SourceType { + WALLET_CONNECT, + SEND, +} \ No newline at end of file diff --git a/features/qr-scanning/impl/.gitignore b/features/qr-scanning/impl/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/qr-scanning/impl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/qr-scanning/impl/build.gradle.kts b/features/qr-scanning/impl/build.gradle.kts new file mode 100644 index 0000000000..22fd460d7f --- /dev/null +++ b/features/qr-scanning/impl/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.feature.qrscanning.impl" + + viewBinding { + enable = true + } +} + + +dependencies { + + implementation(deps.androidx.fragment.ktx) + implementation(deps.viewBindingDelegate) + + /** Camera */ + implementation(deps.camera.camera2) + implementation(deps.camera.lifecycle) + implementation(deps.camera.view) + + implementation(deps.listenableFuture) + implementation(deps.mlKit.barcodeScanning) + + /** Excluded dependencies */ + implementation("com.google.guava:guava:30.0-android") { + // excludes version 9999.0-empty-to-avoid-conflict-with-guava + exclude(group="com.google.guava", module = "listenablefuture") + } + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + + implementation(projects.features.qrScanning.api) + + /** Compose */ + implementation(deps.compose.foundation) + + implementation(deps.arrow.core) +} \ No newline at end of file diff --git a/features/qr-scanning/impl/proguard-rules.pro b/features/qr-scanning/impl/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/features/qr-scanning/impl/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt new file mode 100644 index 0000000000..e2e475f8e9 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt @@ -0,0 +1,5 @@ +package com.tangem.feature.qrscanning + +const val SCAN_QR_REQUEST_CODE = 1001 +const val SCAN_RESULT = "scanResult" +const val PERMISSION_REQUEST_CODE = 16 \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt new file mode 100644 index 0000000000..0e53af0a78 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.qrscanning + +import androidx.fragment.app.Fragment + +internal class DefaultQrScanningRouter : QrScanningRouter { + + override fun getEntryFragment(): Fragment = QrScanningFragment.create() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt similarity index 70% rename from app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 4ba97f4d51..8db65f98c2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -1,46 +1,42 @@ -package com.tangem.tap.features.details.ui.walletconnect +package com.tangem.feature.qrscanning import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.view.* -import androidx.activity.OnBackPressedCallback import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import by.kirich1409.viewbindingdelegate.viewBinding import com.google.common.util.concurrent.ListenableFuture -import com.otaliastudios.cameraview.CameraView -import com.tangem.core.navigation.NavigationAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.LayoutQrScanningBinding +import com.tangem.feature.qrscanning.impl.R +import com.tangem.feature.qrscanning.impl.databinding.LayoutQrScanningBinding +import com.tangem.feature.qrscanning.inner.PreviewBinder +import dagger.hilt.android.AndroidEntryPoint +// import com.tangem.core.navigation.NavigationAction +// import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +// import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder +// import com.tangem.tap.store import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) { +@AndroidEntryPoint +internal class QrScanningFragment : Fragment(R.layout.layout_qr_scanning) { private val binding: LayoutQrScanningBinding by viewBinding(LayoutQrScanningBinding::bind) private val binder = PreviewBinder() + private val viewModel by viewModels() + private var cameraProviderFuture: ListenableFuture? = null private var cameraExecutor: ExecutorService? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setFitSystemWindows(fit = true) - activity?.onBackPressedDispatcher?.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }, - ) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -61,10 +57,10 @@ internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) { cameraProvider = requireNotNull(cameraProvider), cameraExecutor = requireNotNull(cameraExecutor), onScanned = { result -> - store.dispatch(NavigationAction.PopBackTo()) + parentFragmentManager.popBackStack() setFitSystemWindows(fit = false) if (result.isNotBlank()) { - store.dispatch(WalletConnectAction.OpenSession(result)) + viewModel.onQrScanned(result) } }, ) @@ -83,11 +79,10 @@ internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) { } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (requestCode != CameraView.PERMISSION_REQUEST_CODE) return + if (requestCode != PERMISSION_REQUEST_CODE) return if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - store.dispatch(WalletConnectAction.NotifyCameraPermissionIsRequired) - store.dispatch(NavigationAction.PopBackTo()) + parentFragmentManager.popBackStack() } } @@ -97,7 +92,7 @@ internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) { } private fun requestPermission() { - requestPermissions(arrayOf(Manifest.permission.CAMERA), CameraView.PERMISSION_REQUEST_CODE) + requestPermissions(arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) } private fun setFitSystemWindows(fit: Boolean) { @@ -105,4 +100,9 @@ internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) { WindowCompat.setDecorFitsSystemWindows(it, fit) } } + + companion object { + + fun create() = QrScanningFragment() + } } \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt new file mode 100644 index 0000000000..7cd0a6cde4 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt @@ -0,0 +1,27 @@ +package com.tangem.feature.qrscanning + +import com.tangem.feature.qrscanning.repo.DefaultQrScanningEventsRepository +import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository +import com.tangem.feature.qrscanning.usecase.EmitQrScannedEventUseCase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object QrScanningModule { + + @Provides + @Singleton + fun provideQrScannedEventsRepository(): QrScanningEventsRepository { + return DefaultQrScanningEventsRepository() + } + + @Provides + @Singleton + fun provideEmitQrScannedEventUseCase(repository: QrScanningEventsRepository): EmitQrScannedEventUseCase { + return EmitQrScannedEventUseCase(repository) + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt new file mode 100644 index 0000000000..688c8f2cf9 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.qrscanning + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +internal object QrScanningRouterModule { + + @Provides + @ActivityScoped + fun provideQrScanRouter(): QrScanningRouter { + return DefaultQrScanningRouter() + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt new file mode 100644 index 0000000000..b950318128 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.qrscanning + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.feature.qrscanning.QrScanningRouter.Companion.SOURCE_KEY +import com.tangem.feature.qrscanning.usecase.EmitQrScannedEventUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +internal class QrScanningViewModel @Inject constructor( + private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase, + savedStateHandle: SavedStateHandle, +) : ViewModel() { + + private val source: SourceType = savedStateHandle[SOURCE_KEY] ?: error("Source is mandatory") + + fun onQrScanned(qrCode: String) { + viewModelScope.launch { + emitQrScannedEventUseCase.invoke(source, qrCode) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/MLKitBarcodeAnalyzer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt similarity index 96% rename from app/src/main/java/com/tangem/tap/common/qrCodeScan/MLKitBarcodeAnalyzer.kt rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt index d85d5f44c2..40a37a068f 100644 --- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/MLKitBarcodeAnalyzer.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common.qrCodeScan +package com.tangem.feature.qrscanning.inner import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageAnalysis diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PreviewBinder.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PreviewBinder.kt rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt index b958759c58..609a26688a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PreviewBinder.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.details.ui.walletconnect.dialogs +package com.tangem.feature.qrscanning.inner import android.content.Context import android.util.Size @@ -9,8 +9,7 @@ import androidx.camera.core.ImageAnalysis import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.lifecycle.LifecycleOwner -import com.tangem.tap.common.qrCodeScan.MLKitBarcodeAnalyzer -import com.tangem.wallet.databinding.LayoutQrScanningBinding +import com.tangem.feature.qrscanning.impl.databinding.LayoutQrScanningBinding import java.util.concurrent.ExecutorService internal class PreviewBinder { diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ViewFinderOverlay.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt similarity index 93% rename from app/src/main/java/com/tangem/tap/common/qrCodeScan/ViewFinderOverlay.kt rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt index f4939b828f..febd5bbf06 100644 --- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ViewFinderOverlay.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt @@ -1,11 +1,11 @@ -package com.tangem.tap.common.qrCodeScan +package com.tangem.feature.qrscanning.inner import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.View import androidx.core.content.ContextCompat -import com.tangem.wallet.R +import com.tangem.feature.qrscanning.impl.R class ViewFinderOverlay(context: Context, attrs: AttributeSet) : View(context, attrs) { diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/DefaultQrScanningEventsRepository.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/DefaultQrScanningEventsRepository.kt new file mode 100644 index 0000000000..146bee2ef6 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/DefaultQrScanningEventsRepository.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.qrscanning.repo + +import com.tangem.feature.qrscanning.SourceType +import kotlinx.coroutines.flow.* + +internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { + + private data class QrScanningEvent(val type: SourceType, val qrCode: String) + + private val scannedEvents = MutableSharedFlow() + + override suspend fun emitResult(type: SourceType, qrCode: String) { + scannedEvents.emit(QrScanningEvent(type, qrCode)) + } + + override fun subscribeToScanningResults(type: SourceType) = scannedEvents + .filter { it.type == type } + .map { it.qrCode } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/QrScanningEventsRepository.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/QrScanningEventsRepository.kt new file mode 100644 index 0000000000..3640e79c2a --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/repo/QrScanningEventsRepository.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.qrscanning.repo + +import com.tangem.feature.qrscanning.SourceType +import kotlinx.coroutines.flow.Flow + +interface QrScanningEventsRepository { + + suspend fun emitResult(type: SourceType, qrCode: String) + + fun subscribeToScanningResults(type: SourceType): Flow +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/EmitQrScannedEventUseCase.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/EmitQrScannedEventUseCase.kt new file mode 100644 index 0000000000..cc7f7bc6c2 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/EmitQrScannedEventUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.qrscanning.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.feature.qrscanning.SourceType +import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository + +internal class EmitQrScannedEventUseCase( + private val repository: QrScanningEventsRepository, +) { + suspend operator fun invoke(type: SourceType, qrCode: String): Either { + return try { + repository.emitResult(type, qrCode) + Unit.right() + } catch (e: Exception) { + e.left() + } + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/ListenToQrScanningUseCase.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/ListenToQrScanningUseCase.kt new file mode 100644 index 0000000000..e3ef9f9dc1 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/usecase/ListenToQrScanningUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.feature.qrscanning.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.feature.qrscanning.SourceType +import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository +import kotlinx.coroutines.flow.Flow +import java.lang.Exception + +class ListenToQrScanningUseCase( + val repository: QrScanningEventsRepository, +) { + + operator fun invoke(type: SourceType): Either> { + return try { + repository.subscribeToScanningResults(type).right() + } catch (e: Exception) { + e.left() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/layout_qr_scanning.xml b/features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml similarity index 91% rename from app/src/main/res/layout/layout_qr_scanning.xml rename to features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml index 964d9acae6..ecf16ff82e 100644 --- a/app/src/main/res/layout/layout_qr_scanning.xml +++ b/features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - + + #FFFFFFFF + \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/res/values/dimens.xml b/features/qr-scanning/impl/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..19f54ca894 --- /dev/null +++ b/features/qr-scanning/impl/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + + 4dp + 8dp + + \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 9aee74781c..73d457f302 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -59,7 +59,6 @@ kotsonGsonExt = "2.5.0" lottie = "3.4.0" moshi = "1.12.0" okhttp = "4.9.3" -otaliastudiosCameraView = "2.6.3" rekotlin = "1.0.4" retrofit = "2.8.1" retrofitMoshiConverter = "2.6.0" @@ -215,7 +214,6 @@ moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okHttp-prettyLogging = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "okHttp-prettyLogging" } -otaliastudiosCameraView = { module = "com.otaliastudios:cameraview", version.ref = "otaliastudiosCameraView" } shopify-buy = { module = "com.shopify.mobilebuysdk:buy3", version.ref = "shopifyBuySdk" } spongecastle-core = { module = "com.madgag.spongycastle:core", version.ref = "spongycastleCryptoCore" } reKotlin = { module = "org.rekotlin:rekotlin", version.ref = "rekotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1b6c040120..0f7c6dfa32 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,7 +27,7 @@ dependencyResolutionManagement { google() mavenCentral() mavenLocal() - jcenter() // unable to replace with mavenCentral() due to rekotlin and com.otaliastudios:cameraview + jcenter() // unable to replace with mavenCentral() due to rekotlin maven("https://nexus.tangem-tech.com/repository/maven-releases/") maven { // setting any repository from tangem project allows maven search all packages in the project @@ -101,6 +101,9 @@ include(":features:send:impl") include(":features:manage-tokens:api") include(":features:manage-tokens:impl") + +include(":features:qr-scanning:api") +include(":features:qr-scanning:impl") // endregion Feature modules // region Domain modules @@ -140,4 +143,4 @@ include(":data:settings") include(":data:txhistory") include(":data:wallets") include(":data:analytics") -// endregion Data modules \ No newline at end of file +// endregion Data modules