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/.gradle
clients/android/NewsBlur/build.gradle
clients/android/NewsBlur/gradle*
clients/android/NewsBlur/gradlew*
clients/android/NewsBlur/settings.gradle
/docker/volumes/*

View file

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

View file

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

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.5.30'
ext.kotlin_version = '1.6.0'
repositories {
mavenCentral()
maven {
@ -9,7 +9,7 @@ buildscript {
google()
}
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"
}
}
@ -27,28 +27,29 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.fragment:fragment-ktx:1.4.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.android.billingclient:billing:3.0.3'
implementation 'nl.dionsegijn:konfetti:1.2.2'
implementation 'com.google.android.play:core:1.10.0'
implementation "com.google.android.material:material:1.3.0"
implementation 'com.google.android.play:core:1.10.2'
implementation "com.google.android.material:material:1.4.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.browser:browser:1.3.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.browser:browser:1.4.0"
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 {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId "com.newsblur"
minSdkVersion 21
targetSdkVersion 30
versionCode 196
targetSdkVersion 31
versionCode 198
versionName "11.1.1"
}
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_story_list_style_choose">List style…</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_grid_f">Grid (fine)</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_name">New Stories</string>
<string name="go_to_feed">Go to feed</string>
<string name="js_get_selection">(function(){return window.getSelection().toString()})()</string>
</resources>

View file

@ -11,10 +11,10 @@
<item name="android:letterSpacing">0</item>
</style>
<style name="initStyle" parent="Theme.MaterialComponents.Light.DarkActionBar.Bridge">
<item name="android:windowBackground">@color/transparent</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<style name="splashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/black</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash_screen</item>
<item name="postSplashScreenTheme">@style/NewsBlurTheme</item>
</style>
<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.net.Uri;
import android.os.Bundle;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
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);
binding.loadingThrob.setEnabled(!ViewUtils.isPowerSaveMode(this));
binding.loadingThrob.setColors(UIUtils.getColor(this, R.color.refresh_1),
UIUtils.getColor(this, R.color.refresh_2),
UIUtils.getColor(this, R.color.refresh_3),
UIUtils.getColor(this, R.color.refresh_4));
binding.loadingThrob.setColors(ContextCompat.getColor(this, R.color.refresh_1),
ContextCompat.getColor(this, R.color.refresh_2),
ContextCompat.getColor(this, R.color.refresh_3),
ContextCompat.getColor(this, R.color.refresh_4));
Intent intent = getIntent();
Uri uri = intent.getData();

View file

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

View file

@ -1,5 +1,9 @@
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 androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

View file

@ -1,5 +1,10 @@
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.graphics.Bitmap;
import android.net.Uri;

View file

@ -1,5 +1,7 @@
package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.content.Intent;
import android.database.Cursor;
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.fragment.ReadingItemFragment
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.util.*
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.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter;
@ -1641,6 +1642,10 @@ public class BlurDatabaseHelper {
try {c.close();} catch (Exception e) {;}
}
public void sendSyncUpdate(int updateType) {
FeedUtils.syncUpdateStatus(context, updateType);
}
private static String conjoinSelections(CharSequence... args) {
StringBuilder s = null;
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) {
return dbRO.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal);
}
}

View file

@ -1,5 +1,7 @@
package com.newsblur.database;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
@ -10,7 +12,6 @@ import androidx.viewpager.widget.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
@ -283,7 +284,7 @@ public class ReadingAdapter extends PagerAdapter {
ReadingItemFragment rif = fragments.get(s.storyHash);
if (rif != null ) {
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.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.domain.Feed;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
@ -75,18 +73,17 @@ public class DeleteFeedFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
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)) {
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), new APIManager(getActivity()));
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity());
} 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
Activity activity = DeleteFeedFragment.this.getActivity();
if (activity instanceof ItemsList) {
activity.finish();
}
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
DeleteFeedFragment.this.dismiss();
}
});

View file

@ -10,6 +10,7 @@ import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.loader.app.LoaderManager;
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);
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.setOnChildClickListener(this);
binding.folderfeedList.setOnGroupClickListener(this);

View file

@ -7,6 +7,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.GridLayoutManager;
@ -121,21 +122,19 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
fleuronBinding = RowFleuronBinding.bind(fleuronView);
// 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.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1),
UIUtils.getColor(getActivity(), R.color.refresh_2),
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
binding.topLoadingThrob.setColors(colorsArray);
View footerView = inflater.inflate(R.layout.row_loading_throbber, null);
bottomProgressView = (ProgressThrobber) footerView.findViewById(R.id.itemlist_loading_throb);
bottomProgressView.setEnabled(!isDisableAnimations);
bottomProgressView.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1),
UIUtils.getColor(getActivity(), R.color.refresh_2),
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
bottomProgressView.setColors(colorsArray);
fleuronBinding.getRoot().setVisibility(View.INVISIBLE);
fleuronBinding.containerSubscribe.setOnClickListener(view -> UIUtils.startPremiumActivity(requireContext()));

View file

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

View file

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

View file

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

View file

@ -7,6 +7,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
import com.newsblur.util.AppConstants;
import com.newsblur.widget.WidgetUtils;
@ -17,10 +19,12 @@ import com.newsblur.widget.WidgetUtils;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
com.newsblur.util.Log.d(this, "triggering sync service from device boot");
scheduleSyncService(context);
resetWidgetSync(context);
public void onReceive(Context context, @Nullable Intent intent) {
if (intent != null && intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
com.newsblur.util.Log.d(this, "triggering sync service from device boot");
scheduleSyncService(context);
resetWidgetSync(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.os.Process;
import com.newsblur.NbApplication;
import com.newsblur.R;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
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.domain.Feed;
import com.newsblur.domain.Folder;
@ -182,7 +190,7 @@ public class NBSyncService extends JobService {
public int onStartCommand(Intent intent, int flags, final int startId) {
com.newsblur.util.Log.d(this, "onStartCommand");
// 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;
// Services actually get invoked on the main system thread, and are not
// 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) {
com.newsblur.util.Log.d(this, "onStartJob");
// 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;
// Services actually get invoked on the main system thread, and are not
// 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");
if (NbActivity.getActiveActivityCount() < 1) {
if (!NbApplication.isAppForeground()) {
// if the UI isn't running, politely run at background priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
} else {
@ -273,7 +281,7 @@ public class NBSyncService extends JobService {
if (OfflineNow) {
if (NetworkUtils.isOnline(this)) {
OfflineNow = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
} else {
com.newsblur.util.Log.d(this, "Abandoning sync: network still offline");
return;
@ -285,7 +293,7 @@ public class NBSyncService extends JobService {
housekeeping();
// 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.isBackgroundNetworkAllowed(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
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
originalTextService.start();
@ -337,10 +345,10 @@ public class NBSyncService extends JobService {
boolean upgraded = PrefsUtils.checkForUpgrade(this);
if (upgraded) {
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
// run, InitActivity will have wiped for us
if (NbActivity.getActiveActivityCount() < 1) {
if (!NbApplication.isAppForeground()) {
dbHelper.dropAndRecreateTables();
}
// 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);
// 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) {
HousekeepingRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
com.newsblur.util.Log.i(this.getClass().getName(), "rebuilding DB . . .");
dbHelper.vacuum();
com.newsblur.util.Log.i(this.getClass().getName(), ". . . . done rebuilding DB");
@ -366,7 +374,7 @@ public class NBSyncService extends JobService {
} finally {
if (HousekeepingRunning) {
HousekeepingRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
sendSyncUpdate(UPDATE_METADATA);
}
}
}
@ -387,7 +395,7 @@ public class NBSyncService extends JobService {
ActionsRunning = true;
actionsloop : while (c.moveToNext()) {
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
ReadingAction ra;
try {
@ -419,18 +427,19 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "Discarding reading action with fatal message.");
dbHelper.clearAction(id);
String message = response.getErrorMessage(null);
if (message != null) NbActivity.toastError(message);
if (message != null) sendToastError(message);
} else {
// success!
dbHelper.clearAction(id);
FollowupActions.add(ra);
sendSyncUpdate(response.impactCode);
}
lastActionCount--;
}
} finally {
closeQuietly(c);
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);
impactFlags |= impact;
}
NbActivity.updateAllActivities(impactFlags);
sendSyncUpdate(impactFlags);
// 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
@ -484,7 +493,7 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "ready to sync feed list");
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.
Set<String> debugFeedIdsFromFolders = new HashSet<String>();
@ -618,7 +627,7 @@ public class NBSyncService extends JobService {
} finally {
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;
RecountsRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
// of all candidate feeds that were touched, now check to see if any
// actually need their counts fetched
@ -699,7 +708,7 @@ public class NBSyncService extends JobService {
} finally {
if (RecountsRunning) {
RecountsRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_METADATA | UPDATE_STATUS);
}
FlushRecounts = false;
}
@ -760,7 +769,7 @@ public class NBSyncService extends JobService {
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
StorySyncRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
while (totalStoriesSeen < PendingFeedTarget) {
if (stopSync()) return;
@ -784,7 +793,7 @@ public class NBSyncService extends JobService {
insertStories(apiResponse, fs);
// re-do any very recent actions that were incorrectly overwritten by this page
finishActions();
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
prefetchOriginalText(apiResponse);
@ -804,7 +813,7 @@ public class NBSyncService extends JobService {
} finally {
StorySyncRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
synchronized (PENDING_FEED_MUTEX) {
if (finished && fs.equals(PendingFeed)) PendingFeed = null;
}
@ -996,7 +1005,7 @@ public class NBSyncService extends JobService {
}
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;
com.newsblur.util.Log.i(this.getClass().getName(), "abandoning background sync due to recent API failures.");
return true;
@ -1128,7 +1137,7 @@ public class NBSyncService extends JobService {
dbHelper.prepareReadingSession(fs);
// note which feedset we are loading so we can trigger another reset when it changes
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();
}
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;
import com.newsblur.activity.NbActivity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.util.AppConstants;
@ -84,7 +85,7 @@ public class OriginalTextService extends SubService {
}
}
} finally {
gotData(NbActivity.UPDATE_TEXT);
parent.sendSyncUpdate(UPDATE_TEXT);
hashes.removeAll(fetchedHashes);
}
}

View file

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

View file

@ -1,16 +1,23 @@
package com.newsblur.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
private const val TAG = "NBScope"
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit = { },
onPreExecute: () -> Unit = { },
doInBackground: () -> R,
onPostExecute: (R) -> Unit = { }) =
onPostExecute: (R) -> Unit = { }) =
launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { doInBackground() }
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.net.Uri
import android.text.TextUtils
import com.newsblur.NbApplication
import com.newsblur.R
import com.newsblur.activity.NbActivity
import com.newsblur.database.BlurDatabaseHelper
@ -12,7 +13,10 @@ import com.newsblur.fragment.ReadingActionConfirmationFragment
import com.newsblur.network.APIConstants
import com.newsblur.network.APIManager
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.*
object FeedUtils {
@ -70,7 +74,7 @@ object FeedUtils {
@JvmStatic
fun prepareReadingSession(fs: FeedSet?, resetFirst: Boolean) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
try {
if (resetFirst) NBSyncService.resetReadingSession(dbHelper)
@ -96,11 +100,11 @@ object FeedUtils {
}
private fun setStorySaved(storyHash: String?, saved: Boolean, context: Context, userTags: List<String?>?) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_STORY)
dbHelper!!.enqueueAction(ra)
triggerSync(context)
}
@ -108,15 +112,15 @@ object FeedUtils {
}
@JvmStatic
fun deleteSavedSearch(feedId: String?, query: String?, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
fun deleteSavedSearch(feedId: String?, query: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.deleteSearch(feedId, query)
APIManager(context).deleteSearch(feedId, query)
},
onPostExecute = { newsBlurResponse ->
if (!newsBlurResponse.isError) {
dbHelper!!.deleteSavedSearch(feedId, query)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
}
)
@ -124,7 +128,7 @@ object FeedUtils {
@JvmStatic
fun saveSearch(feedId: String?, query: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
apiManager.saveSearch(feedId, query)
},
@ -138,36 +142,36 @@ object FeedUtils {
}
@JvmStatic
fun deleteFeed(feedId: String?, folderName: String?, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
fun deleteFeed(feedId: String?, folderName: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.deleteFeed(feedId, folderName)
APIManager(context).deleteFeed(feedId, folderName)
},
onPostExecute = {
// 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)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@JvmStatic
fun deleteSocialFeed(userId: String?, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
fun deleteSocialFeed(userId: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.unfollowUser(userId)
APIManager(context).unfollowUser(userId)
},
onPostExecute = {
// 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)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@JvmStatic
fun deleteFolder(folderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
apiManager.deleteFolder(folderName, inFolder)
},
@ -189,7 +193,7 @@ object FeedUtils {
@JvmStatic
fun renameFolder(folderName: String?, newFolderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
apiManager.renameFolder(folderName, newFolderName, inFolder)
},
@ -204,7 +208,7 @@ object FeedUtils {
@JvmStatic
fun markStoryUnread(story: Story, context: Context) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
setStoryReadState(story, context, false)
}
@ -213,7 +217,7 @@ object FeedUtils {
@JvmStatic
fun markStoryAsRead(story: Story, context: Context) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
setStoryReadState(story, context, true)
}
@ -238,7 +242,7 @@ object FeedUtils {
// update unread state and unread counts in the local DB
val impactedFeeds = dbHelper!!.setStoryReadState(story, read)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_STORY)
NBSyncService.addRecountCandidates(impactedFeeds)
triggerSync(context)
@ -351,7 +355,7 @@ object FeedUtils {
}
private fun updateFeedNotifications(context: Context, feed: Feed, enable: Boolean, focusOnly: Boolean) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
if (focusOnly) {
feed.setNotifyFocus()
@ -369,11 +373,11 @@ object FeedUtils {
@JvmStatic
fun doAction(ra: ReadingAction?, context: Context) {
requireNotNull(ra) { "ReadingAction must not be null" }
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact)
syncUpdateStatus(context, impact)
triggerSync(context)
}
)
@ -418,7 +422,7 @@ object FeedUtils {
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
}
@ -427,7 +431,7 @@ object FeedUtils {
val ra = ReadingAction.renameFeed(feedId, newFeedName)
dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact)
syncUpdateStatus(context, impact)
triggerSync(context)
}
@ -436,7 +440,7 @@ object FeedUtils {
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
}
@ -444,7 +448,7 @@ object FeedUtils {
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -452,7 +456,7 @@ object FeedUtils {
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -461,7 +465,7 @@ object FeedUtils {
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -470,7 +474,7 @@ object FeedUtils {
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -479,14 +483,14 @@ object FeedUtils {
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@JvmStatic
fun moveFeedToFolders(context: Context, feedId: String?, toFolders: Set<String?>, inFolders: Set<String?>?) {
if (toFolders.isEmpty()) return
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
val apiManager = APIManager(context)
apiManager.moveFeedToFolders(feedId, toFolders, inFolders)
@ -509,7 +513,7 @@ object FeedUtils {
}
private fun updateFeedActiveState(context: Context, feedIds: Set<String>, active: Boolean) {
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
val activeFeeds = dbHelper!!.allActiveFeeds
for (feedId in feedIds) {
@ -529,7 +533,7 @@ object FeedUtils {
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context)
}
)
@ -540,7 +544,7 @@ object FeedUtils {
val ra = ReadingAction.instaFetch(feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context)
}
@ -593,4 +597,15 @@ object FeedUtils {
val url = APIConstants.buildUrl(APIConstants.PATH_FEED_STATISTICS + feedId)
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.Intent
import com.newsblur.activity.Reading
import kotlinx.coroutines.GlobalScope
class NotifyDismissReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
GlobalScope.executeAsyncTask(
NBScope.executeAsyncTask(
doInBackground = {
FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash)

View file

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

View file

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

View file

@ -1,11 +1,15 @@
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.database.Cursor;
import java.io.Serializable;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
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.StoriesResponse;
import com.newsblur.network.APIManager;
import com.newsblur.service.NBSyncReceiver;
import com.newsblur.service.NBSyncService;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@SuppressWarnings("serial")
public class ReadingAction implements Serializable {
private static final long serialVersionUID = 0L;
@ -370,7 +374,7 @@ public class ReadingAction implements Serializable {
} else {
com.newsblur.util.Log.w(this, "failed to refresh story data after action");
}
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NBSyncReceiver.UPDATE_SOCIAL;
}
if (commentResponse != null) {
result = commentResponse;
@ -379,10 +383,11 @@ public class ReadingAction implements Serializable {
} else {
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;
}
@ -408,48 +413,48 @@ public class ReadingAction implements Serializable {
dbHelper.markStoriesRead(feedSet, olderThan, newerThan);
dbHelper.updateLocalFeedCounts(feedSet);
}
impact |= NbActivity.UPDATE_METADATA;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_METADATA;
impact |= UPDATE_STORY;
break;
case MARK_UNREAD:
dbHelper.setStoryReadState(storyHash, false);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SAVE:
dbHelper.setStoryStarred(storyHash, userTags, true);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case UNSAVE:
dbHelper.setStoryStarred(storyHash, null, false);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SHARE:
if (isFollowup) break; // shares are only placeholders
dbHelper.setStoryShared(storyHash, true);
dbHelper.insertCommentPlaceholder(storyId, feedId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_SOCIAL;
impact |= UPDATE_STORY;
break;
case UNSHARE:
dbHelper.setStoryShared(storyHash, false);
dbHelper.clearSelfComments(storyId);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_SOCIAL;
impact |= UPDATE_STORY;
break;
case LIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case UNLIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case REPLY:
@ -459,22 +464,22 @@ public class ReadingAction implements Serializable {
case EDIT_REPLY:
dbHelper.editReply(replyId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case DELETE_REPLY:
dbHelper.deleteReply(replyId);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case MUTE_FEEDS:
case UNMUTE_FEEDS:
dbHelper.setFeedsActive(modifiedFeedIds, type == ActionType.UNMUTE_FEEDS);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SET_NOTIFY:
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case INSTA_FETCH:
@ -489,12 +494,12 @@ public class ReadingAction implements Serializable {
dbHelper.clearClassifiersForFeed(feedId);
classifier.feedId = feedId;
dbHelper.insertClassifier(classifier);
impact |= NbActivity.UPDATE_INTEL;
impact |= UPDATE_INTEL;
break;
case RENAME_FEED:
dbHelper.renameFeed(feedId, newFeedName);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
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 android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@ -303,15 +304,6 @@ public class UIUtils {
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.
*
@ -376,15 +368,6 @@ public class UIUtils {
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
* 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) {
colourIntelDialogRow(row, classifier, key);
row.findViewById(R.id.intel_row_like).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
classifier.put(key, Classifier.LIKE);
colourIntelDialogRow(row, classifier, key);
}
row.findViewById(R.id.intel_row_like).setOnClickListener(v -> {
classifier.put(key, Classifier.LIKE);
colourIntelDialogRow(row, classifier, key);
});
row.findViewById(R.id.intel_row_dislike).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
classifier.put(key, Classifier.DISLIKE);
colourIntelDialogRow(row, classifier, key);
}
row.findViewById(R.id.intel_row_dislike).setOnClickListener(v -> {
classifier.put(key, Classifier.DISLIKE);
colourIntelDialogRow(row, classifier, key);
});
row.findViewById(R.id.intel_row_clear).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
classifier.put(key, Classifier.CLEAR_LIKE);
colourIntelDialogRow(row, classifier, key);
}
row.findViewById(R.id.intel_row_clear).setOnClickListener(v -> {
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) {
boolean isPremium = PrefsUtils.getIsPremium(context);
boolean requiresPremium = feedSet.isFolder() || feedSet.isInfrequent() ||

View file

@ -1,11 +1,15 @@
package com.newsblur.view;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
@ -16,24 +20,23 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import com.newsblur.R;
import com.newsblur.activity.Reading;
import com.newsblur.fragment.ReadingItemFragment;
import com.newsblur.util.UIUtils;
public class NewsblurWebview extends WebView {
private NewsblurWebViewClient webViewClient;
private NewsblurWebChromeClient webChromeClient;
private final NewsblurWebChromeClient webChromeClient;
private boolean isCustomViewShowing;
private Context context;
public ReadingItemFragment fragment;
// we need the less-abstract activity class in order to manipulate the overlay widgets
public Reading activity;
public NewsblurWebview(Context context, AttributeSet attrs) {
@SuppressLint("SetJavaScriptEnabled")
public NewsblurWebview(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
setVerticalScrollBarEnabled(false);
setHorizontalScrollBarEnabled(false);
@ -46,30 +49,51 @@ public class NewsblurWebview extends WebView {
getSettings().setAllowFileAccess(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);
// handle links, loading progress, and error callbacks
webViewClient = new NewsblurWebViewClient();
setWebViewClient(webViewClient);
setWebViewClient(new NewsblurWebViewClient());
// do the minimum handling of view swapping so that fullscreen HTML5 works, for videos.
webChromeClient = new NewsblurWebChromeClient();
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) {
String script = "javascript:document.body.style.fontSize='" + textSize + "em';";
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 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 {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
UIUtils.handleUri(context, Uri.parse(url));
UIUtils.handleUri(getContext(), Uri.parse(url));
return true;
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
UIUtils.handleUri(context, request.getUrl());
UIUtils.handleUri(getContext(), request.getUrl());
return true;
}
@ -179,5 +203,4 @@ public class NewsblurWebview extends WebView {
}
return super.onKeyDown(keyCode, event);
}
}