Merge branch 'sictiru'

* sictiru:
  Android v11.1.1.
  Update dependencies
  Cleanup. Use compat methods
  #1572 Web view custom contextual web search action
  Adding gradle wrapper properties.
  Adding gradle wrapper.
  NBScope for global scope coroutines
  Update android gradle and kotlin dependencies
  Cleanup NbActivity and remove static reference to all activities. Add broadcast receiver based sync mechanism between foreground activities and db updates by the background service.
  Check intent action in the receiver.
  #1556 Android 12 Splash Screen
  Android v11.1 (cleaner than 11.1.1 since 11.1 isn't even public yet)
This commit is contained in:
Samuel Clay 2021-12-08 12:48:24 -05:00
commit ca0672220a
40 changed files with 528 additions and 432 deletions

2
.gitignore vendored
View file

@ -83,7 +83,7 @@ media/safari/NewsBlur.safariextz
clients/android/NewsBlur/.idea clients/android/NewsBlur/.idea
clients/android/NewsBlur/.gradle clients/android/NewsBlur/.gradle
clients/android/NewsBlur/build.gradle clients/android/NewsBlur/build.gradle
clients/android/NewsBlur/gradle* clients/android/NewsBlur/gradlew*
clients/android/NewsBlur/settings.gradle clients/android/NewsBlur/settings.gradle
/docker/volumes/* /docker/volumes/*

View file

@ -14,6 +14,5 @@ libs/ActionBarSherlock/
.project .project
.gradle/ .gradle/
app/ app/
gradle/
*.gradle *.gradle
!build.gradle !build.gradle

View file

@ -12,13 +12,15 @@
android:label="@string/newsblur" android:label="@string/newsblur"
android:theme="@style/NewsBlurTheme" android:theme="@style/NewsBlurTheme"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:fullBackupContent="@xml/backupscheme" > android:fullBackupContent="@xml/backupscheme"
android:name=".NbApplication">
<activity <activity
android:name=".activity.InitActivity" android:name=".activity.InitActivity"
android:label="@string/newsblur" android:label="@string/newsblur"
android:theme="@style/initStyle" android:theme="@style/splashScreen"
android:noHistory="true"> android:noHistory="true"
android:exported="true">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -146,7 +148,8 @@
<service android:name=".widget.WidgetRemoteViewsService" <service android:name=".widget.WidgetRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name=".service.BootReceiver"> <receiver android:name=".service.BootReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
@ -163,7 +166,8 @@
<meta-data android:name="android.appwidget.provider" <meta-data android:name="android.appwidget.provider"
android:resource="@xml/newsblur_appwidget_info" /> android:resource="@xml/newsblur_appwidget_info" />
</receiver> </receiver>
<receiver android:name=".service.TimeChangeReceiver"> <receiver android:name=".service.TimeChangeReceiver"
android:exported="true">
<intent-filter > <intent-filter >
<action android:name="android.intent.action.TIME_SET"/> <action android:name="android.intent.action.TIME_SET"/>
</intent-filter> </intent-filter>
@ -183,7 +187,8 @@
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
<activity android:name=".activity.AddFeedExternal"> <activity android:name=".activity.AddFeedExternal"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -214,6 +219,8 @@
<data android:host="feeds2.feedburner.com"/> <data android:host="feeds2.feedburner.com"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<data android:scheme="http"/>
<data android:scheme="https"/>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />

View file

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.5.30' ext.kotlin_version = '1.6.0'
repositories { repositories {
mavenCentral() mavenCentral()
maven { maven {
@ -9,7 +9,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.0' classpath 'com.android.tools.build:gradle:7.0.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -27,28 +27,29 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.fragment:fragment-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.squareup.okhttp3:okhttp:4.9.2'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.android.billingclient:billing:3.0.3' implementation 'com.android.billingclient:billing:3.0.3'
implementation 'nl.dionsegijn:konfetti:1.2.2' implementation 'nl.dionsegijn:konfetti:1.2.2'
implementation 'com.google.android.play:core:1.10.0' implementation 'com.google.android.play:core:1.10.2'
implementation "com.google.android.material:material:1.3.0" implementation "com.google.android.material:material:1.4.0"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.browser:browser:1.3.0" implementation "androidx.browser:browser:1.4.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
defaultConfig { defaultConfig {
applicationId "com.newsblur" applicationId "com.newsblur"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 31
versionCode 196 versionCode 198
versionName "11.1.1" versionName "11.1.1"
} }
compileOptions.with { compileOptions.with {

Binary file not shown.

View file

@ -0,0 +1,8 @@
#Wed Aug 11 15:59:29 EDT 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
android.enableJetifier=true
android.useAndroidX=true

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="44dp"
android:drawable="@drawable/logo"
android:left="44dp"
android:right="44dp"
android:top="44dp" />
</layer-list>
<!--This is a workaround until they fix the icon padding-->

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:src="@drawable/logo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:layout_centerInParent="true"
/>
</RelativeLayout>

View file

@ -163,6 +163,7 @@
<string name="menu_rename_feed">Rename feed</string> <string name="menu_rename_feed">Rename feed</string>
<string name="menu_story_list_style_choose">List style…</string> <string name="menu_story_list_style_choose">List style…</string>
<string name="menu_save_search">Save Search</string> <string name="menu_save_search">Save Search</string>
<string name="menu_web_search">Web search</string>
<string name="list_style_list">List</string> <string name="list_style_list">List</string>
<string name="list_style_grid_f">Grid (fine)</string> <string name="list_style_grid_f">Grid (fine)</string>
<string name="list_style_grid_m">Grid (medium)</string> <string name="list_style_grid_m">Grid (medium)</string>
@ -650,4 +651,6 @@
<string name="story_notification_channel_id">story_notification_channel</string> <string name="story_notification_channel_id">story_notification_channel</string>
<string name="story_notification_channel_name">New Stories</string> <string name="story_notification_channel_name">New Stories</string>
<string name="go_to_feed">Go to feed</string> <string name="go_to_feed">Go to feed</string>
<string name="js_get_selection">(function(){return window.getSelection().toString()})()</string>
</resources> </resources>

View file

@ -11,10 +11,10 @@
<item name="android:letterSpacing">0</item> <item name="android:letterSpacing">0</item>
</style> </style>
<style name="initStyle" parent="Theme.MaterialComponents.Light.DarkActionBar.Bridge"> <style name="splashScreen" parent="Theme.SplashScreen">
<item name="android:windowBackground">@color/transparent</item> <item name="windowSplashScreenBackground">@color/black</item>
<item name="windowActionBar">false</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash_screen</item>
<item name="windowNoTitle">true</item> <item name="postSplashScreenTheme">@style/NewsBlurTheme</item>
</style> </style>
<style name="actionbar" parent="ThemeOverlay.MaterialComponents.Light"> <style name="actionbar" parent="ThemeOverlay.MaterialComponents.Light">

View file

@ -0,0 +1,30 @@
package com.newsblur
import android.app.Application
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
class NbApplication : Application(), DefaultLifecycleObserver {
override fun onCreate() {
super<Application>.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
isAppForeground = true
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
isAppForeground = false
}
companion object {
@JvmStatic
var isAppForeground = false
}
}

View file

@ -3,6 +3,8 @@ package com.newsblur.activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import android.view.View; import android.view.View;
@ -25,10 +27,10 @@ public class AddFeedExternal extends NbActivity implements AddFeedFragment.AddFe
UIUtils.setupToolbar(this, R.drawable.logo, "Add Feed", true); UIUtils.setupToolbar(this, R.drawable.logo, "Add Feed", true);
binding.loadingThrob.setEnabled(!ViewUtils.isPowerSaveMode(this)); binding.loadingThrob.setEnabled(!ViewUtils.isPowerSaveMode(this));
binding.loadingThrob.setColors(UIUtils.getColor(this, R.color.refresh_1), binding.loadingThrob.setColors(ContextCompat.getColor(this, R.color.refresh_1),
UIUtils.getColor(this, R.color.refresh_2), ContextCompat.getColor(this, R.color.refresh_2),
UIUtils.getColor(this, R.color.refresh_3), ContextCompat.getColor(this, R.color.refresh_3),
UIUtils.getColor(this, R.color.refresh_4)); ContextCompat.getColor(this, R.color.refresh_4));
Intent intent = getIntent(); Intent intent = getIntent();
Uri uri = intent.getData(); Uri uri = intent.getData();

View file

@ -3,8 +3,8 @@ package com.newsblur.activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.newsblur.R
import com.newsblur.util.* import com.newsblur.util.*
/** /**
@ -13,17 +13,18 @@ import com.newsblur.util.*
* DB connection used by all other Activities. * DB connection used by all other Activities.
*/ */
class InitActivity : AppCompatActivity() { class InitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_init) installSplashScreen().also {
it.setKeepVisibleCondition {
// do actual app launch after just a moment so the init screen smoothly loads // keep showing the splash screen until FeedUtils.offerInitContext(...)
lifecycleScope.executeAsyncTask( // finishes and UI ready to display
doInBackground = { FeedUtils.dbHelper != null || FeedUtils.thumbnailLoader != null
start() }
} }
)
lifecycleScope.executeAsyncTask(doInBackground = { start() })
Log.i(this, "cold launching version " + PrefsUtils.getVersion(this)) Log.i(this, "cold launching version " + PrefsUtils.getVersion(this))
} }

