Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@ interface SyncFeature {

@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
fun canOverrideThemeSyncSetup(): Toggle

@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
fun useNewActivityConnectSyncLayout(): Toggle
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -60,6 +71,15 @@ class SyncConnectActivity : DuckDuckGoActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = if (syncFeature.useNewActivityConnectSyncLayout().isEnabled()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could duplicate the entire activity and navigate to one or the other based on RC if we think that's safer

val viewBinding = ActivityConnectSyncNewBinding.inflate(layoutInflater)
ConnectSyncBinding.NewBinding(viewBinding)
} else {
val viewBinding = ActivityConnectSyncBinding.inflate(layoutInflater)
ConnectSyncBinding.OldBinding(viewBinding)
}

setContentView(binding.root)
setupToolbar(binding.includeToolbar.toolbar)

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -93,6 +109,8 @@ constructor(
AndroidSupportInjection.inject(this)
super.onAttachedToWindow()

setupExpandedScanningArea()

findViewTreeLifecycleOwner()?.lifecycle?.addObserver(viewModel)

val scope = findViewTreeLifecycleOwner()?.lifecycleScope!!
Expand All @@ -110,6 +128,23 @@ constructor(
}
}

private fun setupExpandedScanningArea() {
if (minScanningAreaHeight == MIN_SCANNING_AREA_HEIGHT_NOT_SET) return
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If RC flag is off, minScanningAreaHeight will be equal to MIN_SCANNING_AREA_HEIGHT_NOT_SET, so I don't think we need to feature flag here as well

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()
Expand Down Expand Up @@ -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
}
}
153 changes: 153 additions & 0 deletions sync/sync-impl/src/main/res/layout/activity_connect_sync_new.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2023 DuckDuckGo
~
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<include
android:id="@+id/includeToolbar"
layout="@layout/include_default_toolbar" />

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:id="@+id/readerContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?attr/daxColorSurface"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/qrcodeContainer"
app:layout_constraintTop_toTopOf="parent">

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/connect_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/keyline_4"
android:text="@string/connect_screen_scan_qr_hint" />

<com.duckduckgo.sync.impl.ui.qrcode.SyncBarcodeView
android:id="@+id/qrCodeReader"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:minScanningAreaHeight="@dimen/qrBarcodeSize"/>
</LinearLayout>

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/qrcodeContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/daxColorBackground"
app:layout_constraintBottom_toTopOf="@id/copyCodeDivider"
app:layout_constraintHeight_min="185dp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="20dp"
app:layout_constraintVertical_chainStyle="spread">

<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="@dimen/qrSizeMedium"
android:layout_height="@dimen/qrSizeMedium"
android:layout_gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="ContentDescription" />

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/qrCodeHintTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
app:layout_constraintWidth_max="300dp"
android:text="@string/connect_screen_connect_qr_hint_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/qrCodeImageView"
app:layout_constraintTop_toTopOf="@id/qrCodeImageView"
app:typography="h5" />

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/qrCodeHintContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="@dimen/keyline_2"
app:layout_constraintWidth_max="300dp"
android:text="@string/connect_screen_connect_qr_hint_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/qrCodeImageView"
app:layout_constraintTop_toBottomOf="@+id/qrCodeHintTitle"
app:textType="secondary"
app:typography="caption" />

</androidx.constraintlayout.widget.ConstraintLayout>

<com.duckduckgo.common.ui.view.divider.HorizontalDivider
android:id="@+id/copyCodeDivider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toTopOf="@id/cantScanLabel"
/>

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/cantScanLabel"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="@dimen/keyline_4"
android:layout_marginStart="@dimen/keyline_1"
android:text="@string/login_screen_cant_scan_label"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/copyCodeButton"
app:layout_constraintStart_toStartOf="parent"
app:textType="secondary" />


<com.duckduckgo.common.ui.view.button.DaxButtonGhost
android:id="@+id/copyCodeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_screen_cant_scan_button_text"
app:daxButtonSize="large"
app:icon="@drawable/ic_copy_24"
app:layout_constraintBottom_toBottomOf="@id/cantScanLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cantScanLabel"
app:layout_constraintTop_toTopOf="@id/cantScanLabel" />

<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:layout_width="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="0dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<com.journeyapps.barcodescanner.BarcodeView
android:id="@+id/barcodeView"
<FrameLayout
android:id="@+id/barcodeContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:clipChildren="true"
android:clipToPadding="true">

<com.journeyapps.barcodescanner.BarcodeView
android:id="@+id/barcodeView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</FrameLayout>

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/barcodeHint"
Expand Down
24 changes: 24 additions & 0 deletions sync/sync-impl/src/main/res/values/attrs-sync-barcode-view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2023 DuckDuckGo
~
~ 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.
-->

<resources>

<!--SyncBarcodeView-->
<declare-styleable name="SyncBarcodeView">
<attr name="minScanningAreaHeight" format="dimension" />
</declare-styleable>

</resources>
Loading