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
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ dependencies {
implementation project(':serp-logos-api')
implementation project(':serp-logos-impl')

// Widgets
implementation "androidx.core:core-remoteviews:1.1.0"

// Deprecated. TODO: Stop using this artifact.
implementation "androidx.legacy:legacy-support-v4:_"
debugImplementation Square.leakCanary.android
Expand Down
10 changes: 0 additions & 10 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,6 @@
android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity"
/>

<service
android:name="com.duckduckgo.widget.FavoritesWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />

<service
android:name="com.duckduckgo.widget.EmptyFavoritesWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />

<service
android:name="com.duckduckgo.customtabs.impl.service.DuckDuckGoCustomTabService"
android:exported="true"
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/com/duckduckgo/app/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import com.duckduckgo.app.onboarding.di.OnboardingModule
import com.duckduckgo.app.surrogates.di.ResourceSurrogateModule
import com.duckduckgo.app.usage.di.AppUsageModule
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.widget.EmptyFavoritesWidgetService
import com.duckduckgo.widget.FavoritesWidgetService
import com.duckduckgo.widget.EmptyFavoritesWidgetItemFactory
import com.duckduckgo.widget.FavoritesWidgetItemFactory
import com.duckduckgo.widget.SearchAndFavoritesWidget
import com.duckduckgo.widget.SearchOnlyWidget
import com.duckduckgo.widget.SearchWidget
Expand Down Expand Up @@ -88,11 +88,11 @@ interface AppComponent : AndroidInjector<DuckDuckGoApplication> {

fun inject(searchOnlyWidget: SearchOnlyWidget)

fun inject(searchAndFavsWidget: SearchAndFavoritesWidget)
fun inject(searchAndFavoritesWidget: SearchAndFavoritesWidget)

fun inject(favoritesWidgetItemFactory: FavoritesWidgetService.FavoritesWidgetItemFactory)
fun inject(favoritesWidgetItemFactory: FavoritesWidgetItemFactory)

fun inject(emptyFavoritesWidgetItemFactory: EmptyFavoritesWidgetService.EmptyFavoritesWidgetItemFactory)
fun inject(emptyFavoritesWidgetItemFactory: EmptyFavoritesWidgetItemFactory)

// accessor to Retrofit instance for test only only for test
@Named("api")
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/com/duckduckgo/app/widget/FavoritesObserver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ package com.duckduckgo.app.widget
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
Expand All @@ -33,7 +33,7 @@ import javax.inject.Inject

@SingleInstanceIn(AppScope::class)
class FavoritesObserver @Inject constructor(
context: Context,
private val context: Context,
private val savedSitesRepository: SavedSitesRepository,
private val dispatcherProvider: DispatcherProvider,
) : MainProcessLifecycleObserver {
Expand All @@ -47,8 +47,13 @@ class FavoritesObserver @Inject constructor(
owner.lifecycle.coroutineScope.launch(dispatcherProvider.io()) {
appWidgetManager?.let { instance ->
savedSitesRepository.getFavorites().collect {
instance.notifyAppWidgetViewDataChanged(instance.getAppWidgetIds(componentName), R.id.favoritesGrid)
instance.notifyAppWidgetViewDataChanged(instance.getAppWidgetIds(componentName), R.id.emptyfavoritesGrid)
val appWidgetIds = instance.getAppWidgetIds(componentName)
if (appWidgetIds.isNotEmpty()) {
val updateIntent = Intent(context, SearchAndFavoritesWidget::class.java)
updateIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
context.sendBroadcast(updateIntent)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2021 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.
*/

package com.duckduckgo.widget

import android.content.Context
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.global.DuckDuckGoApplication
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.savedsites.api.SavedSitesRepository
import kotlinx.coroutines.withContext
import logcat.logcat
import javax.inject.Inject

/**
* This RemoteViewsFactory will not render any item. It's used for convenience to simplify executing background operations to show/hide empty widget CTA.
* If this RemoteViewsFactory count is 0, SearchAndFavoritesWidget R.id.emptyFavoritesGrid will show the configured EmptyView.
*/
class EmptyFavoritesWidgetItemFactory(
val context: Context,
) : RemoteViewsService.RemoteViewsFactory {

@Inject
lateinit var savedSitesRepository: SavedSitesRepository

@Inject
lateinit var dispatchers: DispatcherProvider

private var count = 0

override fun onCreate() {
inject(context)
}

override fun onDataSetChanged() {
// no-op, we use our own update mechanism
}

suspend fun updateEmptyWidgetFavoritesAsync() {
runCatching {
count = getItemsCountFromFavorites()
}.onFailure { error ->
logcat { "Failed to update Search and Favorites widget when empty: ${error.message}" }
}
}

override fun onDestroy() {
// no-op
}

override fun getCount(): Int {
return count
}

override fun getViewAt(position: Int): RemoteViews {
return RemoteViews(context.packageName, R.layout.empty_view)
}

override fun getLoadingView(): RemoteViews {
return RemoteViews(context.packageName, R.layout.empty_view)
}

override fun getViewTypeCount(): Int {
return 1
}

override fun getItemId(position: Int): Long {
return position.toLong()
}

override fun hasStableIds(): Boolean {
return true
}

private suspend fun getItemsCountFromFavorites(): Int {
return withContext(dispatchers.io()) {
if (savedSitesRepository.hasFavorites()) 1 else 0
}
}

private fun inject(context: Context) {
val application = context.applicationContext as DuckDuckGoApplication
application.daggerAppComponent.inject(this)
}
}

This file was deleted.

Loading
Loading