View file

@ -1,5 +1,9 @@
package com.newsblur.activity; package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.os.Bundle; import android.os.Bundle;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;

View file

@ -1,5 +1,10 @@
package com.newsblur.activity; package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_DB_READY;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;

View file

@ -1,5 +1,7 @@
package com.newsblur.activity; package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.text.TextUtils; import android.text.TextUtils;

View file

@ -1,174 +0,0 @@
package com.newsblur.activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.PrefConstants.ThemeValue;
import com.newsblur.util.UIUtils;
import java.util.ArrayList;
/**
* The base class for all Activities in the NewsBlur app. Handles enforcement of
* login state and tracking of sync/update broadcasts.
*/
public class NbActivity extends AppCompatActivity {
public static final int UPDATE_DB_READY = (1<<0);
public static final int UPDATE_METADATA = (1<<1);
public static final int UPDATE_STORY = (1<<2);
public static final int UPDATE_SOCIAL = (1<<3);
public static final int UPDATE_INTEL = (1<<4);
public static final int UPDATE_STATUS = (1<<5);
public static final int UPDATE_TEXT = (1<<6);
public static final int UPDATE_REBUILD = (1<<7);
private final static String UNIQUE_LOGIN_KEY = "uniqueLoginKey";
private String uniqueLoginKey;
private ThemeValue lastTheme = null;
/**
* Keep track of all activie activities so they can be notified when the sync service
* has updated the DB. This is essentially an ultra-lightweight implementation of a
* local, unfiltered broadcast manager.
*/
private static ArrayList<NbActivity> AllActivities = new ArrayList<NbActivity>();
@Override
protected void onCreate(Bundle bundle) {
com.newsblur.util.Log.offerContext(this);
com.newsblur.util.Log.d(this, "onCreate");
// this is not redundant to the applyThemePreference() call in onResume. the theme needs to be set
// before onCreate() in order to work
PrefsUtils.applyThemePreference(this);
lastTheme = PrefsUtils.getSelectedTheme(this);
// in rare cases of process interruption or DB corruption, an activity can launch without valid
// login creds. redirect the user back to the loging workflow.
if (PrefsUtils.getUserId(this) == null) {
com.newsblur.util.Log.e(this, "post-login activity launched without valid login.");
PrefsUtils.logout(this);
finish();
}
super.onCreate(bundle);
FeedUtils.offerInitContext(this);
if (bundle != null) {
uniqueLoginKey = bundle.getString(UNIQUE_LOGIN_KEY);
}
if (uniqueLoginKey == null) {
uniqueLoginKey = PrefsUtils.getUniqueLoginKey(this);
}
finishIfNotLoggedIn();
}
@Override
protected void onResume() {
com.newsblur.util.Log.d(this, "onResume" + UIUtils.getMemoryUsageDebug(this));
super.onResume();
finishIfNotLoggedIn();
// is is possible that another activity changed the theme while we were on the backstack
if (lastTheme != PrefsUtils.getSelectedTheme(this)) {
lastTheme = PrefsUtils.getSelectedTheme(this);
PrefsUtils.applyThemePreference(this);
UIUtils.restartActivity(this);
}
synchronized (AllActivities) {
AllActivities.add(this);
}
}
@Override
protected void onPause() {
com.newsblur.util.Log.d(this.getClass().getName(), "onPause");
super.onPause();
synchronized (AllActivities) {
AllActivities.remove(this);
}
}
protected void finishIfNotLoggedIn() {
String currentLoginKey = PrefsUtils.getUniqueLoginKey(this);
if(currentLoginKey == null || !currentLoginKey.equals(uniqueLoginKey)) {
com.newsblur.util.Log.d(this.getClass().getName(), "This activity was for a different login. finishing it.");
finish();
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
com.newsblur.util.Log.d(this, "onSave");
savedInstanceState.putString(UNIQUE_LOGIN_KEY, uniqueLoginKey);
super.onSaveInstanceState(savedInstanceState);
}
/**
* Pokes the sync service to perform any pending sync actions.
*/
protected void triggerSync() {
FeedUtils.triggerSync(this);
}
/**
* Called on each NB activity after the DB has been updated by the sync service.
*
* @param updateType one or more of the UPDATE_* flags in this class to indicate the
* type of update being broadcast.
*/
protected void handleUpdate(int updateType) {
com.newsblur.util.Log.w(this, "activity doesn't implement handleUpdate");
}
private void _handleUpdate(final int updateType) {
runOnUiThread(new Runnable() {
public void run() {
handleUpdate(updateType);
}
});
}
/**
* Notify all activities in the app that the DB has been updated. Should only be called
* by the sync service, which owns updating the DB.
*/
public static void updateAllActivities(int updateType) {
synchronized (AllActivities) {
for (NbActivity activity : AllActivities) {
activity._handleUpdate(updateType);
}
}
}
public static void toastError(final String message) {
synchronized (AllActivities) {
for (final NbActivity activity : AllActivities) {
activity.runOnUiThread(new Runnable() {
public void run() {
UIUtils.safeToast(activity, message, Toast.LENGTH_SHORT);
}
});
}
}
}
/**
* Gets the number of active/foreground NB activities. Used by the sync service to
* determine if the app is active so we can honour user requests not to run in
* the background.
*/
public static int getActiveActivityCount() {
return AllActivities.size();
}
}

View file

@ -0,0 +1,116 @@
package com.newsblur.activity
import android.content.IntentFilter
import com.newsblur.util.FeedUtils.offerInitContext
import com.newsblur.util.FeedUtils.triggerSync
import androidx.appcompat.app.AppCompatActivity
import com.newsblur.util.PrefConstants.ThemeValue
import android.os.Bundle
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.service.NBSyncReceiver
import com.newsblur.util.Log
/**
* The base class for all Activities in the NewsBlur app. Handles enforcement of
* login state and tracking of sync/update broadcasts.
*/
open class NbActivity : AppCompatActivity() {
private var uniqueLoginKey: String? = null
private var lastTheme: ThemeValue? = null
// Facilitates the db updates by the sync service on the UI
private val serviceSyncReceiver = object : NBSyncReceiver() {
override fun handleUpdateType(updateType: Int) {
runOnUiThread { handleUpdate(updateType) }
}
}
override fun onCreate(bundle: Bundle?) {
Log.offerContext(this)
Log.d(this, "onCreate")
// this is not redundant to the applyThemePreference() call in onResume. the theme needs to be set
// before onCreate() in order to work
PrefsUtils.applyThemePreference(this)
lastTheme = PrefsUtils.getSelectedTheme(this)
// in rare cases of process interruption or DB corruption, an activity can launch without valid
// login creds. redirect the user back to the loging workflow.
if (PrefsUtils.getUserId(this) == null) {
Log.e(this, "post-login activity launched without valid login.")
PrefsUtils.logout(this)
finish()
}
super.onCreate(bundle)
offerInitContext(this)
bundle?.let {
uniqueLoginKey = it.getString(UNIQUE_LOGIN_KEY)
}
if (uniqueLoginKey == null) {
uniqueLoginKey = PrefsUtils.getUniqueLoginKey(this)
}
finishIfNotLoggedIn()
}
override fun onResume() {
Log.d(this, "onResume" + UIUtils.getMemoryUsageDebug(this))
super.onResume()
finishIfNotLoggedIn()
// is is possible that another activity changed the theme while we were on the backstack
val currentSelectedTheme = PrefsUtils.getSelectedTheme(this)
if (lastTheme != currentSelectedTheme) {
lastTheme = currentSelectedTheme
PrefsUtils.applyThemePreference(this)
UIUtils.restartActivity(this)
}
registerReceiver(serviceSyncReceiver, IntentFilter().apply {
addAction(NBSyncReceiver.NB_SYNC_ACTION)
})
}
override fun onPause() {
Log.d(this.javaClass.name, "onPause")
super.onPause()
unregisterReceiver(serviceSyncReceiver)
}
private fun finishIfNotLoggedIn() {
val currentLoginKey = PrefsUtils.getUniqueLoginKey(this)
if (currentLoginKey == null || currentLoginKey != uniqueLoginKey) {
Log.d(this.javaClass.name, "This activity was for a different login. finishing it.")
finish()
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
Log.d(this, "onSave")
savedInstanceState.putString(UNIQUE_LOGIN_KEY, uniqueLoginKey)
super.onSaveInstanceState(savedInstanceState)
}
/**
* Pokes the sync service to perform any pending sync actions.
*/
protected fun triggerSync() {
triggerSync(this)
}
/**
* Called on each NB activity after the DB has been updated by the sync service.
*
* @param updateType one or more of the UPDATE_* flags in this class to indicate the
* type of update being broadcast.
*/
protected open fun handleUpdate(updateType: Int) {
Log.w(this, "activity doesn't implement handleUpdate")
}
}
private const val UNIQUE_LOGIN_KEY = "uniqueLoginKey"

View file

@ -24,6 +24,9 @@ import com.newsblur.databinding.ActivityReadingBinding
import com.newsblur.domain.Story import com.newsblur.domain.Story
import com.newsblur.fragment.ReadingItemFragment import com.newsblur.fragment.ReadingItemFragment
import com.newsblur.fragment.ReadingPagerFragment import com.newsblur.fragment.ReadingPagerFragment
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_REBUILD
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STATUS
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import com.newsblur.service.NBSyncService import com.newsblur.service.NBSyncService
import com.newsblur.util.* import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue import com.newsblur.util.PrefConstants.ThemeValue

View file

@ -24,6 +24,7 @@ import com.newsblur.network.domain.CommentResponse;
import com.newsblur.network.domain.StoriesResponse; import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.util.AppConstants; import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet; import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils; import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadingAction; import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter; import com.newsblur.util.ReadFilter;
@ -1641,6 +1642,10 @@ public class BlurDatabaseHelper {
try {c.close();} catch (Exception e) {;} try {c.close();} catch (Exception e) {;}
} }
public void sendSyncUpdate(int updateType) {
FeedUtils.syncUpdateStatus(context, updateType);
}
private static String conjoinSelections(CharSequence... args) { private static String conjoinSelections(CharSequence... args) {
StringBuilder s = null; StringBuilder s = null;
for (CharSequence c : args) { for (CharSequence c : args) {
@ -1674,5 +1679,4 @@ public class BlurDatabaseHelper {
private Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) { private Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
return dbRO.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal); return dbRO.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal);
} }
} }

