diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt index b32f80b8c2ce..7c1574ebfa25 100644 --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt @@ -65,4 +65,7 @@ interface SyncFeature { @Toggle.DefaultValue(DefaultFeatureValue.TRUE) fun canOverrideThemeSyncSetup(): Toggle + + @Toggle.DefaultValue(DefaultFeatureValue.TRUE) + fun useNewActivityConnectSyncLayout(): Toggle } diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncConnectActivity.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncConnectActivity.kt index c4413b5c1873..d18d0837d802 100644 --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncConnectActivity.kt +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncConnectActivity.kt @@ -19,18 +19,23 @@ package com.duckduckgo.sync.impl.ui import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View +import android.widget.ImageView import androidx.activity.addCallback import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.common.ui.DuckDuckGoActivity +import com.duckduckgo.common.ui.view.button.DaxButtonGhost import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder import com.duckduckgo.common.ui.view.show -import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.mobile.android.databinding.IncludeDefaultToolbarBinding import com.duckduckgo.sync.impl.R +import com.duckduckgo.sync.impl.SyncFeature import com.duckduckgo.sync.impl.databinding.ActivityConnectSyncBinding +import com.duckduckgo.sync.impl.databinding.ActivityConnectSyncNewBinding import com.duckduckgo.sync.impl.ui.EnterCodeActivity.Companion.Code.CONNECT_CODE import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.Command import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.Command.FinishWithError @@ -39,15 +44,21 @@ import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.Command.ReadTextCode import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.Command.ShowError import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.Command.ShowMessage import com.duckduckgo.sync.impl.ui.SyncConnectViewModel.ViewState +import com.duckduckgo.sync.impl.ui.qrcode.SyncBarcodeView import com.duckduckgo.sync.impl.ui.setup.EnterCodeContract import com.duckduckgo.sync.impl.ui.setup.EnterCodeContract.EnterCodeContractOutput import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import javax.inject.Inject @InjectWith(ActivityScope::class) class SyncConnectActivity : DuckDuckGoActivity() { - private val binding: ActivityConnectSyncBinding by viewBinding() + + @Inject + lateinit var syncFeature: SyncFeature + + private lateinit var binding: ConnectSyncBinding private val viewModel: SyncConnectViewModel by bindViewModel() private val enterCodeLauncher = registerForActivityResult( @@ -60,6 +71,15 @@ class SyncConnectActivity : DuckDuckGoActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + binding = if (syncFeature.useNewActivityConnectSyncLayout().isEnabled()) { + val viewBinding = ActivityConnectSyncNewBinding.inflate(layoutInflater) + ConnectSyncBinding.NewBinding(viewBinding) + } else { + val viewBinding = ActivityConnectSyncBinding.inflate(layoutInflater) + ConnectSyncBinding.OldBinding(viewBinding) + } + setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) @@ -166,3 +186,27 @@ class SyncConnectActivity : DuckDuckGoActivity() { private const val SOURCE_INTENT_KEY = "source" } } + +private sealed interface ConnectSyncBinding { + val root: View + val includeToolbar: IncludeDefaultToolbarBinding + val qrCodeReader: SyncBarcodeView + val qrCodeImageView: ImageView + val copyCodeButton: DaxButtonGhost + + data class OldBinding(private val binding: ActivityConnectSyncBinding) : ConnectSyncBinding { + override val root: View get() = binding.root + override val includeToolbar: IncludeDefaultToolbarBinding get() = binding.includeToolbar + override val qrCodeReader: SyncBarcodeView get() = binding.qrCodeReader + override val qrCodeImageView: ImageView get() = binding.qrCodeImageView + override val copyCodeButton: DaxButtonGhost get() = binding.copyCodeButton + } + + data class NewBinding(private val binding: ActivityConnectSyncNewBinding) : ConnectSyncBinding { + override val root: View get() = binding.root + override val includeToolbar: IncludeDefaultToolbarBinding get() = binding.includeToolbar + override val qrCodeReader: SyncBarcodeView get() = binding.qrCodeReader + override val qrCodeImageView: ImageView get() = binding.qrCodeImageView + override val copyCodeButton: DaxButtonGhost get() = binding.copyCodeButton + } +} diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/qrcode/SyncBarcodeView.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/qrcode/SyncBarcodeView.kt index 141c74f65534..893527d2467f 100644 --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/qrcode/SyncBarcodeView.kt +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/qrcode/SyncBarcodeView.kt @@ -30,11 +30,13 @@ import android.view.View import android.widget.FrameLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.view.doOnNextLayout import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.ConflatedJob import com.duckduckgo.common.utils.DispatcherProvider @@ -48,7 +50,6 @@ import com.duckduckgo.sync.impl.ui.qrcode.SquareDecoratedBarcodeViewModel.Comman import com.duckduckgo.sync.impl.ui.qrcode.SquareDecoratedBarcodeViewModel.Command.RequestPermissions import com.duckduckgo.sync.impl.ui.qrcode.SquareDecoratedBarcodeViewModel.ViewState import dagger.android.support.AndroidSupportInjection -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import javax.inject.Inject @@ -66,12 +67,27 @@ constructor( ) : FrameLayout(context, attrs, defStyleAttr) { + private val minScanningAreaHeight: Int + + init { + context.obtainStyledAttributes(attrs, R.styleable.SyncBarcodeView).apply { + try { + minScanningAreaHeight = getDimensionPixelSize(R.styleable.SyncBarcodeView_minScanningAreaHeight, MIN_SCANNING_AREA_HEIGHT_NOT_SET) + } finally { + recycle() + } + } + } + @Inject lateinit var viewModelFactory: SquareDecoratedBarcodeViewModel.Factory @Inject lateinit var dispatchers: DispatcherProvider + @Inject + lateinit var appBuildConfig: AppBuildConfig + private val cameraBlockedDrawable by lazy { ContextCompat.getDrawable(context, R.drawable.camera_blocked) } @@ -93,6 +109,8 @@ constructor( AndroidSupportInjection.inject(this) super.onAttachedToWindow() + setupExpandedScanningArea() + findViewTreeLifecycleOwner()?.lifecycle?.addObserver(viewModel) val scope = findViewTreeLifecycleOwner()?.lifecycleScope!! @@ -110,6 +128,23 @@ constructor( } } + private fun setupExpandedScanningArea() { + if (minScanningAreaHeight == MIN_SCANNING_AREA_HEIGHT_NOT_SET) return + binding.barcodeContainer.doOnNextLayout { + val containerHeight = binding.barcodeContainer.height + + if (containerHeight >= minScanningAreaHeight) return@doOnNextLayout + + binding.barcodeView.layoutParams = binding.barcodeView.layoutParams.apply { + height = minScanningAreaHeight + } + + binding.barcodeView.doOnNextLayout { + binding.barcodeView.translationY = -((minScanningAreaHeight - containerHeight) / 2f) + } + } + } + override fun onDetachedFromWindow() { conflatedStateJob.cancel() conflatedCommandJob.cancel() @@ -217,4 +252,8 @@ constructor( } throw IllegalStateException("The ${this.javaClass.simpleName}'s Context is not an Activity.") } + + companion object { + private const val MIN_SCANNING_AREA_HEIGHT_NOT_SET = -1 + } } diff --git a/sync/sync-impl/src/main/res/layout/activity_connect_sync_new.xml b/sync/sync-impl/src/main/res/layout/activity_connect_sync_new.xml new file mode 100644 index 000000000000..c98b8de3c622 --- /dev/null +++ b/sync/sync-impl/src/main/res/layout/activity_connect_sync_new.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sync/sync-impl/src/main/res/layout/view_square_decorated_barcode.xml b/sync/sync-impl/src/main/res/layout/view_square_decorated_barcode.xml index a3cd63086f62..1b96419e9dc0 100644 --- a/sync/sync-impl/src/main/res/layout/view_square_decorated_barcode.xml +++ b/sync/sync-impl/src/main/res/layout/view_square_decorated_barcode.xml @@ -18,10 +18,19 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - + android:layout_height="match_parent" + android:clipChildren="true" + android:clipToPadding="true"> + + + + + + + + + + + + + \ No newline at end of file