Skip to content

Commit b210b63

Browse files
authored
Make "bookmark added" dialog specific to that use case (#7099)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208273769335188/task/1211910160515992?focus=true ### Description This is taking `BookmarksBottomSheetDialog`, which is generic but only ever used for a specific use case (bookmark added confirmation) and just making it specific to that use case. This simplifies it as well as better preparing it for extension to support adding an option to let users know they can enable sync. - `BookmarksBottomSheetDialog` --> `BookmarkAddedConfirmationDialog` Strings moving to the correct module is done in #7127 to de-noise this one.. ### Steps to test this PR **Auto-dismisses** - [x] Visit site, tap overflow and choose `Add Bookmark`. - [x] Verify the bookmark added dialog shows and looks the same as prod does - [x] Don't interact with it; verify it auto-dismisses (after ~3.5s) - [x] Choose `Edit Bookmark` and delete it **Edit (not as favorite)** - [x] Choose `Add Bookmark` again, don't toggle the `Add to Favorites` but do tap on `Edit Bookmark`. Verify it isn't marked as favorite. - [x] Delete it **Edit (when a favorite)** - [x] Choose `Add Bookmark` again, this time toggle the `Add to Favorites` to enabled - [x] Tap `Edit Bookmark`. Verify it is still marked as a favorite.
1 parent e003507 commit b210b63

File tree

4 files changed

+160
-275
lines changed

4 files changed

+160
-275
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright (c) 2024 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.bookmarks.dialog
18+
19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.graphics.Typeface
22+
import android.text.Spannable
23+
import android.text.SpannableString
24+
import android.text.style.StyleSpan
25+
import android.view.LayoutInflater
26+
import android.view.WindowManager
27+
import android.widget.FrameLayout
28+
import androidx.lifecycle.lifecycleScope
29+
import com.duckduckgo.app.browser.databinding.BottomSheetAddBookmarkBinding
30+
import com.duckduckgo.common.utils.ConflatedJob
31+
import com.duckduckgo.savedsites.api.models.BookmarkFolder
32+
import com.google.android.material.R
33+
import com.google.android.material.bottomsheet.BottomSheetBehavior
34+
import com.google.android.material.bottomsheet.BottomSheetDialog
35+
import com.google.android.material.shape.CornerFamily
36+
import com.google.android.material.shape.MaterialShapeDrawable
37+
import com.google.android.material.shape.ShapeAppearanceModel
38+
import kotlinx.coroutines.delay
39+
import kotlinx.coroutines.launch
40+
import com.duckduckgo.mobile.android.R as CommonR
41+
42+
@SuppressLint("NoBottomSheetDialog")
43+
class BookmarkAddedConfirmationDialog(
44+
context: Context,
45+
private val bookmarkFolder: BookmarkFolder?,
46+
) : BottomSheetDialog(context) {
47+
48+
abstract class EventListener {
49+
/** Sets a listener to be invoked when favorite state is changed */
50+
open fun onFavoriteStateChangeClicked(isFavorited: Boolean) {}
51+
52+
/** Sets a listener to be invoked when edit bookmarks is clicked */
53+
open fun onEditBookmarkClicked() {}
54+
}
55+
56+
private var listener: EventListener? = null
57+
58+
private val binding = BottomSheetAddBookmarkBinding.inflate(LayoutInflater.from(context))
59+
60+
private val autoDismissDialogJob = ConflatedJob()
61+
62+
override fun show() {
63+
setContentView(binding.root)
64+
65+
window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
66+
behavior.state = BottomSheetBehavior.STATE_EXPANDED
67+
behavior.isDraggable = false
68+
roundCornersAlways(this)
69+
binding.bookmarksBottomSheetDialogTitle.text = getBookmarksBottomSheetTitle(context, bookmarkFolder)
70+
71+
binding.setAsFavorite.setOnClickListener {
72+
cancelDialogAutoDismiss()
73+
binding.setAsFavoriteSwitch.isChecked = !binding.setAsFavoriteSwitch.isChecked
74+
listener?.onFavoriteStateChangeClicked(binding.setAsFavoriteSwitch.isChecked)
75+
}
76+
binding.setAsFavoriteSwitch.setOnClickListener {
77+
cancelDialogAutoDismiss()
78+
listener?.onFavoriteStateChangeClicked(binding.setAsFavoriteSwitch.isChecked)
79+
}
80+
binding.editBookmark.setOnClickListener {
81+
cancelDialogAutoDismiss()
82+
listener?.onEditBookmarkClicked()
83+
dismiss()
84+
}
85+
86+
autoDismissDialogJob += lifecycleScope.launch {
87+
delay(BOOKMARKS_BOTTOM_SHEET_DURATION)
88+
dismiss()
89+
}
90+
super.show()
91+
}
92+
93+
private fun cancelDialogAutoDismiss() {
94+
autoDismissDialogJob.cancel()
95+
}
96+
97+
private fun getBookmarksBottomSheetTitle(context: Context, bookmarkFolder: BookmarkFolder?): SpannableString {
98+
val folderName = bookmarkFolder?.name ?: ""
99+
val fullText = context.getString(com.duckduckgo.saved.sites.impl.R.string.bookmarkAddedInBookmarks, folderName)
100+
val spannableString = SpannableString(fullText)
101+
102+
val boldStart = fullText.indexOf(folderName)
103+
val boldEnd = boldStart + folderName.length
104+
spannableString.setSpan(StyleSpan(Typeface.BOLD), boldStart, boldEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
105+
return spannableString
106+
}
107+
108+
/** Sets event listener for the bottom sheet dialog */
109+
fun addEventListener(eventListener: EventListener) {
110+
listener = eventListener
111+
}
112+
113+
// TODO: Use a style when bookmarks is moved to its own module
114+
private fun roundCornersAlways(dialog: BottomSheetDialog) {
115+
dialog.setOnShowListener { dialogInterface ->
116+
val bottomSheetDialog = dialogInterface as BottomSheetDialog
117+
val bottomSheet = bottomSheetDialog.findViewById<FrameLayout>(R.id.design_bottom_sheet)
118+
bottomSheet?.background = MaterialShapeDrawable(
119+
ShapeAppearanceModel.builder().apply {
120+
setTopLeftCorner(CornerFamily.ROUNDED, context.resources.getDimension(CommonR.dimen.dialogBorderRadius))
121+
setTopRightCorner(CornerFamily.ROUNDED, context.resources.getDimension(CommonR.dimen.dialogBorderRadius))
122+
}.build(),
123+
)
124+
}
125+
}
126+
127+
private companion object {
128+
private const val BOOKMARKS_BOTTOM_SHEET_DURATION = 3_500L
129+
}
130+
}

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 21 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,16 @@ import android.content.pm.ActivityInfo
3131
import android.content.pm.PackageManager
3232
import android.content.pm.ResolveInfo
3333
import android.content.res.Configuration
34-
import android.graphics.Typeface
3534
import android.net.Uri
3635
import android.os.Build
3736
import android.os.Bundle
3837
import android.os.Environment
3938
import android.os.Handler
40-
import android.os.Looper
4139
import android.os.Message
4240
import android.print.PrintAttributes
4341
import android.print.PrintManager
4442
import android.provider.MediaStore
45-
import android.text.Spannable
46-
import android.text.SpannableString
4743
import android.text.Spanned
48-
import android.text.style.StyleSpan
4944
import android.view.ContextMenu
5045
import android.view.MenuItem
5146
import android.view.MotionEvent
@@ -102,6 +97,7 @@ import androidx.webkit.WebViewCompat
10297
import androidx.webkit.WebViewFeature
10398
import com.duckduckgo.anvil.annotations.InjectWith
10499
import com.duckduckgo.app.accessibility.data.AccessibilitySettingsDataStore
100+
import com.duckduckgo.app.bookmarks.dialog.BookmarkAddedConfirmationDialog
105101
import com.duckduckgo.app.browser.BrowserTabViewModel.FileChooserRequestedParams
106102
import com.duckduckgo.app.browser.R.string
107103
import com.duckduckgo.app.browser.SSLErrorType.NONE
@@ -322,11 +318,9 @@ import com.duckduckgo.privacy.dashboard.api.ui.PrivacyDashboardHybridScreenResul
322318
import com.duckduckgo.privacyprotectionspopup.api.PrivacyProtectionsPopup
323319
import com.duckduckgo.privacyprotectionspopup.api.PrivacyProtectionsPopupFactory
324320
import com.duckduckgo.privacyprotectionspopup.api.PrivacyProtectionsPopupViewState
325-
import com.duckduckgo.savedsites.api.models.BookmarkFolder
326321
import com.duckduckgo.savedsites.api.models.SavedSite
327322
import com.duckduckgo.savedsites.api.models.SavedSite.Bookmark
328323
import com.duckduckgo.savedsites.api.models.SavedSitesNames
329-
import com.duckduckgo.savedsites.impl.bookmarks.BookmarksBottomSheetDialog
330324
import com.duckduckgo.savedsites.impl.bookmarks.FaviconPromptSheet
331325
import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment
332326
import com.duckduckgo.serp.logos.api.SerpLogoScreens.EasterEggLogoScreen
@@ -651,8 +645,6 @@ class BrowserTabFragment :
651645

652646
private lateinit var webViewContainer: FrameLayout
653647

654-
private var bookmarksBottomSheetDialog: BookmarksBottomSheetDialog.Builder? = null
655-
656648
private var autocompleteItemOffsetTop: Int = 0
657649
private var autocompleteFirstVisibleItemPosition: Int = 0
658650

@@ -3770,70 +3762,30 @@ class BrowserTabFragment :
37703762
}
37713763

37723764
private fun savedSiteAdded(savedSiteChangedViewState: SavedSiteChangedViewState) {
3773-
val dismissHandler = Handler(Looper.getMainLooper())
3774-
val dismissRunnable =
3775-
Runnable {
3776-
if (isAdded) {
3777-
bookmarksBottomSheetDialog?.dialog?.let { dialog ->
3778-
if (dialog.isShowing) {
3779-
dialog.dismiss()
3780-
}
3765+
context?.let { ctx ->
3766+
val dialog = BookmarkAddedConfirmationDialog(ctx, savedSiteChangedViewState.bookmarkFolder)
3767+
dialog.addEventListener(
3768+
object : BookmarkAddedConfirmationDialog.EventListener() {
3769+
override fun onFavoriteStateChangeClicked(isFavorited: Boolean) {
3770+
viewModel.onFavoriteMenuClicked()
37813771
}
3782-
}
3783-
}
3784-
val title = getBookmarksBottomSheetTitle(savedSiteChangedViewState.bookmarkFolder)
37853772

3786-
bookmarksBottomSheetDialog =
3787-
BookmarksBottomSheetDialog
3788-
.Builder(requireContext())
3789-
.setTitle(title)
3790-
.setPrimaryItem(
3791-
getString(com.duckduckgo.saved.sites.impl.R.string.addToFavorites),
3792-
icon = com.duckduckgo.mobile.android.R.drawable.ic_favorite_24,
3793-
).setSecondaryItem(
3794-
getString(com.duckduckgo.saved.sites.impl.R.string.editBookmark),
3795-
icon = com.duckduckgo.mobile.android.R.drawable.ic_edit_24,
3796-
).addEventListener(
3797-
object : BookmarksBottomSheetDialog.EventListener() {
3798-
override fun onPrimaryItemClicked() {
3799-
viewModel.onFavoriteMenuClicked()
3800-
dismissHandler.removeCallbacks(dismissRunnable)
3801-
}
3802-
3803-
override fun onSecondaryItemClicked() {
3804-
if (savedSiteChangedViewState.savedSite is Bookmark) {
3805-
pixel.fire(AppPixelName.ADD_BOOKMARK_CONFIRM_EDITED)
3806-
editSavedSite(
3807-
savedSiteChangedViewState.copy(
3808-
savedSite = savedSiteChangedViewState.savedSite.copy(
3809-
isFavorite = viewModel.browserViewState.value?.favorite != null,
3810-
),
3773+
override fun onEditBookmarkClicked() {
3774+
if (savedSiteChangedViewState.savedSite is Bookmark) {
3775+
pixel.fire(AppPixelName.ADD_BOOKMARK_CONFIRM_EDITED)
3776+
editSavedSite(
3777+
savedSiteChangedViewState.copy(
3778+
savedSite = savedSiteChangedViewState.savedSite.copy(
3779+
isFavorite = viewModel.browserViewState.value?.favorite != null,
38113780
),
3812-
)
3813-
dismissHandler.removeCallbacks(dismissRunnable)
3814-
}
3815-
}
3816-
3817-
override fun onBottomSheetDismissed() {
3818-
super.onBottomSheetDismissed()
3819-
dismissHandler.removeCallbacks(dismissRunnable)
3781+
),
3782+
)
38203783
}
3821-
},
3822-
)
3823-
bookmarksBottomSheetDialog?.show()
3824-
3825-
dismissHandler.postDelayed(dismissRunnable, BOOKMARKS_BOTTOM_SHEET_DURATION)
3826-
}
3827-
3828-
private fun getBookmarksBottomSheetTitle(bookmarkFolder: BookmarkFolder?): SpannableString {
3829-
val folderName = bookmarkFolder?.name ?: ""
3830-
val fullText = getString(com.duckduckgo.saved.sites.impl.R.string.bookmarkAddedInBookmarks, folderName)
3831-
val spannableString = SpannableString(fullText)
3832-
3833-
val boldStart = fullText.indexOf(folderName)
3834-
val boldEnd = boldStart + folderName.length
3835-
spannableString.setSpan(StyleSpan(Typeface.BOLD), boldStart, boldEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
3836-
return spannableString
3784+
}
3785+
},
3786+
)
3787+
dialog.show()
3788+
}
38373789
}
38383790

38393791
private fun editSavedSite(savedSiteChangedViewState: SavedSiteChangedViewState) {
@@ -4371,8 +4323,6 @@ class BrowserTabFragment :
43714323

43724324
private const val COOKIES_ANIMATION_DELAY = 400L
43734325

4374-
private const val BOOKMARKS_BOTTOM_SHEET_DURATION = 3500L
4375-
43764326
private const val AUTOCOMPLETE_PADDING_DP = 6
43774327

43784328
private const val SITE_SECURITY_WARNING = "Warning: Security Risk"
@@ -4618,7 +4568,6 @@ class BrowserTabFragment :
46184568
renderFullscreenMode(viewState)
46194569
privacyProtectionsPopup.setViewState(viewState.privacyProtectionsPopupViewState)
46204570

4621-
bookmarksBottomSheetDialog?.dialog?.toggleSwitch(viewState.favorite != null)
46224571
val bookmark =
46234572
viewModel.browserViewState.value
46244573
?.bookmark
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
xmlns:tools="http://schemas.android.com/tools"
2020
android:layout_width="match_parent"
2121
android:layout_height="match_parent"
22+
android:background="@drawable/rounded_top_corners_bottom_sheet_drawable"
2223
android:orientation="vertical"
2324
android:paddingTop="@dimen/actionBottomSheetVerticalPadding"
24-
android:paddingBottom="@dimen/actionBottomSheetVerticalPadding"
25-
android:background="@drawable/rounded_top_corners_bottom_sheet_drawable">
25+
android:paddingBottom="@dimen/actionBottomSheetVerticalPadding">
2626

2727
<com.duckduckgo.common.ui.view.text.DaxTextView
2828
android:id="@+id/bookmarksBottomSheetDialogTitle"
@@ -31,7 +31,6 @@
3131
android:layout_marginStart="@dimen/keyline_4"
3232
android:paddingTop="@dimen/bottomSheetTitleVerticalPadding"
3333
android:paddingBottom="@dimen/bottomSheetTitleVerticalPadding"
34-
android:visibility="gone"
3534
app:textType="secondary"
3635
app:typography="body1"
3736
tools:text="Actions" />
@@ -41,14 +40,15 @@
4140
android:layout_height="wrap_content">
4241

4342
<com.duckduckgo.common.ui.view.listitem.OneLineListItem
44-
android:id="@+id/bookmarksBottomSheetDialogPrimaryItem"
43+
android:id="@+id/setAsFavorite"
4544
android:layout_width="match_parent"
4645
android:layout_height="wrap_content"
4746
app:leadingIconBackground="circular"
48-
app:primaryText="Primary Item" />
47+
app:primaryText="@string/addToFavorites"
48+
app:leadingIcon="@drawable/ic_favorite_24" />
4949

5050
<com.duckduckgo.common.ui.view.DaxSwitch
51-
android:id="@+id/bookmarksBottomSheetSwitch"
51+
android:id="@+id/setAsFavoriteSwitch"
5252
android:layout_width="wrap_content"
5353
android:layout_height="wrap_content"
5454
android:layout_gravity="end|center_vertical"
@@ -57,10 +57,11 @@
5757

5858

5959
<com.duckduckgo.common.ui.view.listitem.OneLineListItem
60-
android:id="@+id/bookmarksBottomSheetDialogSecondaryItem"
60+
android:id="@+id/editBookmark"
6161
android:layout_width="match_parent"
6262
android:layout_height="wrap_content"
63+
app:leadingIcon="@drawable/ic_edit_24"
6364
app:leadingIconBackground="circular"
64-
app:primaryText="Secondary Item" />
65+
app:primaryText="@string/editBookmark" />
6566

6667
</LinearLayout>

0 commit comments

Comments
 (0)