View file

@ -1,5 +1,7 @@
package com.newsblur.database; package com.newsblur.database;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
@ -10,7 +12,6 @@ import androidx.viewpager.widget.PagerAdapter;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.Reading; import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier; import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story; import com.newsblur.domain.Story;
@ -283,7 +284,7 @@ public class ReadingAdapter extends PagerAdapter {
ReadingItemFragment rif = fragments.get(s.storyHash); ReadingItemFragment rif = fragments.get(s.storyHash);
if (rif != null ) { if (rif != null ) {
rif.offerStoryUpdate(s); rif.offerStoryUpdate(s);
rif.handleUpdate(NbActivity.UPDATE_STORY); rif.handleUpdate(UPDATE_STORY);
} }
} }
} }

View file

@ -2,11 +2,9 @@ package com.newsblur.fragment;
import com.newsblur.R; import com.newsblur.R;
import com.newsblur.activity.ItemsList; import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.domain.Feed; import com.newsblur.domain.Feed;
import com.newsblur.domain.SavedSearch; import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed; import com.newsblur.domain.SocialFeed;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils; import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils; import com.newsblur.util.UIUtils;
@ -75,18 +73,17 @@ public class DeleteFeedFragment extends DialogFragment {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) { if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) {
FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), new APIManager(getActivity())); FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), getActivity());
} else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) { } else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) {
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), new APIManager(getActivity())); FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity());
} else { } else {
FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), new APIManager(getActivity())); FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity());
} }
// if called from a feed view, end it // if called from a feed view, end it
Activity activity = DeleteFeedFragment.this.getActivity(); Activity activity = DeleteFeedFragment.this.getActivity();
if (activity instanceof ItemsList) { if (activity instanceof ItemsList) {
activity.finish(); activity.finish();
} }
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
DeleteFeedFragment.this.dismiss(); DeleteFeedFragment.this.dismiss();
} }
}); });

View file

@ -10,6 +10,7 @@ import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
@ -202,7 +203,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
View v = inflater.inflate(R.layout.fragment_folderfeedlist, container); View v = inflater.inflate(R.layout.fragment_folderfeedlist, container);
binding = FragmentFolderfeedlistBinding.bind(v); binding = FragmentFolderfeedlistBinding.bind(v);
binding.folderfeedList.setGroupIndicator(UIUtils.getDrawable(getActivity(), R.drawable.transparent)); binding.folderfeedList.setGroupIndicator(ContextCompat.getDrawable(requireContext(), R.drawable.transparent));
binding.folderfeedList.setOnCreateContextMenuListener(this); binding.folderfeedList.setOnCreateContextMenuListener(this);
binding.folderfeedList.setOnChildClickListener(this); binding.folderfeedList.setOnChildClickListener(this);
binding.folderfeedList.setOnGroupClickListener(this); binding.folderfeedList.setOnGroupClickListener(this);

View file

@ -7,6 +7,7 @@ import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
@ -121,21 +122,19 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
fleuronBinding = RowFleuronBinding.bind(fleuronView); fleuronBinding = RowFleuronBinding.bind(fleuronView);
// disable the throbbers if animations are going to have a zero time scale // disable the throbbers if animations are going to have a zero time scale
boolean isDisableAnimations = ViewUtils.isPowerSaveMode(getActivity()); boolean isDisableAnimations = ViewUtils.isPowerSaveMode(requireContext());
int[] colorsArray = {ContextCompat.getColor(requireContext(), R.color.refresh_1),
ContextCompat.getColor(requireContext(), R.color.refresh_2),
ContextCompat.getColor(requireContext(), R.color.refresh_3),
ContextCompat.getColor(requireContext(), R.color.refresh_4)};
binding.topLoadingThrob.setEnabled(!isDisableAnimations); binding.topLoadingThrob.setEnabled(!isDisableAnimations);
binding.topLoadingThrob.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1), binding.topLoadingThrob.setColors(colorsArray);
UIUtils.getColor(getActivity(), R.color.refresh_2),
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
View footerView = inflater.inflate(R.layout.row_loading_throbber, null); View footerView = inflater.inflate(R.layout.row_loading_throbber, null);
bottomProgressView = (ProgressThrobber) footerView.findViewById(R.id.itemlist_loading_throb); bottomProgressView = (ProgressThrobber) footerView.findViewById(R.id.itemlist_loading_throb);
bottomProgressView.setEnabled(!isDisableAnimations); bottomProgressView.setEnabled(!isDisableAnimations);
bottomProgressView.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1), bottomProgressView.setColors(colorsArray);
UIUtils.getColor(getActivity(), R.color.refresh_2),
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
fleuronBinding.getRoot().setVisibility(View.INVISIBLE); fleuronBinding.getRoot().setVisibility(View.INVISIBLE);
fleuronBinding.containerSubscribe.setOnClickListener(view -> UIUtils.startPremiumActivity(requireContext())); fleuronBinding.containerSubscribe.setOnClickListener(view -> UIUtils.startPremiumActivity(requireContext()));

