mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
#1774 Widget remote views factory refactor
This commit is contained in:
parent
3e75293c3d
commit
d92e666b9e
13 changed files with 351 additions and 370 deletions
|
@ -1,116 +0,0 @@
|
|||
package com.newsblur.widget;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.activity.AllStoriesItemsList;
|
||||
import com.newsblur.activity.ItemsList;
|
||||
import com.newsblur.activity.WidgetConfig;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.PendingIntentUtils;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.WidgetBackground;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class WidgetProvider extends AppWidgetProvider {
|
||||
|
||||
private static String TAG = "WidgetProvider";
|
||||
|
||||
// Called when the BroadcastReceiver receives an Intent broadcast.
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "onReceive");
|
||||
if (intent.getAction().equals(WidgetUtils.ACTION_OPEN_STORY)) {
|
||||
String storyHash = intent.getStringExtra(WidgetUtils.EXTRA_ITEM_ID);
|
||||
Intent i = new Intent(context, AllStoriesItemsList.class);
|
||||
i.putExtra(ItemsList.EXTRA_FEED_SET, FeedSet.allFeeds());
|
||||
i.putExtra(ItemsList.EXTRA_STORY_HASH, storyHash);
|
||||
i.putExtra(ItemsList.EXTRA_WIDGET_STORY, true);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.getApplicationContext().startActivity(i);
|
||||
} else if (intent.getAction().equals(WidgetUtils.ACTION_OPEN_CONFIG)) {
|
||||
Intent i = new Intent(context, WidgetConfig.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.getApplicationContext().startActivity(i);
|
||||
}
|
||||
super.onReceive(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called to update the App Widget at intervals defined by the updatePeriodMillis attribute
|
||||
*/
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
// update each of the app widgets with the remote adapter
|
||||
Log.d(TAG, "onUpdate");
|
||||
WidgetUtils.checkWidgetUpdateAlarm(context);
|
||||
WidgetBackground widgetBackground = PrefsUtils.getWidgetBackground(context);
|
||||
Set<String> feedIds = PrefsUtils.getWidgetFeedIds(context);
|
||||
for (int appWidgetId : appWidgetIds) {
|
||||
|
||||
// Set up the intent that starts the WidgetRemoteViewService, which will
|
||||
// provide the views for this collection.
|
||||
Intent intent = new Intent(context, WidgetRemoteViewsService.class);
|
||||
// Add the app widget ID to the intent extras.
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
|
||||
|
||||
// Instantiate the RemoteViews object for the app widget layout.
|
||||
WidgetRemoteViews rv = new WidgetRemoteViews(context.getPackageName(), R.layout.view_app_widget);
|
||||
|
||||
if (widgetBackground == WidgetBackground.DEFAULT) {
|
||||
rv.setViewBackgroundColor(R.id.container_widget, ContextCompat.getColor(context, R.color.widget_background));
|
||||
} else if (widgetBackground == WidgetBackground.TRANSPARENT) {
|
||||
rv.setViewBackgroundColor(R.id.container_widget, Color.TRANSPARENT);
|
||||
}
|
||||
// Set up the RemoteViews object to use a RemoteViews adapter.
|
||||
// This adapter connects
|
||||
// to a RemoteViewsService through the specified intent.
|
||||
// This is how you populate the data.
|
||||
rv.setRemoteAdapter(R.id.widget_list, intent);
|
||||
|
||||
// The empty view is displayed when the collection has no items.
|
||||
// It should be in the same layout used to instantiate the RemoteViews
|
||||
// object above.
|
||||
rv.setEmptyView(R.id.widget_list, R.id.widget_empty_view);
|
||||
|
||||
if (feedIds != null && feedIds.isEmpty()) {
|
||||
rv.setTextViewText(R.id.widget_empty_view, context.getString(R.string.title_widget_setup));
|
||||
}
|
||||
|
||||
Intent configIntent = new Intent(context, WidgetProvider.class);
|
||||
configIntent.setAction(WidgetUtils.ACTION_OPEN_CONFIG);
|
||||
PendingIntent configIntentTemplate = PendingIntentUtils.getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_CONFIG, configIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
rv.setOnClickPendingIntent(R.id.widget_empty_view, configIntentTemplate);
|
||||
|
||||
// This section makes it possible for items to have individualized behavior.
|
||||
// It does this by setting up a pending intent template. Individuals items of a collection
|
||||
// cannot set up their own pending intents. Instead, the collection as a whole sets
|
||||
// up a pending intent template, and the individual items set a fillInIntent
|
||||
// to create unique behavior on an item-by-item basis.
|
||||
Intent touchIntent = new Intent(context, WidgetProvider.class);
|
||||
// Set the action for the intent.
|
||||
// When the user touches a particular view, it will have the effect of
|
||||
// broadcasting ACTION_OPEN_STORY.
|
||||
touchIntent.setAction(WidgetUtils.ACTION_OPEN_STORY);
|
||||
touchIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
|
||||
PendingIntent touchIntentTemplate = PendingIntentUtils.getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_STORY, touchIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
rv.setPendingIntentTemplate(R.id.widget_list, touchIntentTemplate);
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, rv);
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list);
|
||||
}
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.newsblur.R
|
||||
import com.newsblur.activity.AllStoriesItemsList
|
||||
import com.newsblur.activity.ItemsList
|
||||
import com.newsblur.activity.WidgetConfig
|
||||
import com.newsblur.util.FeedSet
|
||||
import com.newsblur.util.PendingIntentUtils.getImmutableBroadcast
|
||||
import com.newsblur.util.PrefsUtils
|
||||
import com.newsblur.util.WidgetBackground
|
||||
import com.newsblur.widget.WidgetUtils.checkWidgetUpdateAlarm
|
||||
|
||||
class WidgetProvider : AppWidgetProvider() {
|
||||
|
||||
/**
|
||||
* Called when the BroadcastReceiver receives an Intent broadcast.
|
||||
*/
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Log.d(this.javaClass.name, "onReceive")
|
||||
if (intent.action == WidgetUtils.ACTION_OPEN_STORY) {
|
||||
val storyHash = intent.getStringExtra(WidgetUtils.EXTRA_ITEM_ID)
|
||||
Intent(context, AllStoriesItemsList::class.java).apply {
|
||||
putExtra(ItemsList.EXTRA_FEED_SET, FeedSet.allFeeds())
|
||||
putExtra(ItemsList.EXTRA_STORY_HASH, storyHash)
|
||||
putExtra(ItemsList.EXTRA_WIDGET_STORY, true)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
context.applicationContext.startActivity(it)
|
||||
}
|
||||
} else if (intent.action == WidgetUtils.ACTION_OPEN_CONFIG) {
|
||||
Intent(context, WidgetConfig::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
context.applicationContext.startActivity(it)
|
||||
}
|
||||
}
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called to update the App Widget at intervals defined by the updatePeriodMillis attribute
|
||||
*/
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
// update each of the app widgets with the remote adapter
|
||||
Log.d(this.javaClass.name, "onUpdate")
|
||||
checkWidgetUpdateAlarm(context)
|
||||
val widgetBackground = PrefsUtils.getWidgetBackground(context)
|
||||
val feedIds = PrefsUtils.getWidgetFeedIds(context)
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
|
||||
// Set up the intent that starts the WidgetRemoteViewService, which will
|
||||
// provide the views for this collection.
|
||||
val intent = Intent(context, WidgetRemoteViewsService::class.java).apply {
|
||||
// Add the app widget ID to the intent extras.
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||
}
|
||||
|
||||
// Instantiate the RemoteViews object for the app widget layout.
|
||||
val rv = WidgetRemoteViews(context.packageName, R.layout.view_app_widget)
|
||||
if (widgetBackground == WidgetBackground.DEFAULT) {
|
||||
rv.setViewBackgroundColor(R.id.container_widget, ContextCompat.getColor(context, R.color.widget_background))
|
||||
} else if (widgetBackground == WidgetBackground.TRANSPARENT) {
|
||||
rv.setViewBackgroundColor(R.id.container_widget, Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
// Set up the RemoteViews object to use a RemoteViews adapter.
|
||||
// This adapter connects to a RemoteViewsService through the
|
||||
// specified intent. This is how you populate the data.
|
||||
rv.setRemoteAdapter(R.id.widget_list, intent)
|
||||
|
||||
// The empty view is displayed when the collection has no items.
|
||||
// It should be in the same layout used to instantiate the RemoteViews
|
||||
// object above.
|
||||
rv.setEmptyView(R.id.widget_list, R.id.widget_empty_view)
|
||||
if (feedIds != null && feedIds.isEmpty()) {
|
||||
rv.setTextViewText(R.id.widget_empty_view, context.getString(R.string.title_widget_setup))
|
||||
}
|
||||
|
||||
val configIntent = Intent(context, WidgetProvider::class.java)
|
||||
configIntent.action = WidgetUtils.ACTION_OPEN_CONFIG
|
||||
val configIntentTemplate = getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_CONFIG, configIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
rv.setOnClickPendingIntent(R.id.widget_empty_view, configIntentTemplate)
|
||||
|
||||
// This section makes it possible for items to have individualized behavior.
|
||||
// It does this by setting up a pending intent template. Individuals items of a collection
|
||||
// cannot set up their own pending intents. Instead, the collection as a whole sets
|
||||
// up a pending intent template, and the individual items set a fillInIntent
|
||||
// to create unique behavior on an item-by-item basis.
|
||||
val touchIntent = Intent(context, WidgetProvider::class.java).apply {
|
||||
// Set the action for the intent.
|
||||
// When the user touches a particular view, it will have the effect of
|
||||
// broadcasting ACTION_OPEN_STORY.
|
||||
action = WidgetUtils.ACTION_OPEN_STORY
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
}
|
||||
|
||||
val touchIntentTemplate = getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_STORY, touchIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
rv.setPendingIntentTemplate(R.id.widget_list, touchIntentTemplate)
|
||||
appWidgetManager.updateAppWidget(appWidgetId, rv)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list)
|
||||
}
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
|
||||
super.onDeleted(context, appWidgetIds)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.newsblur.widget;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
class WidgetRemoteViews extends RemoteViews {
|
||||
|
||||
WidgetRemoteViews(String packageName, int layoutId) {
|
||||
super(packageName, layoutId);
|
||||
}
|
||||
|
||||
void setViewBackgroundColor(int viewId, @ColorInt int color) {
|
||||
setInt(viewId, "setBackgroundColor", color);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
internal class WidgetRemoteViews(
|
||||
packageName: String,
|
||||
layoutId: Int,
|
||||
) : RemoteViews(packageName, layoutId) {
|
||||
|
||||
fun setViewBackgroundColor(viewId: Int, @ColorInt color: Int) {
|
||||
setInt(viewId, "setBackgroundColor", color)
|
||||
}
|
||||
}
|
|
@ -12,17 +12,16 @@ import android.widget.RemoteViews
|
|||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import com.newsblur.R
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.di.IconLoader
|
||||
import com.newsblur.di.ThumbnailLoader
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.domain.Story
|
||||
import com.newsblur.network.APIManager
|
||||
import com.newsblur.util.*
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.math.min
|
||||
|
||||
class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFactory {
|
||||
|
@ -32,14 +31,15 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
|||
private val dbHelper: BlurDatabaseHelper
|
||||
private val iconLoader: ImageLoader
|
||||
private val thumbnailLoader: ImageLoader
|
||||
private var fs: FeedSet? = null
|
||||
private val appWidgetId: Int
|
||||
private var dataCompleted = false
|
||||
private val storyItems: MutableList<Story> = ArrayList()
|
||||
|
||||
private val storyItems: MutableList<Story> = mutableListOf()
|
||||
private val cancellationSignal = CancellationSignal()
|
||||
|
||||
private val storiesLock = ReentrantLock()
|
||||
|
||||
init {
|
||||
Log.d(TAG, "Constructor")
|
||||
Log.d(this.javaClass.name, "init")
|
||||
val hiltEntryPoint = EntryPointAccessors
|
||||
.fromApplication(context.applicationContext, WidgetRemoteViewsFactoryEntryPoint::class.java)
|
||||
this.context = context
|
||||
|
@ -54,22 +54,14 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
|||
/**
|
||||
* The system calls onCreate() when creating your factory for the first time.
|
||||
* This is where you set up any connections and/or cursors to your data source.
|
||||
*
|
||||
*
|
||||
* Heavy lifting,
|
||||
* for example downloading or creating content etc, should be deferred to onDataSetChanged()
|
||||
* or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
|
||||
*/
|
||||
override fun onCreate() {
|
||||
Log.d(TAG, "onCreate")
|
||||
Log.d(this.javaClass.name, "onCreate")
|
||||
WidgetUtils.enableWidgetUpdate(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed to run synchronous calls
|
||||
*/
|
||||
override fun getViewAt(position: Int): RemoteViews {
|
||||
Log.d(TAG, "getViewAt $position")
|
||||
override fun getViewAt(position: Int): RemoteViews = storiesLock.withLock {
|
||||
Log.d(this.javaClass.name, "getViewAt $position")
|
||||
val story = storyItems[position]
|
||||
val rv = WidgetRemoteViews(context.packageName, R.layout.view_widget_story_item)
|
||||
rv.setTextViewText(R.id.story_item_title, story.title)
|
||||
|
@ -117,39 +109,53 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
|||
* @param position The position of the item within the data set whose row id we want.
|
||||
* @return The id of the item at the specified position.
|
||||
*/
|
||||
override fun getItemId(position: Int): Long = storyItems[position].hashCode().toLong()
|
||||
override fun getItemId(position: Int): Long = storiesLock.withLock {
|
||||
storyItems[position].hashCode().toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the same id always refers to the same object.
|
||||
*/
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
Log.d(TAG, "onDataSetChanged")
|
||||
/**
|
||||
* Heavy lifting like downloading or creating content etc, should be deferred to onDataSetChanged()
|
||||
*/
|
||||
override fun onDataSetChanged() = storiesLock.withLock {
|
||||
Log.d(this.javaClass.name, "onDataSetChanged")
|
||||
// if user logged out don't try to update widget
|
||||
if (!WidgetUtils.isLoggedIn(context)) {
|
||||
Log.d(TAG, "onDataSetChanged - not logged in")
|
||||
return
|
||||
Log.d(this.javaClass.name, "onDataSetChanged - not logged in")
|
||||
return@withLock
|
||||
}
|
||||
if (dataCompleted) {
|
||||
// we have all the stories data, just let the widget redraw
|
||||
Log.d(TAG, "onDataSetChanged - redraw widget")
|
||||
dataCompleted = false
|
||||
} else {
|
||||
setFeedSet()
|
||||
if (fs == null) {
|
||||
Log.d(TAG, "onDataSetChanged - null feed set. Show empty view")
|
||||
setStories(arrayOf(), HashMap(0))
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "onDataSetChanged - fetch stories")
|
||||
val response = apiManager.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
|
||||
if (response?.stories == null) {
|
||||
Log.d(TAG, "Error fetching widget stories")
|
||||
} else {
|
||||
Log.d(TAG, "Fetched widget stories")
|
||||
processStories(response.stories)
|
||||
dbHelper.insertStories(response, true)
|
||||
|
||||
// get fs based on pref widget feed ids
|
||||
val feedIds = PrefsUtils.getWidgetFeedIds(context)
|
||||
val fs = if (feedIds == null || feedIds.isNotEmpty()) {
|
||||
// null feed ids get all feeds
|
||||
FeedSet.widgetFeeds(feedIds)
|
||||
} else null // intentionally no feeds selected.
|
||||
|
||||
if (fs == null) {
|
||||
Log.d(this.javaClass.name, "onDataSetChanged - null fs cleared stories")
|
||||
storyItems.clear()
|
||||
return@withLock
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
try {
|
||||
// Taking more than 20 seconds in this method will result in an ANR.
|
||||
withTimeout(18000) {
|
||||
Log.d(this.javaClass.name, "onDataSetChanged - get remote stories")
|
||||
val response = apiManager.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
|
||||
response.stories?.let {
|
||||
Log.d(this.javaClass.name, "onDataSetChanged - got ${it.size} remote stories")
|
||||
processStories(response.stories)
|
||||
dbHelper.insertStories(response, true)
|
||||
} ?: Log.d(this.javaClass.name, "onDataSetChanged - null remote stories")
|
||||
}
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
Log.d(this.javaClass.name, "onDataSetChanged - timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +165,7 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
|||
* unbound.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "onDestroy")
|
||||
Log.d(this.javaClass.name, "onDestroy")
|
||||
cancellationSignal.cancel()
|
||||
WidgetUtils.disableWidgetUpdate(context)
|
||||
PrefsUtils.removeWidgetData(context)
|
||||
|
@ -168,81 +174,39 @@ class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFa
|
|||
/**
|
||||
* @return Count of items.
|
||||
*/
|
||||
override fun getCount(): Int = min(storyItems.size, WidgetUtils.STORIES_LIMIT)
|
||||
|
||||
private fun processStories(stories: Array<Story>) {
|
||||
Log.d(TAG, "processStories")
|
||||
val feedMap = HashMap<String, Feed>()
|
||||
NBScope.executeAsyncTask(
|
||||
doInBackground = {
|
||||
dbHelper.getFeedsCursor(cancellationSignal)
|
||||
},
|
||||
onPostExecute = {
|
||||
while (it != null && it.moveToNext()) {
|
||||
val feed = Feed.fromCursor(it)
|
||||
if (feed.active) {
|
||||
feedMap[feed.feedId] = feed
|
||||
}
|
||||
}
|
||||
setStories(stories, feedMap)
|
||||
}
|
||||
)
|
||||
override fun getCount(): Int = storiesLock.withLock {
|
||||
min(storyItems.size, WidgetUtils.STORIES_LIMIT)
|
||||
}
|
||||
|
||||
private fun setStories(stories: Array<Story>, feedMap: HashMap<String, Feed>) {
|
||||
Log.d(TAG, "setStories")
|
||||
/**
|
||||
* Widget will show tap to config view when
|
||||
* empty stories and feeds maps are used
|
||||
*/
|
||||
private fun processStories(stories: Array<Story>) = storiesLock.withLock {
|
||||
Log.d(this.javaClass.name, "processStories")
|
||||
val feedMap = mutableMapOf<String, Feed>()
|
||||
val cursor = dbHelper.getFeedsCursor(cancellationSignal)
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
val feed = Feed.fromCursor(cursor)
|
||||
if (feed.active) {
|
||||
feedMap[feed.feedId] = feed
|
||||
}
|
||||
}
|
||||
|
||||
for (story in stories) {
|
||||
val storyFeed = feedMap[story.feedId]
|
||||
storyFeed?.let { bindStoryValues(story, it) }
|
||||
}
|
||||
storyItems.clear()
|
||||
storyItems.addAll(mutableListOf(*stories))
|
||||
// we have the data, notify data set changed
|
||||
dataCompleted = true
|
||||
invalidate()
|
||||
storyItems.addAll(stories.toList())
|
||||
}
|
||||
|
||||
private fun bindStoryValues(story: Story, feed: Feed) {
|
||||
story.thumbnailUrl = Story.guessStoryThumbnailURL(story)
|
||||
story.extern_faviconBorderColor = feed.faviconBorder
|
||||
story.extern_faviconUrl = feed.faviconUrl
|
||||
story.extern_feedTitle = feed.title
|
||||
story.extern_feedFade = feed.faviconFade
|
||||
story.extern_feedColor = feed.faviconColor
|
||||
}
|
||||
|
||||
private fun invalidate() {
|
||||
Log.d(TAG, "Invalidate app widget with id: $appWidgetId")
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list)
|
||||
}
|
||||
|
||||
private fun setFeedSet() {
|
||||
val feedIds = PrefsUtils.getWidgetFeedIds(context)
|
||||
fs = if (feedIds == null || feedIds.isNotEmpty()) {
|
||||
FeedSet.widgetFeeds(feedIds)
|
||||
} else {
|
||||
// no feeds selected. Widget will show tap to config view
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WidgetRemoteViewsFactory"
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface WidgetRemoteViewsFactoryEntryPoint {
|
||||
|
||||
fun apiManager(): APIManager
|
||||
|
||||
fun dbHelper(): BlurDatabaseHelper
|
||||
|
||||
@IconLoader
|
||||
fun iconLoader(): ImageLoader
|
||||
|
||||
@ThumbnailLoader
|
||||
fun thumbnailLoader(): ImageLoader
|
||||
private fun bindStoryValues(story: Story, feed: Feed) = story.apply {
|
||||
thumbnailUrl = Story.guessStoryThumbnailURL(story)
|
||||
extern_faviconBorderColor = feed.faviconBorder
|
||||
extern_faviconUrl = feed.faviconUrl
|
||||
extern_feedTitle = feed.title
|
||||
extern_feedFade = feed.faviconFade
|
||||
extern_feedColor = feed.faviconColor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.di.IconLoader
|
||||
import com.newsblur.di.ThumbnailLoader
|
||||
import com.newsblur.network.APIManager
|
||||
import com.newsblur.util.ImageLoader
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface WidgetRemoteViewsFactoryEntryPoint {
|
||||
|
||||
fun apiManager(): APIManager
|
||||
|
||||
fun dbHelper(): BlurDatabaseHelper
|
||||
|
||||
@IconLoader
|
||||
fun iconLoader(): ImageLoader
|
||||
|
||||
@ThumbnailLoader
|
||||
fun thumbnailLoader(): ImageLoader
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.newsblur.widget;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.widget.RemoteViewsService;
|
||||
|
||||
import com.newsblur.util.Log;
|
||||
|
||||
public class WidgetRemoteViewsService extends RemoteViewsService {
|
||||
|
||||
private static String TAG = "WidgetRemoteViewsFactory";
|
||||
|
||||
@Override
|
||||
public RemoteViewsService.RemoteViewsFactory onGetViewFactory(Intent intent) {
|
||||
Log.d(TAG, "onGetViewFactory");
|
||||
return new WidgetRemoteViewsFactory(this.getApplicationContext(), intent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
import com.newsblur.util.Log
|
||||
|
||||
class WidgetRemoteViewsService : RemoteViewsService() {
|
||||
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
|
||||
WidgetRemoteViewsFactory(this.applicationContext, intent).also {
|
||||
Log.d("WidgetRemoteViewsFactory", "onGetViewFactory")
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.newsblur.widget;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.util.Log;
|
||||
|
||||
public class WidgetUpdateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, @Nullable Intent intent) {
|
||||
if (intent != null && intent.getAction() != null &&
|
||||
intent.getAction().equals(WidgetUtils.ACTION_UPDATE_WIDGET)) {
|
||||
Log.d(this.getClass().getName(), "Received " + WidgetUtils.ACTION_UPDATE_WIDGET);
|
||||
AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = widgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
|
||||
widgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.newsblur.R
|
||||
import com.newsblur.util.Log
|
||||
|
||||
class WidgetUpdateReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
if (intent != null && intent.action != null && intent.action == WidgetUtils.ACTION_UPDATE_WIDGET) {
|
||||
Log.d(this.javaClass.name, "Received ${WidgetUtils.ACTION_UPDATE_WIDGET}")
|
||||
val widgetManager = AppWidgetManager.getInstance(context)
|
||||
val appWidgetIds = widgetManager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java))
|
||||
widgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package com.newsblur.widget;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.newsblur.util.PendingIntentUtils;
|
||||
import com.newsblur.util.Log;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
|
||||
public class WidgetUtils {
|
||||
|
||||
private static String TAG = "WidgetUtils";
|
||||
|
||||
public static String ACTION_UPDATE_WIDGET = "ACTION_UPDATE_WIDGET";
|
||||
public static String ACTION_OPEN_STORY = "ACTION_OPEN_STORY";
|
||||
public static String ACTION_OPEN_CONFIG = "ACTION_OPEN_CONFIG";
|
||||
public static String EXTRA_ITEM_ID = "EXTRA_ITEM_ID";
|
||||
public static int RC_WIDGET_UPDATE = 1;
|
||||
public static int RC_WIDGET_STORY = 2;
|
||||
public static int RC_WIDGET_CONFIG = 3;
|
||||
public static int STORIES_LIMIT = 5;
|
||||
|
||||
static void enableWidgetUpdate(Context context) {
|
||||
Log.d(TAG, "enableWidgetUpdate");
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = getUpdateIntent(context);
|
||||
PendingIntent pendingIntent = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
int widgetUpdateInterval = 1000 * 60 * 5;
|
||||
long startAlarmAt = SystemClock.currentThreadTimeMillis() + widgetUpdateInterval;
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC, startAlarmAt, widgetUpdateInterval, pendingIntent);
|
||||
}
|
||||
|
||||
public static void disableWidgetUpdate(Context context) {
|
||||
Log.d(TAG, "disableWidgetUpdate");
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
alarmManager.cancel(pendingIntent);
|
||||
}
|
||||
|
||||
public static void resetWidgetUpdate(Context context) {
|
||||
if (hasActiveAppWidgets(context)) {
|
||||
WidgetUtils.enableWidgetUpdate(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasActiveAppWidgets(Context context) {
|
||||
AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = widgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
|
||||
return appWidgetIds.length > 0;
|
||||
}
|
||||
|
||||
public static boolean isLoggedIn(Context context) {
|
||||
return PrefsUtils.getUniqueLoginKey(context) != null;
|
||||
}
|
||||
|
||||
public static void updateWidget(Context context) {
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
|
||||
Intent intent = new Intent(context, WidgetProvider.class);
|
||||
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static void checkWidgetUpdateAlarm(Context context) {
|
||||
boolean hasActiveUpdates = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_NO_CREATE) != null;
|
||||
if (!hasActiveUpdates) {
|
||||
enableWidgetUpdate(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent getUpdateIntent(Context context) {
|
||||
Intent intent = new Intent(context, WidgetUpdateReceiver.class);
|
||||
intent.setAction(ACTION_UPDATE_WIDGET);
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.newsblur.widget
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.SystemClock
|
||||
import com.newsblur.util.Log
|
||||
import com.newsblur.util.PendingIntentUtils.getImmutableBroadcast
|
||||
import com.newsblur.util.PrefsUtils
|
||||
|
||||
object WidgetUtils {
|
||||
|
||||
private const val RC_WIDGET_UPDATE = 1
|
||||
|
||||
const val ACTION_UPDATE_WIDGET = "ACTION_UPDATE_WIDGET"
|
||||
const val ACTION_OPEN_STORY = "ACTION_OPEN_STORY"
|
||||
const val ACTION_OPEN_CONFIG = "ACTION_OPEN_CONFIG"
|
||||
const val EXTRA_ITEM_ID = "EXTRA_ITEM_ID"
|
||||
|
||||
const val RC_WIDGET_STORY = 2
|
||||
const val RC_WIDGET_CONFIG = 3
|
||||
const val STORIES_LIMIT = 5
|
||||
|
||||
fun enableWidgetUpdate(context: Context) {
|
||||
Log.d(this.javaClass.name, "enableWidgetUpdate")
|
||||
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||
val intent = getUpdateIntent(context)
|
||||
val pendingIntent = getImmutableBroadcast(context, RC_WIDGET_UPDATE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val widgetUpdateInterval = 1000 * 60 * 5
|
||||
val startAlarmAt = SystemClock.currentThreadTimeMillis() + widgetUpdateInterval
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC, startAlarmAt, widgetUpdateInterval.toLong(), pendingIntent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun disableWidgetUpdate(context: Context) {
|
||||
Log.d(this.javaClass.name, "disableWidgetUpdate")
|
||||
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||
val pendingIntent = getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
alarmManager.cancel(pendingIntent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun resetWidgetUpdate(context: Context) {
|
||||
if (hasActiveAppWidgets(context)) {
|
||||
enableWidgetUpdate(context)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun hasActiveAppWidgets(context: Context): Boolean {
|
||||
val widgetManager = AppWidgetManager.getInstance(context)
|
||||
val appWidgetIds = widgetManager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java))
|
||||
return appWidgetIds.isNotEmpty()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun updateWidget(context: Context) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java))
|
||||
val intent = Intent(context, WidgetProvider::class.java)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun checkWidgetUpdateAlarm(context: Context) {
|
||||
val hasActiveUpdates = getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_NO_CREATE) != null
|
||||
if (!hasActiveUpdates) {
|
||||
enableWidgetUpdate(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun isLoggedIn(context: Context): Boolean = PrefsUtils.getUniqueLoginKey(context) != null
|
||||
|
||||
private fun getUpdateIntent(context: Context) = Intent(context, WidgetUpdateReceiver::class.java).apply {
|
||||
action = ACTION_UPDATE_WIDGET
|
||||
}
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=obsolete
|
Loading…
Add table
Reference in a new issue