Skip to content

Commit 9dc02cd

Browse files
authored
Branch for all changes related to widgets. We cannot use feature flags. (#6572)
Task/Issue URL: https://app.asana.com/1/137249556945/project/715106103902962/task/1211036141742774?focus=true ### Description This branch will eventually contain all changes to widgets + new widgets. We want to merge to develop only after all changes are implemented. ### Steps to test this PR All tests will be conducted in individual PRs. ### UI changes All UI changes will be shown in individual PRs.
1 parent 117836d commit 9dc02cd

File tree

94 files changed

+3030
-279
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+3030
-279
lines changed

app/src/androidTest/java/com/duckduckgo/widget/SearchAndFavoritesGridCalculatorKtTest.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ class SearchAndFavoritesGridCalculatorKtTest {
5252
return arrayOf(
5353
TestCase(2, 100),
5454
TestCase(2, 144),
55-
TestCase(3, 212),
55+
TestCase(2, 212),
5656
TestCase(3, 279),
57-
TestCase(4, 280),
58-
TestCase(5, 348),
59-
TestCase(6, 416),
60-
TestCase(7, 484),
61-
TestCase(8, 552),
57+
TestCase(3, 280),
58+
TestCase(4, 348),
59+
TestCase(4, 416),
60+
TestCase(4, 484),
61+
TestCase(4, 552),
6262
)
6363
}
6464
}
@@ -90,10 +90,10 @@ class SearchAndFavoritesGridCalculatorKtTest {
9090
return arrayOf(
9191
TestCase(1, 100),
9292
TestCase(1, 172),
93-
TestCase(2, 270),
94-
TestCase(3, 368),
93+
TestCase(1, 270),
94+
TestCase(2, 368),
9595
TestCase(3, 465),
96-
TestCase(4, 466),
96+
TestCase(3, 466),
9797
TestCase(4, 564),
9898
TestCase(4, 662),
9999
TestCase(4, 760),

app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,18 @@
518518
android:resource="@xml/search_widget_info_light" />
519519
</receiver>
520520

521+
<receiver
522+
android:name="com.duckduckgo.widget.SearchOnlyWidget"
523+
android:exported="false"
524+
android:label="@string/searchOnlyWidgetLabel">
525+
<intent-filter>
526+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
527+
</intent-filter>
528+
<meta-data
529+
android:name="android.appwidget.provider"
530+
android:resource="@xml/search_only_widget_info" />
531+
</receiver>
532+
521533
<receiver
522534
android:name="com.duckduckgo.widget.SearchAndFavoritesWidget"
523535
android:exported="false"
@@ -538,6 +550,14 @@
538550
</intent-filter>
539551
</receiver>
540552

553+
<receiver
554+
android:name="com.duckduckgo.widget.DuckAiSearchWidgetUpdaterReceiver"
555+
android:exported="false">
556+
<intent-filter>
557+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
558+
</intent-filter>
559+
</receiver>
560+
541561
<receiver
542562
android:name=".remotemessage.SharePromoLinkRMFBroadCastReceiver"
543563
android:exported="false" />

app/src/main/java/com/duckduckgo/app/WidgetThemeConfiguration.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,16 @@ class WidgetThemeConfiguration : DuckDuckGoActivity() {
7171
if (appBuildConfig.sdkInt >= Build.VERSION_CODES.Q) {
7272
binding.widgetConfigThemeSystem.visibility = View.VISIBLE
7373
binding.widgetConfigThemeSystem.isChecked = true
74+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_daynight)
7475
} else {
7576
binding.widgetConfigThemeSystem.visibility = View.GONE
7677
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
7778
if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
7879
binding.widgetConfigThemeDark.isChecked = true
80+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_dark)
7981
} else {
8082
binding.widgetConfigThemeLight.isChecked = true
83+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_light)
8184
}
8285
}
8386

@@ -86,16 +89,16 @@ class WidgetThemeConfiguration : DuckDuckGoActivity() {
8689
R.id.widgetConfigThemeSystem -> {
8790
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
8891
if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
89-
binding.widgetConfigPreview.setImageResource(R.drawable.search_favorites_widget_dark_preview)
92+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_dark)
9093
} else {
91-
binding.widgetConfigPreview.setImageResource(R.drawable.search_favorites_widget_light_preview)
94+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_light)
9295
}
9396
}
9497
R.id.widgetConfigThemeLight -> {
95-
binding.widgetConfigPreview.setImageResource(R.drawable.search_favorites_widget_light_preview)
98+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_light)
9699
}
97100
R.id.widgetConfigThemeDark -> {
98-
binding.widgetConfigPreview.setImageResource(R.drawable.search_favorites_widget_dark_preview)
101+
binding.widgetConfigPreview.setImageResource(R.drawable.image_preview_search_favorites_widget_dark)
99102
}
100103
}
101104
}