View file

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import android.widget.AdapterView.OnItemClickListener import android.widget.AdapterView.OnItemClickListener
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.newsblur.R import com.newsblur.R
@ -38,19 +39,17 @@ abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_profileactivity, null) val view = inflater.inflate(R.layout.fragment_profileactivity, null)
binding = FragmentProfileactivityBinding.bind(view) binding = FragmentProfileactivityBinding.bind(view)
binding.emptyViewLoadingThrob.setColors(UIUtils.getColor(activity, R.color.refresh_1), val colorsArray = intArrayOf(ContextCompat.getColor(requireContext(), R.color.refresh_1),
UIUtils.getColor(activity, R.color.refresh_2), ContextCompat.getColor(requireContext(), R.color.refresh_2),
UIUtils.getColor(activity, R.color.refresh_3), ContextCompat.getColor(requireContext(), R.color.refresh_3),
UIUtils.getColor(activity, R.color.refresh_4)) ContextCompat.getColor(requireContext(), R.color.refresh_4))
binding.emptyViewLoadingThrob.setColors(*colorsArray)
binding.profileDetailsActivitylist.setFooterDividersEnabled(false) binding.profileDetailsActivitylist.setFooterDividersEnabled(false)
binding.profileDetailsActivitylist.emptyView = binding.emptyView binding.profileDetailsActivitylist.emptyView = binding.emptyView
val footerView = inflater.inflate(R.layout.row_loading_throbber, null) val footerView = inflater.inflate(R.layout.row_loading_throbber, null)
footerBinding = RowLoadingThrobberBinding.bind(footerView) footerBinding = RowLoadingThrobberBinding.bind(footerView)
footerBinding.itemlistLoadingThrob.setColors(UIUtils.getColor(activity, R.color.refresh_1), footerBinding.itemlistLoadingThrob.setColors(*colorsArray)
UIUtils.getColor(activity, R.color.refresh_2),
UIUtils.getColor(activity, R.color.refresh_3),
UIUtils.getColor(activity, R.color.refresh_4))
binding.profileDetailsActivitylist.addFooterView(footerView, null, false) binding.profileDetailsActivitylist.addFooterView(footerView, null, false)
if (adapter != null) { if (adapter != null) {
displayActivities() displayActivities()

View file

@ -19,7 +19,6 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.newsblur.R import com.newsblur.R
import com.newsblur.activity.FeedItemsList import com.newsblur.activity.FeedItemsList
import com.newsblur.activity.NbActivity
import com.newsblur.activity.Reading import com.newsblur.activity.Reading
import com.newsblur.databinding.FragmentReadingitemBinding import com.newsblur.databinding.FragmentReadingitemBinding
import com.newsblur.databinding.IncludeReadingItemCommentBinding import com.newsblur.databinding.IncludeReadingItemCommentBinding
@ -28,6 +27,10 @@ import com.newsblur.domain.Story
import com.newsblur.domain.UserDetails import com.newsblur.domain.UserDetails
import com.newsblur.fragment.StoryUserTagsFragment.Companion.newInstance import com.newsblur.fragment.StoryUserTagsFragment.Companion.newInstance
import com.newsblur.network.APIManager import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_INTEL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_TEXT
import com.newsblur.service.OriginalTextService import com.newsblur.service.OriginalTextService
import com.newsblur.util.* import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue import com.newsblur.util.PrefConstants.ThemeValue
@ -380,11 +383,11 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
binding.itemFeedBorder.setBackgroundColor(Color.parseColor("#$feedBorder")) binding.itemFeedBorder.setBackgroundColor(Color.parseColor("#$feedBorder"))
if (faviconText == "black") { if (faviconText == "black") {
binding.readingFeedTitle.setTextColor(UIUtils.getColor(requireContext(), R.color.text)) binding.readingFeedTitle.setTextColor(ContextCompat.getColor(requireContext(), R.color.text))
binding.readingFeedTitle.setShadowLayer(1f, 0f, 1f, UIUtils.getColor(requireContext(), R.color.half_white)) binding.readingFeedTitle.setShadowLayer(1f, 0f, 1f, ContextCompat.getColor(requireContext(), R.color.half_white))
} else { } else {
binding.readingFeedTitle.setTextColor(UIUtils.getColor(requireContext(), R.color.white)) binding.readingFeedTitle.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
binding.readingFeedTitle.setShadowLayer(1f, 0f, 1f, UIUtils.getColor(requireContext(), R.color.half_black)) binding.readingFeedTitle.setShadowLayer(1f, 0f, 1f, ContextCompat.getColor(requireContext(), R.color.half_black))
} }
if (!displayFeedDetails) { if (!displayFeedDetails) {
binding.readingFeedTitle.visibility = View.GONE binding.readingFeedTitle.visibility = View.GONE
@ -497,8 +500,8 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
binding.readingItemAuthors.text = "" + story!!.authors binding.readingItemAuthors.text = "" + story!!.authors
if (classifier != null && classifier!!.authors.containsKey(story!!.authors)) { if (classifier != null && classifier!!.authors.containsKey(story!!.authors)) {
when (classifier!!.authors[story!!.authors]) { when (classifier!!.authors[story!!.authors]) {
Classifier.LIKE -> binding.readingItemAuthors.setTextColor(UIUtils.getColor(requireContext(), R.color.positive)) Classifier.LIKE -> binding.readingItemAuthors.setTextColor(ContextCompat.getColor(requireContext(), R.color.positive))
Classifier.DISLIKE -> binding.readingItemAuthors.setTextColor(UIUtils.getColor(requireContext(), R.color.negative)) Classifier.DISLIKE -> binding.readingItemAuthors.setTextColor(ContextCompat.getColor(requireContext(), R.color.negative))
else -> binding.readingItemAuthors.setTextColor(UIUtils.getThemedColor(requireContext(), R.attr.readingItemMetadata, android.R.attr.textColor)) else -> binding.readingItemAuthors.setTextColor(UIUtils.getThemedColor(requireContext(), R.attr.readingItemMetadata, android.R.attr.textColor))
} }
} }
@ -598,19 +601,19 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
} }
fun handleUpdate(updateType: Int) { fun handleUpdate(updateType: Int) {
if (updateType and NbActivity.UPDATE_STORY != 0) { if (updateType and UPDATE_STORY != 0) {
updateSaveButton() updateSaveButton()
updateShareButton() updateShareButton()
setupItemCommentsAndShares() setupItemCommentsAndShares()
} }
if (updateType and NbActivity.UPDATE_TEXT != 0) { if (updateType and UPDATE_TEXT != 0) {
reloadStoryContent() reloadStoryContent()
} }
if (updateType and NbActivity.UPDATE_SOCIAL != 0) { if (updateType and UPDATE_SOCIAL != 0) {
updateShareButton() updateShareButton()
setupItemCommentsAndShares() setupItemCommentsAndShares()
} }
if (updateType and NbActivity.UPDATE_INTEL != 0) { if (updateType and UPDATE_INTEL != 0) {
classifier = FeedUtils.dbHelper!!.getClassifierForFeed(story!!.feedId) classifier = FeedUtils.dbHelper!!.getClassifierForFeed(story!!.feedId)
setupTagsAndIntel() setupTagsAndIntel()
} }

View file

@ -14,6 +14,7 @@ public class NewsBlurResponse {
public String message; public String message;
public String[] errors; public String[] errors;
public long readTime; public long readTime;
public int impactCode;
public boolean isError() { public boolean isError() {
if (isProtocolError) return true; if (isProtocolError) return true;

View file

@ -7,6 +7,8 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import androidx.annotation.Nullable;
import com.newsblur.util.AppConstants; import com.newsblur.util.AppConstants;
import com.newsblur.widget.WidgetUtils; import com.newsblur.widget.WidgetUtils;
@ -17,10 +19,12 @@ import com.newsblur.widget.WidgetUtils;
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, @Nullable Intent intent) {
com.newsblur.util.Log.d(this, "triggering sync service from device boot"); if (intent != null && intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
scheduleSyncService(context); com.newsblur.util.Log.d(this, "triggering sync service from device boot");
resetWidgetSync(context); scheduleSyncService(context);
resetWidgetSync(context);
}
} }
public static void scheduleSyncService(Context context) { public static void scheduleSyncService(Context context) {

View file

@ -0,0 +1,31 @@
package com.newsblur.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class NBSyncReceiver : BroadcastReceiver() {
abstract fun handleUpdateType(updateType: Int)
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == NB_SYNC_ACTION) {
handleUpdateType(intent.getIntExtra(NB_SYNC_UPDATE_TYPE, 0))
}
}
companion object {
const val NB_SYNC_ACTION = "nb.sync.action"
const val NB_SYNC_ERROR_MESSAGE = "nb_sync_error_msg"
const val NB_SYNC_UPDATE_TYPE = "nb_sync_update_type"
const val UPDATE_DB_READY = 1 shl 0
const val UPDATE_METADATA = 1 shl 1
const val UPDATE_STORY = 1 shl 2
const val UPDATE_SOCIAL = 1 shl 3
const val UPDATE_INTEL = 1 shl 4
const val UPDATE_STATUS = 1 shl 5
const val UPDATE_TEXT = 1 shl 6
const val UPDATE_REBUILD = 1 shl 7
}
}

View file

@ -9,10 +9,18 @@ import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Process; import android.os.Process;
import com.newsblur.NbApplication;
import com.newsblur.R; import com.newsblur.R;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper; import com.newsblur.database.BlurDatabaseHelper;
import static com.newsblur.database.BlurDatabaseHelper.closeQuietly; import static com.newsblur.database.BlurDatabaseHelper.closeQuietly;
import static com.newsblur.service.NBSyncReceiver.UPDATE_DB_READY;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import androidx.annotation.NonNull;
import com.newsblur.database.DatabaseConstants; import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Feed; import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder; import com.newsblur.domain.Folder;
@ -182,7 +190,7 @@ public class NBSyncService extends JobService {
public int onStartCommand(Intent intent, int flags, final int startId) { public int onStartCommand(Intent intent, int flags, final int startId) {
com.newsblur.util.Log.d(this, "onStartCommand"); com.newsblur.util.Log.d(this, "onStartCommand");
// only perform a sync if the app is actually running or background syncs are enabled // only perform a sync if the app is actually running or background syncs are enabled
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) { if (NbApplication.isAppForeground() || PrefsUtils.isBackgroundNeeded(this)) {
HaltNow = false; HaltNow = false;
// Services actually get invoked on the main system thread, and are not // Services actually get invoked on the main system thread, and are not
// allowed to do tangible work. We spawn a thread to do so. // allowed to do tangible work. We spawn a thread to do so.
@ -215,7 +223,7 @@ public class NBSyncService extends JobService {
public boolean onStartJob(final JobParameters params) { public boolean onStartJob(final JobParameters params) {
com.newsblur.util.Log.d(this, "onStartJob"); com.newsblur.util.Log.d(this, "onStartJob");
// only perform a sync if the app is actually running or background syncs are enabled // only perform a sync if the app is actually running or background syncs are enabled
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) { if (NbApplication.isAppForeground() || PrefsUtils.isBackgroundNeeded(this)) {
HaltNow = false; HaltNow = false;
// Services actually get invoked on the main system thread, and are not // Services actually get invoked on the main system thread, and are not
// allowed to do tangible work. We spawn a thread to do so. // allowed to do tangible work. We spawn a thread to do so.
@ -260,7 +268,7 @@ public class NBSyncService extends JobService {
Log.d(this, "starting primary sync"); Log.d(this, "starting primary sync");
if (NbActivity.getActiveActivityCount() < 1) { if (!NbApplication.isAppForeground()) {
// if the UI isn't running, politely run at background priority // if the UI isn't running, politely run at background priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
} else { } else {
@ -273,7 +281,7 @@ public class NBSyncService extends JobService {
if (OfflineNow) { if (OfflineNow) {
if (NetworkUtils.isOnline(this)) { if (NetworkUtils.isOnline(this)) {
OfflineNow = false; OfflineNow = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
} else { } else {
com.newsblur.util.Log.d(this, "Abandoning sync: network still offline"); com.newsblur.util.Log.d(this, "Abandoning sync: network still offline");
return; return;
@ -285,7 +293,7 @@ public class NBSyncService extends JobService {
housekeeping(); housekeeping();
// check to see if we are on an allowable network only after ensuring we have CPU // check to see if we are on an allowable network only after ensuring we have CPU
if (!( (NbActivity.getActiveActivityCount() > 0) || if (!( NbApplication.isAppForeground() ||
PrefsUtils.isEnableNotifications(this) || PrefsUtils.isEnableNotifications(this) ||
PrefsUtils.isBackgroundNetworkAllowed(this) || PrefsUtils.isBackgroundNetworkAllowed(this) ||
WidgetUtils.hasActiveAppWidgets(this)) ) { WidgetUtils.hasActiveAppWidgets(this)) ) {
@ -294,7 +302,7 @@ public class NBSyncService extends JobService {
} }
// ping activities to indicate that housekeeping is done, and the DB is safe to use // ping activities to indicate that housekeeping is done, and the DB is safe to use
NbActivity.updateAllActivities(NbActivity.UPDATE_DB_READY); sendSyncUpdate(UPDATE_DB_READY);
// async text requests might have been queued up and are being waiting on by the live UI. give them priority // async text requests might have been queued up and are being waiting on by the live UI. give them priority
originalTextService.start(); originalTextService.start();
@ -337,10 +345,10 @@ public class NBSyncService extends JobService {
boolean upgraded = PrefsUtils.checkForUpgrade(this); boolean upgraded = PrefsUtils.checkForUpgrade(this);
if (upgraded) { if (upgraded) {
HousekeepingRunning = true; HousekeepingRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS | NbActivity.UPDATE_REBUILD); sendSyncUpdate(UPDATE_STATUS | UPDATE_REBUILD);
// wipe the local DB if this is a first background run. if this is a first foreground // wipe the local DB if this is a first background run. if this is a first foreground
// run, InitActivity will have wiped for us // run, InitActivity will have wiped for us
if (NbActivity.getActiveActivityCount() < 1) { if (!NbApplication.isAppForeground()) {
dbHelper.dropAndRecreateTables(); dbHelper.dropAndRecreateTables();
} }
// in case this is the first time we have run since moving the cache to the new location, // in case this is the first time we have run since moving the cache to the new location,
@ -353,11 +361,11 @@ public class NBSyncService extends JobService {
boolean autoVac = PrefsUtils.isTimeToVacuum(this); boolean autoVac = PrefsUtils.isTimeToVacuum(this);
// this will lock up the DB for a few seconds, only do it if the UI is hidden // this will lock up the DB for a few seconds, only do it if the UI is hidden
if (NbActivity.getActiveActivityCount() > 0) autoVac = false; if (NbApplication.isAppForeground()) autoVac = false;
if (upgraded || autoVac) { if (upgraded || autoVac) {
HousekeepingRunning = true; HousekeepingRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
com.newsblur.util.Log.i(this.getClass().getName(), "rebuilding DB . . ."); com.newsblur.util.Log.i(this.getClass().getName(), "rebuilding DB . . .");
dbHelper.vacuum(); dbHelper.vacuum();
com.newsblur.util.Log.i(this.getClass().getName(), ". . . . done rebuilding DB"); com.newsblur.util.Log.i(this.getClass().getName(), ". . . . done rebuilding DB");
@ -366,7 +374,7 @@ public class NBSyncService extends JobService {
} finally { } finally {
if (HousekeepingRunning) { if (HousekeepingRunning) {
HousekeepingRunning = false; HousekeepingRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA); sendSyncUpdate(UPDATE_METADATA);
} }
} }
} }
@ -387,7 +395,7 @@ public class NBSyncService extends JobService {
ActionsRunning = true; ActionsRunning = true;
actionsloop : while (c.moveToNext()) { actionsloop : while (c.moveToNext()) {
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID)); String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
ReadingAction ra; ReadingAction ra;
try { try {
@ -419,18 +427,19 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "Discarding reading action with fatal message."); com.newsblur.util.Log.i(this.getClass().getName(), "Discarding reading action with fatal message.");
dbHelper.clearAction(id); dbHelper.clearAction(id);
String message = response.getErrorMessage(null); String message = response.getErrorMessage(null);
if (message != null) NbActivity.toastError(message); if (message != null) sendToastError(message);
} else { } else {
// success! // success!
dbHelper.clearAction(id); dbHelper.clearAction(id);
FollowupActions.add(ra); FollowupActions.add(ra);
sendSyncUpdate(response.impactCode);
} }
lastActionCount--; lastActionCount--;
} }
} finally { } finally {
closeQuietly(c); closeQuietly(c);
ActionsRunning = false; ActionsRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
} }
} }
@ -448,7 +457,7 @@ public class NBSyncService extends JobService {
int impact = ra.doLocal(dbHelper, true); int impact = ra.doLocal(dbHelper, true);
impactFlags |= impact; impactFlags |= impact;
} }
NbActivity.updateAllActivities(impactFlags); sendSyncUpdate(impactFlags);
// if there is a feed fetch loop running, don't clear, there will likely be races for // if there is a feed fetch loop running, don't clear, there will likely be races for
// stories that were just tapped as they were being re-fetched // stories that were just tapped as they were being re-fetched
@ -484,7 +493,7 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "ready to sync feed list"); com.newsblur.util.Log.i(this.getClass().getName(), "ready to sync feed list");
FFSyncRunning = true; FFSyncRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
// there is an issue with feeds that have no folder or folders that list feeds that do not exist. capture them for workarounds. // there is an issue with feeds that have no folder or folders that list feeds that do not exist. capture them for workarounds.
Set<String> debugFeedIdsFromFolders = new HashSet<String>(); Set<String> debugFeedIdsFromFolders = new HashSet<String>();
@ -618,7 +627,7 @@ public class NBSyncService extends JobService {
} finally { } finally {
FFSyncRunning = false; FFSyncRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_METADATA | UPDATE_STATUS);
} }
} }
@ -633,7 +642,7 @@ public class NBSyncService extends JobService {
if (RecountCandidates.size() < 1) return; if (RecountCandidates.size() < 1) return;
RecountsRunning = true; RecountsRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
// of all candidate feeds that were touched, now check to see if any // of all candidate feeds that were touched, now check to see if any
// actually need their counts fetched // actually need their counts fetched
@ -699,7 +708,7 @@ public class NBSyncService extends JobService {
} finally { } finally {
if (RecountsRunning) { if (RecountsRunning) {
RecountsRunning = false; RecountsRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_METADATA | UPDATE_STATUS);
} }
FlushRecounts = false; FlushRecounts = false;
} }
@ -760,7 +769,7 @@ public class NBSyncService extends JobService {
ReadFilter filter = PrefsUtils.getReadFilter(this, fs); ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
StorySyncRunning = true; StorySyncRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
while (totalStoriesSeen < PendingFeedTarget) { while (totalStoriesSeen < PendingFeedTarget) {
if (stopSync()) return; if (stopSync()) return;
@ -784,7 +793,7 @@ public class NBSyncService extends JobService {
insertStories(apiResponse, fs); insertStories(apiResponse, fs);
// re-do any very recent actions that were incorrectly overwritten by this page // re-do any very recent actions that were incorrectly overwritten by this page
finishActions(); finishActions();
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
prefetchOriginalText(apiResponse); prefetchOriginalText(apiResponse);
@ -804,7 +813,7 @@ public class NBSyncService extends JobService {
} finally { } finally {
StorySyncRunning = false; StorySyncRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); sendSyncUpdate(UPDATE_STATUS);
synchronized (PENDING_FEED_MUTEX) { synchronized (PENDING_FEED_MUTEX) {
if (finished && fs.equals(PendingFeed)) PendingFeed = null; if (finished && fs.equals(PendingFeed)) PendingFeed = null;
} }
@ -996,7 +1005,7 @@ public class NBSyncService extends JobService {
} }
private boolean backoffBackgroundCalls() { private boolean backoffBackgroundCalls() {
if (NbActivity.getActiveActivityCount() > 0) return false; if (NbApplication.isAppForeground()) return false;
if (System.currentTimeMillis() > (lastAPIFailure + AppConstants.API_BACKGROUND_BACKOFF_MILLIS)) return false; if (System.currentTimeMillis() > (lastAPIFailure + AppConstants.API_BACKGROUND_BACKOFF_MILLIS)) return false;
com.newsblur.util.Log.i(this.getClass().getName(), "abandoning background sync due to recent API failures."); com.newsblur.util.Log.i(this.getClass().getName(), "abandoning background sync due to recent API failures.");
return true; return true;
@ -1128,7 +1137,7 @@ public class NBSyncService extends JobService {
dbHelper.prepareReadingSession(fs); dbHelper.prepareReadingSession(fs);
// note which feedset we are loading so we can trigger another reset when it changes // note which feedset we are loading so we can trigger another reset when it changes
dbHelper.setSessionFeedSet(fs); dbHelper.setSessionFeedSet(fs);
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS); dbHelper.sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
} }
} }
} }
@ -1241,4 +1250,21 @@ public class NBSyncService extends JobService {
return s.toString(); return s.toString();
} }
protected void sendSyncUpdate(int update) {
Intent i = new Intent(NBSyncReceiver.NB_SYNC_ACTION);
i.putExtra(NBSyncReceiver.NB_SYNC_UPDATE_TYPE, update);
broadcastSync(i);
}
protected void sendToastError(@NonNull String message) {
Intent i = new Intent(NBSyncReceiver.NB_SYNC_ACTION);
i.putExtra(NBSyncReceiver.NB_SYNC_ERROR_MESSAGE, message);
broadcastSync(i);
}
private void broadcastSync(@NonNull Intent intent) {
if (NbApplication.isAppForeground()) {
sendBroadcast(intent);
}
}
} }

View file

@ -1,6 +1,7 @@
package com.newsblur.service; package com.newsblur.service;
import com.newsblur.activity.NbActivity; import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
import com.newsblur.database.DatabaseConstants; import com.newsblur.database.DatabaseConstants;
import com.newsblur.network.domain.StoryTextResponse; import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.util.AppConstants; import com.newsblur.util.AppConstants;
@ -84,7 +85,7 @@ public class OriginalTextService extends SubService {
} }
} }
} finally { } finally {
gotData(NbActivity.UPDATE_TEXT); parent.sendSyncUpdate(UPDATE_TEXT);
hashes.removeAll(fetchedHashes); hashes.removeAll(fetchedHashes);
} }
} }

