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