app/src/main/java/com/duckduckgo/app/di/AppComponent.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.duckduckgo.di.scopes.AppScope
3030
import com.duckduckgo.widget.EmptyFavoritesWidgetService
3131
import com.duckduckgo.widget.FavoritesWidgetService
3232
import com.duckduckgo.widget.SearchAndFavoritesWidget
33+
import com.duckduckgo.widget.SearchOnlyWidget
3334
import com.duckduckgo.widget.SearchWidget
3435
import com.squareup.anvil.annotations.MergeComponent
3536
import dagger.BindsInstance
@@ -85,6 +86,8 @@ interface AppComponent : AndroidInjector<DuckDuckGoApplication> {
8586

8687
fun inject(searchWidget: SearchWidget)
8788

89+
fun inject(searchOnlyWidget: SearchOnlyWidget)
90+
8891
fun inject(searchAndFavsWidget: SearchAndFavoritesWidget)
8992

9093
fun inject(favoritesWidgetItemFactory: FavoritesWidgetService.FavoritesWidgetItemFactory)

app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
7979
SEARCH_AND_FAVORITES_WIDGET_DELETED(pixelName = "m_search_and_favorites_widget_deleted"),
8080
SEARCH_WIDGET_ADDED(pixelName = "m_search_widget_added"),
8181
SEARCH_WIDGET_DELETED(pixelName = "m_search_widget_deleted"),
82+
SEARCH_ONLY_WIDGET_ADDED(pixelName = "m_search_only_widget_added"),
83+
SEARCH_ONLY_WIDGET_DELETED(pixelName = "m_search_only_widget_deleted"),
84+
DUCKAI_ONLY_WIDGET_ADDED(pixelName = "m_duckai_only_widget_added"),
85+
DUCKAI_ONLY_WIDGET_DELETED(pixelName = "m_duckai_only_widget_deleted"),
8286

8387
FAVORITE_OMNIBAR_ITEM_PRESSED("m_fav_o"),
8488
FAVORITE_HOMETAB_ITEM_PRESSED("m_fav_ht"),

app/src/main/java/com/duckduckgo/app/widget/AddWidgetLauncher.kt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,20 @@ import com.duckduckgo.app.widget.ui.WidgetCapabilities
3030
import com.duckduckgo.common.ui.store.AppTheme
3131
import com.duckduckgo.di.scopes.AppScope
3232
import com.duckduckgo.widget.SearchAndFavoritesWidget
33+
import com.duckduckgo.widget.SearchOnlyWidget
3334
import com.duckduckgo.widget.SearchWidget
3435
import com.duckduckgo.widget.SearchWidgetLight
3536
import com.squareup.anvil.annotations.ContributesBinding
3637
import javax.inject.Inject
3738
import javax.inject.Named
3839

3940
interface AddWidgetLauncher {
40-
fun launchAddWidget(activity: Activity?, simpleWidgetPrompt: Boolean = false)
41+
fun launchAddWidget(
42+
activity: Activity?,
43+
simpleWidgetPrompt: Boolean = false,
44+
searchOnlyWidgetPrompt: Boolean = false,
45+
duckAiOnlyWidgetPrompt: Boolean = false,
46+
)
4147
}
4248

4349
@ContributesBinding(AppScope::class)
@@ -47,11 +53,16 @@ class AddWidgetCompatLauncher @Inject constructor(
4753
private val widgetCapabilities: WidgetCapabilities,
4854
) : AddWidgetLauncher {
4955

50-
override fun launchAddWidget(activity: Activity?, simpleWidgetPrompt: Boolean) {
56+
override fun launchAddWidget(
57+
activity: Activity?,
58+
simpleWidgetPrompt: Boolean,
59+
searchOnlyWidgetPrompt: Boolean,
60+
duckAiOnlyWidgetPrompt: Boolean,
61+
) {
5162
if (widgetCapabilities.supportsAutomaticWidgetAdd) {
52-
defaultAddWidgetLauncher.launchAddWidget(activity, simpleWidgetPrompt)
63+
defaultAddWidgetLauncher.launchAddWidget(activity, simpleWidgetPrompt, searchOnlyWidgetPrompt, duckAiOnlyWidgetPrompt)
5364
} else {
54-
legacyAddWidgetLauncher.launchAddWidget(activity, simpleWidgetPrompt)
65+
legacyAddWidgetLauncher.launchAddWidget(activity, simpleWidgetPrompt, searchOnlyWidgetPrompt, duckAiOnlyWidgetPrompt)
5566
}
5667
}
5768
}
@@ -71,6 +82,8 @@ class AppWidgetManagerAddWidgetLauncher @Inject constructor(
7182
override fun launchAddWidget(
7283
activity: Activity?,
7384
simpleWidgetPrompt: Boolean,
85+
searchOnlyWidgetPrompt: Boolean,
86+
duckAiOnlyWidgetPrompt: Boolean,
7487
) {
7588
activity?.let {
7689
val widgetLabel: String
@@ -83,6 +96,10 @@ class AppWidgetManagerAddWidgetLauncher @Inject constructor(
8396
widgetLabel = it.getString(R.string.searchWidgetLabel)
8497
ComponentName(it, SearchWidget::class.java)
8598
}
99+
searchOnlyWidgetPrompt -> {
100+
widgetLabel = it.getString(R.string.searchOnlyWidgetLabel)
101+
ComponentName(it, SearchOnlyWidget::class.java)
102+
}
86103
else -> {
87104
widgetLabel = it.getString(R.string.favoritesWidgetLabel)
88105
ComponentName(it, SearchAndFavoritesWidget::class.java)
@@ -109,7 +126,7 @@ class AppWidgetManagerAddWidgetLauncher @Inject constructor(
109126
@ContributesBinding(AppScope::class)
110127
@Named("legacyAddWidgetLauncher")
111128
class LegacyAddWidgetLauncher @Inject constructor() : AddWidgetLauncher {
112-
override fun launchAddWidget(activity: Activity?, simpleWidgetPrompt: Boolean) {
129+
override fun launchAddWidget(activity: Activity?, simpleWidgetPrompt: Boolean, searchOnlyWidgetPrompt: Boolean, duckAiOnlyWidgetPrompt: Boolean) {
113130
activity?.let {
114131
val options = ActivityOptions.makeSceneTransitionAnimation(it).toBundle()
115132
it.startActivity(AddWidgetInstructionsActivity.intent(it), options)

app/src/main/java/com/duckduckgo/app/widget/ui/WidgetCapabilities.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetManager
2020
import android.content.ComponentName
2121
import android.content.Context
2222
import com.duckduckgo.widget.SearchAndFavoritesWidget
23+
import com.duckduckgo.widget.SearchOnlyWidget
2324
import com.duckduckgo.widget.SearchWidget
2425
import com.duckduckgo.widget.SearchWidgetLight
2526
import javax.inject.Inject
@@ -45,6 +46,7 @@ val Context.hasInstalledWidgets: Boolean
4546
val manager = AppWidgetManager.getInstance(this)
4647
val hasDarkWidget = manager.getAppWidgetIds(ComponentName(this, SearchWidget::class.java)).any()
4748
val hasLightWidget = manager.getAppWidgetIds(ComponentName(this, SearchWidgetLight::class.java)).any()
49+
val hasSearchOnlyWidget = manager.getAppWidgetIds(ComponentName(this, SearchOnlyWidget::class.java)).any()
4850
val hasSearchAndFavoritesWidget = manager.getAppWidgetIds(ComponentName(this, SearchAndFavoritesWidget::class.java)).any()
49-
return hasDarkWidget || hasLightWidget || hasSearchAndFavoritesWidget
51+
return hasDarkWidget || hasLightWidget || hasSearchOnlyWidget || hasSearchAndFavoritesWidget
5052
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2025 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.widget
18+
19+
import android.appwidget.AppWidgetManager
20+
import android.content.BroadcastReceiver
21+
import android.content.Context
22+
import android.content.Intent
23+
import com.duckduckgo.anvil.annotations.InjectWith
24+
import com.duckduckgo.di.scopes.ReceiverScope
25+
import dagger.android.AndroidInjection
26+
import javax.inject.Inject
27+
28+
@InjectWith(ReceiverScope::class)
29+
class DuckAiSearchWidgetUpdaterReceiver : BroadcastReceiver() {
30+
31+
@Inject
32+
lateinit var widgetUpdater: WidgetUpdater
33+
34+
override fun onReceive(
35+
context: Context,
36+
intent: Intent,
37+
) {
38+
AndroidInjection.inject(this, context)
39+
if (intent.action == AppWidgetManager.ACTION_APPWIDGET_UPDATE) {
40+
widgetUpdater.updateWidgets(context)
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)