View file

@ -1,8 +1,10 @@
package com.newsblur.service; package com.newsblur.service;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.os.Process; import android.os.Process;
import com.newsblur.activity.NbActivity; import com.newsblur.NbApplication;
import com.newsblur.util.AppConstants; import com.newsblur.util.AppConstants;
import com.newsblur.util.Log; import com.newsblur.util.Log;
@ -36,7 +38,7 @@ public abstract class SubService {
Runnable r = new Runnable() { Runnable r = new Runnable() {
public void run() { public void run() {
if (parent.stopSync()) return; if (parent.stopSync()) return;
if (NbActivity.getActiveActivityCount() < 1) { if (!NbApplication.isAppForeground()) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE ); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
} else { } else {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE ); Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE );
@ -52,7 +54,7 @@ public abstract class SubService {
executor.execute(new Runnable() { executor.execute(new Runnable() {
public void run() { public void run() {
parent.checkCompletion(); parent.checkCompletion();
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS); parent.sendSyncUpdate(UPDATE_STATUS);
} }
}); });
} catch (RejectedExecutionException ree) { } catch (RejectedExecutionException ree) {
@ -85,10 +87,6 @@ public abstract class SubService {
} }
} }
protected void gotData(int updateType) {
NbActivity.updateAllActivities(updateType);
}
public boolean isRunning() { public boolean isRunning() {
// don't advise completion until there are no tasks, or just one check task left // don't advise completion until there are no tasks, or just one check task left
return (executor.getQueue().size() > 0); return (executor.getQueue().size() > 0);
@ -115,7 +113,7 @@ public abstract class SubService {
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0); long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS; if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
if (NbActivity.getActiveActivityCount() > 0 ) { if (NbApplication.isAppForeground()) {
com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle."); com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
try { try {
Thread.sleep(cooloffTimeMs); Thread.sleep(cooloffTimeMs);

View file

@ -1,16 +1,23 @@
package com.newsblur.util package com.newsblur.util
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch private const val TAG = "NBScope"
import kotlinx.coroutines.withContext
fun <R> CoroutineScope.executeAsyncTask( fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit = { }, onPreExecute: () -> Unit = { },
doInBackground: () -> R, doInBackground: () -> R,
onPostExecute: (R) -> Unit = { }) = onPostExecute: (R) -> Unit = { }) =
launch { launch {
onPreExecute() onPreExecute()
val result = withContext(Dispatchers.IO) { doInBackground() } val result = withContext(Dispatchers.IO) { doInBackground() }
onPostExecute(result) onPostExecute(result)
} }
val NBScope = CoroutineScope(
CoroutineName(TAG) +
Dispatchers.Default +
SupervisorJob() + // children coroutines won't stop parent if they cancel or error
CoroutineExceptionHandler { context, throwable ->
Log.e(TAG, "Coroutine exception on context $context with $throwable")
})

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import com.newsblur.NbApplication
import com.newsblur.R import com.newsblur.R
import com.newsblur.activity.NbActivity import com.newsblur.activity.NbActivity
import com.newsblur.database.BlurDatabaseHelper import com.newsblur.database.BlurDatabaseHelper
@ -12,7 +13,10 @@ import com.newsblur.fragment.ReadingActionConfirmationFragment
import com.newsblur.network.APIConstants import com.newsblur.network.APIConstants
import com.newsblur.network.APIManager import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncService import com.newsblur.service.NBSyncService
import kotlinx.coroutines.GlobalScope import com.newsblur.service.NBSyncReceiver
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_METADATA
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import java.util.* import java.util.*
object FeedUtils { object FeedUtils {
@ -70,7 +74,7 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun prepareReadingSession(fs: FeedSet?, resetFirst: Boolean) { fun prepareReadingSession(fs: FeedSet?, resetFirst: Boolean) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
try { try {
if (resetFirst) NBSyncService.resetReadingSession(dbHelper) if (resetFirst) NBSyncService.resetReadingSession(dbHelper)
@ -96,11 +100,11 @@ object FeedUtils {
} }
private fun setStorySaved(storyHash: String?, saved: Boolean, context: Context, userTags: List<String?>?) { private fun setStorySaved(storyHash: String?, saved: Boolean, context: Context, userTags: List<String?>?) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash) val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY) syncUpdateStatus(context, UPDATE_STORY)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
triggerSync(context) triggerSync(context)
} }
@ -108,15 +112,15 @@ object FeedUtils {
} }
@JvmStatic @JvmStatic
fun deleteSavedSearch(feedId: String?, query: String?, apiManager: APIManager) { fun deleteSavedSearch(feedId: String?, query: String?, context: Context) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.deleteSearch(feedId, query) APIManager(context).deleteSearch(feedId, query)
}, },
onPostExecute = { newsBlurResponse -> onPostExecute = { newsBlurResponse ->
if (!newsBlurResponse.isError) { if (!newsBlurResponse.isError) {
dbHelper!!.deleteSavedSearch(feedId, query) dbHelper!!.deleteSavedSearch(feedId, query)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA) syncUpdateStatus(context, UPDATE_METADATA)
} }
} }
) )
@ -124,7 +128,7 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun saveSearch(feedId: String?, query: String?, context: Context, apiManager: APIManager) { fun saveSearch(feedId: String?, query: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.saveSearch(feedId, query) apiManager.saveSearch(feedId, query)
}, },
@ -138,36 +142,36 @@ object FeedUtils {
} }
@JvmStatic @JvmStatic
fun deleteFeed(feedId: String?, folderName: String?, apiManager: APIManager) { fun deleteFeed(feedId: String?, folderName: String?, context: Context) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.deleteFeed(feedId, folderName) APIManager(context).deleteFeed(feedId, folderName)
}, },
onPostExecute = { onPostExecute = {
// TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check // TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check
dbHelper!!.deleteFeed(feedId) dbHelper!!.deleteFeed(feedId)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA) syncUpdateStatus(context, UPDATE_METADATA)
} }
) )
} }
@JvmStatic @JvmStatic
fun deleteSocialFeed(userId: String?, apiManager: APIManager) { fun deleteSocialFeed(userId: String?, context: Context) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.unfollowUser(userId) APIManager(context).unfollowUser(userId)
}, },
onPostExecute = { onPostExecute = {
// TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check // TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check
dbHelper!!.deleteSocialFeed(userId) dbHelper!!.deleteSocialFeed(userId)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA) syncUpdateStatus(context, UPDATE_METADATA)
} }
) )
} }
@JvmStatic @JvmStatic
fun deleteFolder(folderName: String?, inFolder: String?, context: Context, apiManager: APIManager) { fun deleteFolder(folderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.deleteFolder(folderName, inFolder) apiManager.deleteFolder(folderName, inFolder)
}, },
@ -189,7 +193,7 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun renameFolder(folderName: String?, newFolderName: String?, inFolder: String?, context: Context, apiManager: APIManager) { fun renameFolder(folderName: String?, newFolderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
apiManager.renameFolder(folderName, newFolderName, inFolder) apiManager.renameFolder(folderName, newFolderName, inFolder)
}, },
@ -204,7 +208,7 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun markStoryUnread(story: Story, context: Context) { fun markStoryUnread(story: Story, context: Context) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
setStoryReadState(story, context, false) setStoryReadState(story, context, false)
} }
@ -213,7 +217,7 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun markStoryAsRead(story: Story, context: Context) { fun markStoryAsRead(story: Story, context: Context) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
setStoryReadState(story, context, true) setStoryReadState(story, context, true)
} }
@ -238,7 +242,7 @@ object FeedUtils {
// update unread state and unread counts in the local DB // update unread state and unread counts in the local DB
val impactedFeeds = dbHelper!!.setStoryReadState(story, read) val impactedFeeds = dbHelper!!.setStoryReadState(story, read)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY) syncUpdateStatus(context, UPDATE_STORY)
NBSyncService.addRecountCandidates(impactedFeeds) NBSyncService.addRecountCandidates(impactedFeeds)
triggerSync(context) triggerSync(context)
@ -351,7 +355,7 @@ object FeedUtils {
} }
private fun updateFeedNotifications(context: Context, feed: Feed, enable: Boolean, focusOnly: Boolean) { private fun updateFeedNotifications(context: Context, feed: Feed, enable: Boolean, focusOnly: Boolean) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
if (focusOnly) { if (focusOnly) {
feed.setNotifyFocus() feed.setNotifyFocus()
@ -369,11 +373,11 @@ object FeedUtils {
@JvmStatic @JvmStatic
fun doAction(ra: ReadingAction?, context: Context) { fun doAction(ra: ReadingAction?, context: Context) {
requireNotNull(ra) { "ReadingAction must not be null" } requireNotNull(ra) { "ReadingAction must not be null" }
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper) val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact) syncUpdateStatus(context, impact)
triggerSync(context) triggerSync(context)
} }
) )
@ -418,7 +422,7 @@ object FeedUtils {
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment) val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY) syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context) triggerSync(context)
} }
@ -427,7 +431,7 @@ object FeedUtils {
val ra = ReadingAction.renameFeed(feedId, newFeedName) val ra = ReadingAction.renameFeed(feedId, newFeedName)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper) val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact) syncUpdateStatus(context, impact)
triggerSync(context) triggerSync(context)
} }
@ -436,7 +440,7 @@ object FeedUtils {
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId) val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY) syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context) triggerSync(context)
} }
@ -444,7 +448,7 @@ object FeedUtils {
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId) val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL) syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context) triggerSync(context)
} }
@ -452,7 +456,7 @@ object FeedUtils {
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId) val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL) syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context) triggerSync(context)
} }
@ -461,7 +465,7 @@ object FeedUtils {
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText) val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL) syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context) triggerSync(context)
} }
@ -470,7 +474,7 @@ object FeedUtils {
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText) val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL) syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context) triggerSync(context)
} }
@ -479,14 +483,14 @@ object FeedUtils {
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId) val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL) syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context) triggerSync(context)
} }
@JvmStatic @JvmStatic
fun moveFeedToFolders(context: Context, feedId: String?, toFolders: Set<String?>, inFolders: Set<String?>?) { fun moveFeedToFolders(context: Context, feedId: String?, toFolders: Set<String?>, inFolders: Set<String?>?) {
if (toFolders.isEmpty()) return if (toFolders.isEmpty()) return
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
val apiManager = APIManager(context) val apiManager = APIManager(context)
apiManager.moveFeedToFolders(feedId, toFolders, inFolders) apiManager.moveFeedToFolders(feedId, toFolders, inFolders)
@ -509,7 +513,7 @@ object FeedUtils {
} }
private fun updateFeedActiveState(context: Context, feedIds: Set<String>, active: Boolean) { private fun updateFeedActiveState(context: Context, feedIds: Set<String>, active: Boolean) {
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
val activeFeeds = dbHelper!!.allActiveFeeds val activeFeeds = dbHelper!!.allActiveFeeds
for (feedId in feedIds) { for (feedId in feedIds) {
@ -529,7 +533,7 @@ object FeedUtils {
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA) syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context) triggerSync(context)
} }
) )
@ -540,7 +544,7 @@ object FeedUtils {
val ra = ReadingAction.instaFetch(feedId) val ra = ReadingAction.instaFetch(feedId)
dbHelper!!.enqueueAction(ra) dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper) ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA) syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context) triggerSync(context)
} }
@ -593,4 +597,15 @@ object FeedUtils {
val url = APIConstants.buildUrl(APIConstants.PATH_FEED_STATISTICS + feedId) val url = APIConstants.buildUrl(APIConstants.PATH_FEED_STATISTICS + feedId)
UIUtils.handleUri(context, Uri.parse(url)) UIUtils.handleUri(context, Uri.parse(url))
} }
@JvmStatic
fun syncUpdateStatus(context: Context, updateType: Int) {
if (NbApplication.isAppForeground) {
Intent(NBSyncReceiver.NB_SYNC_ACTION).apply {
putExtra(NBSyncReceiver.NB_SYNC_UPDATE_TYPE, updateType)
}.also {
context.sendBroadcast(it)
}
}
}
} }

View file

@ -4,13 +4,12 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.newsblur.activity.Reading import com.newsblur.activity.Reading
import kotlinx.coroutines.GlobalScope
class NotifyDismissReceiver : BroadcastReceiver() { class NotifyDismissReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) { override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH) val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
FeedUtils.offerInitContext(c) FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash) FeedUtils.dbHelper!!.putStoryDismissed(storyHash)

View file

@ -4,14 +4,13 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.newsblur.activity.Reading import com.newsblur.activity.Reading
import kotlinx.coroutines.GlobalScope
class NotifyMarkreadReceiver : BroadcastReceiver() { class NotifyMarkreadReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) { override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH) val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
NotificationUtils.cancel(c, storyHash.hashCode()) NotificationUtils.cancel(c, storyHash.hashCode())
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
FeedUtils.offerInitContext(c) FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash) FeedUtils.dbHelper!!.putStoryDismissed(storyHash)

View file

@ -4,14 +4,13 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.newsblur.activity.Reading import com.newsblur.activity.Reading
import kotlinx.coroutines.GlobalScope
class NotifySaveReceiver : BroadcastReceiver() { class NotifySaveReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) { override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH) val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
NotificationUtils.cancel(c, storyHash.hashCode()) NotificationUtils.cancel(c, storyHash.hashCode())
GlobalScope.executeAsyncTask( NBScope.executeAsyncTask(
doInBackground = { doInBackground = {
FeedUtils.offerInitContext(c) FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash) FeedUtils.dbHelper!!.putStoryDismissed(storyHash)

View file

@ -1,11 +1,15 @@
package com.newsblur.util; package com.newsblur.util;
import static com.newsblur.service.NBSyncReceiver.UPDATE_INTEL;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_SOCIAL;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import java.io.Serializable; import java.io.Serializable;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper; import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants; import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Classifier; import com.newsblur.domain.Classifier;
@ -13,13 +17,13 @@ import com.newsblur.network.domain.CommentResponse;
import com.newsblur.network.domain.NewsBlurResponse; import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.domain.StoriesResponse; import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.network.APIManager; import com.newsblur.network.APIManager;
import com.newsblur.service.NBSyncReceiver;
import com.newsblur.service.NBSyncService; import com.newsblur.service.NBSyncService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@SuppressWarnings("serial")
public class ReadingAction implements Serializable { public class ReadingAction implements Serializable {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
@ -370,7 +374,7 @@ public class ReadingAction implements Serializable {
} else { } else {
com.newsblur.util.Log.w(this, "failed to refresh story data after action"); com.newsblur.util.Log.w(this, "failed to refresh story data after action");
} }
impact |= NbActivity.UPDATE_SOCIAL; impact |= NBSyncReceiver.UPDATE_SOCIAL;
} }
if (commentResponse != null) { if (commentResponse != null) {
result = commentResponse; result = commentResponse;
@ -379,10 +383,11 @@ public class ReadingAction implements Serializable {
} else { } else {
com.newsblur.util.Log.w(this, "failed to refresh comment data after action"); com.newsblur.util.Log.w(this, "failed to refresh comment data after action");
} }
impact |= NbActivity.UPDATE_SOCIAL; impact |= NBSyncReceiver.UPDATE_SOCIAL;
}
if (result != null && impact != 0) {
result.impactCode = impact;
} }
NbActivity.updateAllActivities(impact);
return result; return result;
} }
@ -408,48 +413,48 @@ public class ReadingAction implements Serializable {
dbHelper.markStoriesRead(feedSet, olderThan, newerThan); dbHelper.markStoriesRead(feedSet, olderThan, newerThan);
dbHelper.updateLocalFeedCounts(feedSet); dbHelper.updateLocalFeedCounts(feedSet);
} }
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
impact |= NbActivity.UPDATE_STORY; impact |= UPDATE_STORY;
break; break;
case MARK_UNREAD: case MARK_UNREAD:
dbHelper.setStoryReadState(storyHash, false); dbHelper.setStoryReadState(storyHash, false);
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
case SAVE: case SAVE:
dbHelper.setStoryStarred(storyHash, userTags, true); dbHelper.setStoryStarred(storyHash, userTags, true);
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
case UNSAVE: case UNSAVE:
dbHelper.setStoryStarred(storyHash, null, false); dbHelper.setStoryStarred(storyHash, null, false);
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
case SHARE: case SHARE:
if (isFollowup) break; // shares are only placeholders if (isFollowup) break; // shares are only placeholders
dbHelper.setStoryShared(storyHash, true); dbHelper.setStoryShared(storyHash, true);
dbHelper.insertCommentPlaceholder(storyId, feedId, commentReplyText); dbHelper.insertCommentPlaceholder(storyId, feedId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY; impact |= UPDATE_STORY;
break; break;
case UNSHARE: case UNSHARE:
dbHelper.setStoryShared(storyHash, false); dbHelper.setStoryShared(storyHash, false);
dbHelper.clearSelfComments(storyId); dbHelper.clearSelfComments(storyId);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY; impact |= UPDATE_STORY;
break; break;
case LIKE_COMMENT: case LIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true); dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
break; break;
case UNLIKE_COMMENT: case UNLIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false); dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
break; break;
case REPLY: case REPLY:
@ -459,22 +464,22 @@ public class ReadingAction implements Serializable {
case EDIT_REPLY: case EDIT_REPLY:
dbHelper.editReply(replyId, commentReplyText); dbHelper.editReply(replyId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
break; break;
case DELETE_REPLY: case DELETE_REPLY:
dbHelper.deleteReply(replyId); dbHelper.deleteReply(replyId);
impact |= NbActivity.UPDATE_SOCIAL; impact |= UPDATE_SOCIAL;
break; break;
case MUTE_FEEDS: case MUTE_FEEDS:
case UNMUTE_FEEDS: case UNMUTE_FEEDS:
dbHelper.setFeedsActive(modifiedFeedIds, type == ActionType.UNMUTE_FEEDS); dbHelper.setFeedsActive(modifiedFeedIds, type == ActionType.UNMUTE_FEEDS);
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
case SET_NOTIFY: case SET_NOTIFY:
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
case INSTA_FETCH: case INSTA_FETCH:
@ -489,12 +494,12 @@ public class ReadingAction implements Serializable {
dbHelper.clearClassifiersForFeed(feedId); dbHelper.clearClassifiersForFeed(feedId);
classifier.feedId = feedId; classifier.feedId = feedId;
dbHelper.insertClassifier(classifier); dbHelper.insertClassifier(classifier);
impact |= NbActivity.UPDATE_INTEL; impact |= UPDATE_INTEL;
break; break;
case RENAME_FEED: case RENAME_FEED:
dbHelper.renameFeed(feedId, newFeedName); dbHelper.renameFeed(feedId, newFeedName);
impact |= NbActivity.UPDATE_METADATA; impact |= UPDATE_METADATA;
break; break;
default: default:

View file

@ -8,6 +8,7 @@ import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROL
import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP; import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP;
import android.app.Activity; import android.app.Activity;
import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
@ -303,15 +304,6 @@ public class UIUtils {
return memInfo; return memInfo;
} }
@SuppressWarnings("deprecation")
public static int getColor(Context activity, int rid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return activity.getResources().getColor(rid, activity.getTheme());
} else {
return activity.getResources().getColor(rid);
}
}
/** /**
* Get a color defined by our particular way of using styles that are indirectly defined by themes. * Get a color defined by our particular way of using styles that are indirectly defined by themes.
* *
@ -376,15 +368,6 @@ public class UIUtils {
return result; return result;
} }
@SuppressWarnings("deprecation")
public static Drawable getDrawable(Context activity, int rid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return activity.getResources().getDrawable(rid, activity.getTheme());
} else {
return activity.getResources().getDrawable(rid);
}
}
/** /**
* Sets the background resource of a view, working around a platform bug that causes the declared * Sets the background resource of a view, working around a platform bug that causes the declared
* padding to get reset. * padding to get reset.
@ -440,26 +423,17 @@ public class UIUtils {
*/ */
public static void setupIntelDialogRow(final View row, final Map<String,Integer> classifier, final String key) { public static void setupIntelDialogRow(final View row, final Map<String,Integer> classifier, final String key) {
colourIntelDialogRow(row, classifier, key); colourIntelDialogRow(row, classifier, key);
row.findViewById(R.id.intel_row_like).setOnClickListener(new OnClickListener() { row.findViewById(R.id.intel_row_like).setOnClickListener(v -> {
@Override classifier.put(key, Classifier.LIKE);
public void onClick(View v) { colourIntelDialogRow(row, classifier, key);
classifier.put(key, Classifier.LIKE);
colourIntelDialogRow(row, classifier, key);
}
}); });
row.findViewById(R.id.intel_row_dislike).setOnClickListener(new OnClickListener() { row.findViewById(R.id.intel_row_dislike).setOnClickListener(v -> {
@Override classifier.put(key, Classifier.DISLIKE);
public void onClick(View v) { colourIntelDialogRow(row, classifier, key);
classifier.put(key, Classifier.DISLIKE);
colourIntelDialogRow(row, classifier, key);
}
}); });
row.findViewById(R.id.intel_row_clear).setOnClickListener(new OnClickListener() { row.findViewById(R.id.intel_row_clear).setOnClickListener(v -> {
@Override classifier.put(key, Classifier.CLEAR_LIKE);
public void onClick(View v) { colourIntelDialogRow(row, classifier, key);
classifier.put(key, Classifier.CLEAR_LIKE);
colourIntelDialogRow(row, classifier, key);
}
}); });
} }
@ -584,6 +558,16 @@ public class UIUtils {
} }
} }
public static void openWebSearch(Context context, String query) {
try {
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH );
intent.putExtra(SearchManager.QUERY, query);
context.startActivity(intent);
} catch (Exception e) {
com.newsblur.util.Log.e(context.getClass().getName(), "Browser app not available to search: " + query);
}
}
public static boolean needsPremiumAccess(Context context, FeedSet feedSet) { public static boolean needsPremiumAccess(Context context, FeedSet feedSet) {
boolean isPremium = PrefsUtils.getIsPremium(context); boolean isPremium = PrefsUtils.getIsPremium(context);
boolean requiresPremium = feedSet.isFolder() || feedSet.isInfrequent() || boolean requiresPremium = feedSet.isFolder() || feedSet.isInfrequent() ||

View file

@ -1,11 +1,15 @@
package com.newsblur.view; package com.newsblur.view;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.WebChromeClient; import android.webkit.WebChromeClient;
@ -16,24 +20,23 @@ import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.newsblur.R;
import com.newsblur.activity.Reading; import com.newsblur.activity.Reading;
import com.newsblur.fragment.ReadingItemFragment; import com.newsblur.fragment.ReadingItemFragment;
import com.newsblur.util.UIUtils; import com.newsblur.util.UIUtils;
public class NewsblurWebview extends WebView { public class NewsblurWebview extends WebView {
private NewsblurWebViewClient webViewClient; private final NewsblurWebChromeClient webChromeClient;
private NewsblurWebChromeClient webChromeClient;
private boolean isCustomViewShowing; private boolean isCustomViewShowing;
private Context context;
public ReadingItemFragment fragment; public ReadingItemFragment fragment;
// we need the less-abstract activity class in order to manipulate the overlay widgets // we need the less-abstract activity class in order to manipulate the overlay widgets
public Reading activity; public Reading activity;
public NewsblurWebview(Context context, AttributeSet attrs) { @SuppressLint("SetJavaScriptEnabled")
public NewsblurWebview(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
this.context = context;
setVerticalScrollBarEnabled(false); setVerticalScrollBarEnabled(false);
setHorizontalScrollBarEnabled(false); setHorizontalScrollBarEnabled(false);
@ -46,30 +49,51 @@ public class NewsblurWebview extends WebView {
getSettings().setAllowFileAccess(true); getSettings().setAllowFileAccess(true);
getSettings().setAppCacheEnabled(true); getSettings().setAppCacheEnabled(true);
// Explicitly remove the system default web search menu item in case it's available
// to add a custom web search menu item to ensure menu item availability
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
getSettings().getDisabledActionModeMenuItems() == WebSettings.MENU_ITEM_NONE) {
getSettings().setDisabledActionModeMenuItems(WebSettings.MENU_ITEM_WEB_SEARCH);
}
this.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY); this.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
// handle links, loading progress, and error callbacks // handle links, loading progress, and error callbacks
webViewClient = new NewsblurWebViewClient(); setWebViewClient(new NewsblurWebViewClient());
setWebViewClient(webViewClient);
// do the minimum handling of view swapping so that fullscreen HTML5 works, for videos. // do the minimum handling of view swapping so that fullscreen HTML5 works, for videos.
webChromeClient = new NewsblurWebChromeClient(); webChromeClient = new NewsblurWebChromeClient();
setWebChromeClient(webChromeClient); setWebChromeClient(webChromeClient);
} }
// Add a custom web search menu item to the web view contextual menu
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int type) {
ActionMode actionMode = super.startActionMode(callback, type);
Menu menu = actionMode.getMenu();
menu.add(R.string.menu_web_search).setOnMenuItemClickListener(menuItem -> {
contextualWebSearch();
actionMode.finish();
return true;
});
return actionMode;
}
private void contextualWebSearch() {
evaluateJavascript(getContext().getString(R.string.js_get_selection), value -> {
// remove beginning and ending double quotes
String query = value.replaceAll("^\"|\"$", "");
if (!TextUtils.isEmpty(query)) {
UIUtils.openWebSearch(getContext(), query);
}
});
}
public void setTextSize(float textSize) { public void setTextSize(float textSize) {
String script = "javascript:document.body.style.fontSize='" + textSize + "em';"; String script = "javascript:document.body.style.fontSize='" + textSize + "em';";
evaluateJavascript(script, null); evaluateJavascript(script, null);
} }
/**
* http://stackoverflow.com/questions/5994066/webview-ontouch-handling-when-the-user-does-not-click-a-link
*/
public boolean wasLinkClicked() {
WebView.HitTestResult result = getHitTestResult();
return (result != null && result.getExtra() != null);
}
/** /**
* If HTML5 views (like fullscreen video) are to work, we need a container in which to put them. * If HTML5 views (like fullscreen video) are to work, we need a container in which to put them.
* If the activity using this webview creates a hidden layout under/over this webview, we can * If the activity using this webview creates a hidden layout under/over this webview, we can
@ -90,14 +114,14 @@ public class NewsblurWebview extends WebView {
class NewsblurWebViewClient extends WebViewClient { class NewsblurWebViewClient extends WebViewClient {
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
UIUtils.handleUri(context, Uri.parse(url)); UIUtils.handleUri(getContext(), Uri.parse(url));
return true; return true;
} }
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
UIUtils.handleUri(context, request.getUrl()); UIUtils.handleUri(getContext(), request.getUrl());
return true; return true;
} }
@ -179,5 +203,4 @@ public class NewsblurWebview extends WebView {
} }
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
} }