Merge branch 'sictiru'

* sictiru: (22 commits)
  Android 13.3.2 230
  #1886 Support for themed icons
  Add nullability annotations to ReadingAction
  Android 13.3.1 229
  #1891 Handle Android 9 and below for OPML file export
  Add nullability annotation to help with Kotlin conversion
  Revert binding view destroy
  Android v13.3.0
  Android v13.2.11 Revert db mutex changes
  Revert "#1853 Remove read write db mutex"
  Android v13.2.10. Clear web chrome client on web view destroy
  Android v13.2.9. Destroy webview on fragment view destroy
  Android v13.2.8
  Cleanup web view binding on destroy view
  Android v13.2.7
  Remove play core in favor or play review. Update other dependencies
  Android v13.2.6
  Catch OperationCanceledException exception when loading active stories
  #1853 Remove read write db mutex
  Android v13.2.5
  ...
This commit is contained in:
Samuel Clay 2024-11-12 17:09:17 -08:00
commit 5af380136c
26 changed files with 540 additions and 282 deletions

View file

@ -1,6 +1,7 @@
package com.newsblur.benchmark
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
@ -10,7 +11,7 @@ import org.junit.runner.RunWith
/**
* Runs in its own process
*/
@OptIn(ExperimentalBaselineProfilesApi::class)
@RequiresApi(Build.VERSION_CODES.P)
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@ -19,7 +20,7 @@ class BaselineProfileGenerator {
@Test
fun generateSimpleStartupProfile() {
rule.collectBaselineProfile(packageName = "com.newsblur") {
rule.collect(packageName = "com.newsblur") {
pressHome()
startActivityAndWait()
}
@ -28,7 +29,7 @@ class BaselineProfileGenerator {
@Test
fun generateUserJourneyProfile() {
var needsLogin = true
rule.collectBaselineProfile(packageName = "com.newsblur") {
rule.collect(packageName = "com.newsblur") {
pressHome()
startActivityAndWait()

View file

@ -57,7 +57,7 @@ dependencies {
implementation(Dependencies.okHttp)
implementation(Dependencies.gson)
implementation(Dependencies.billing)
implementation(Dependencies.playCore)
implementation(Dependencies.playReview)
implementation(Dependencies.material)
implementation(Dependencies.preference)
implementation(Dependencies.browser)

View file

@ -1,22 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:icon="@drawable/logo"
android:label="@string/newsblur"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
android:fullBackupContent="@xml/backupscheme"
android:name=".NbApplication"
android:dataExtractionRules="@xml/data_extraction_rules"
android:allowBackup="true"
android:fullBackupOnly="true">
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backupscheme"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/newsblur"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
<profileable
android:shell="true"
@ -24,15 +27,16 @@
<activity
android:name=".activity.InitActivity"
android:theme="@style/splashScreen"
android:exported="true"
android:noHistory="true"
android:exported="true">
android:theme="@style/splashScreen">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
@ -48,13 +52,13 @@
<activity
android:name=".activity.RegisterProgress"
android:noHistory="true"
android:label="@string/get_started" />
android:label="@string/get_started"
android:noHistory="true" />
<activity
android:name=".activity.Main"
android:launchMode="singleTask"
android:alwaysRetainTaskState="true" />
android:alwaysRetainTaskState="true"
android:launchMode="singleTask" />
<activity
android:name=".activity.Profile"
@ -68,113 +72,109 @@
<activity android:name=".activity.NotificationsActivity" />
<activity
android:name=".activity.WidgetConfig"
android:launchMode="singleTask"
android:label="@string/menu_widget" />
android:label="@string/menu_widget"
android:launchMode="singleTask" />
<activity
android:name=".activity.FeedItemsList" />
<activity android:name=".activity.FeedItemsList" />
<activity
android:name=".activity.AllStoriesItemsList"
android:launchMode="singleTask" />
<activity
android:name=".activity.InfrequentItemsList" />
<activity android:name=".activity.InfrequentItemsList" />
<activity
android:name=".activity.ReadStoriesItemsList" />
<activity android:name=".activity.ReadStoriesItemsList" />
<activity
android:name=".activity.SavedStoriesItemsList" />
<activity android:name=".activity.SavedStoriesItemsList" />
<activity
android:name=".activity.AllSharedStoriesItemsList" />
<activity android:name=".activity.AllSharedStoriesItemsList" />
<activity
android:name=".activity.GlobalSharedStoriesItemsList" />
<activity android:name=".activity.GlobalSharedStoriesItemsList" />
<activity
android:name=".activity.FolderItemsList" />
<activity android:name=".activity.FolderItemsList" />
<activity
android:name=".activity.SocialFeedItemsList" />
<activity android:name=".activity.SocialFeedItemsList" />
<activity
android:name=".activity.FeedReading"/>
<activity android:name=".activity.FeedReading" />
<activity
android:name=".activity.AllStoriesReading"/>
<activity android:name=".activity.AllStoriesReading" />
<activity
android:name=".activity.InfrequentReading"/>
<activity android:name=".activity.InfrequentReading" />
<activity
android:name=".activity.ReadStoriesReading"/>
<activity android:name=".activity.ReadStoriesReading" />
<activity
android:name=".activity.SavedStoriesReading"/>
<activity android:name=".activity.SavedStoriesReading" />
<activity
android:name=".activity.AllSharedStoriesReading"/>
<activity android:name=".activity.AllSharedStoriesReading" />
<activity
android:name=".activity.GlobalSharedStoriesReading"/>
<activity android:name=".activity.GlobalSharedStoriesReading" />
<activity
android:name=".activity.FolderReading"/>
<activity android:name=".activity.FolderReading" />
<activity
android:name=".activity.SubscriptionActivity" />
<activity android:name=".activity.SubscriptionActivity" />
<activity
android:name=".activity.MuteConfig"
android:launchMode="singleTask"
android:label="@string/mute_sites"/>
android:label="@string/mute_sites"
android:launchMode="singleTask" />
<activity
android:name=".activity.FeedSearchActivity"
android:launchMode="singleTop" />
<activity
android:name=".activity.SocialFeedReading"/>
<activity android:name=".activity.SocialFeedReading" />
<service
android:name=".service.NBSyncService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".widget.WidgetRemoteViewsService"
<service
android:name=".widget.WidgetRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".service.SubscriptionSyncService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<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>
</receiver>
<receiver android:name=".util.DownloadCompleteReceiver"
<receiver
android:name=".util.DownloadCompleteReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
<receiver android:name=".util.NotifyDismissReceiver" android:exported="false" />
<receiver android:name=".util.NotifySaveReceiver" android:exported="false" />
<receiver android:name=".util.NotifyMarkreadReceiver" android:exported="false" />
<receiver android:name=".util.NotifyShareReceiver" android:exported="false" />
<receiver android:name=".widget.WidgetProvider"
<receiver
android:name=".util.NotifyDismissReceiver"
android:exported="false" />
<receiver
android:name=".util.NotifySaveReceiver"
android:exported="false" />
<receiver
android:name=".util.NotifyMarkreadReceiver"
android:exported="false" />
<receiver
android:name=".util.NotifyShareReceiver"
android:exported="false" />
<receiver
android:name=".widget.WidgetProvider"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
<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" />
@ -183,8 +183,8 @@
<receiver
android:name=".widget.WidgetUpdateReceiver"
android:enabled="true"
android:exported="false">
</receiver>
android:exported="false"></receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.newsblur.fileprovider"
@ -195,12 +195,15 @@
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" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
@ -212,15 +215,19 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="feed" />
<data android:scheme="rss" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="feeds.feedburner.com" />
@ -229,9 +236,12 @@
<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" />
<data android:mimeType="text/xml" />
<data android:mimeType="application/rss+xml" />
<data android:mimeType="application/atom+xml" />
@ -243,11 +253,12 @@
</intent-filter>
</activity>
<activity android:name=".activity.ShareExternalStoryActivity"
android:theme="@style/Theme.Translucent"
android:windowSoftInputMode="adjustResize"
<activity
android:name=".activity.ShareExternalStoryActivity"
android:exported="true"
android:launchMode="singleInstance"
android:exported="true">
android:theme="@style/Theme.Translucent"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />

View file

@ -10,10 +10,10 @@ import androidx.fragment.app.DialogFragment;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.gms.tasks.Task;
import com.google.android.play.core.review.ReviewInfo;
import com.google.android.play.core.review.ReviewManager;
import com.google.android.play.core.review.ReviewManagerFactory;
import com.google.android.play.core.tasks.Task;
import com.newsblur.R;
import com.newsblur.di.IconLoader;
import com.newsblur.domain.Feed;

View file

@ -1,12 +1,16 @@
package com.newsblur.activity
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import com.newsblur.R
import com.newsblur.databinding.ActivityImportExportBinding
@ -40,6 +44,16 @@ class ImportExportActivity : NbActivity() {
}
}
// used for Android 9 and below
private val requestWriteStoragePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
exportOpmlFile()
} else {
Toast.makeText(this, R.string.write_storage_permission_opml, Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityImportExportBinding.inflate(layoutInflater)
@ -55,7 +69,13 @@ class ImportExportActivity : NbActivity() {
private fun setupListeners() {
binding.btnUpload.setOnClickListener { pickOpmlFile() }
binding.btnDownload.setOnClickListener { exportOpmlFile() }
binding.btnDownload.setOnClickListener {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
checkAndRequestWriteStoragePermission()
} else {
exportOpmlFile()
}
}
}
private fun pickOpmlFile() {
@ -131,4 +151,16 @@ class ImportExportActivity : NbActivity() {
override fun handleUpdate(updateType: Int) {
// ignore
}
// Android 9 and below
private fun checkAndRequestWriteStoragePermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED) {
exportOpmlFile()
} else {
requestWriteStoragePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}

View file

@ -35,6 +35,7 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.Log;
import com.newsblur.util.PendingTransitionUtils;
import com.newsblur.util.ReadingActionListener;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.Session;
@ -82,7 +83,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
Trace.beginSection("ItemsListOnCreate");
super.onCreate(bundle);
overridePendingTransition(R.anim.slide_in_from_right, R.anim.slide_out_to_left);
PendingTransitionUtils.overrideEnterTransition(this);
contextMenuDelegate = new ItemListContextMenuDelegateImpl(this, feedUtils);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
@ -313,13 +314,7 @@ public abstract class ItemsList extends NbActivity implements ReadingActionListe
@Override
public void finish() {
super.finish();
/*
* Animate out the list by sliding it to the right and the Main activity in from
* the left. Do this when going back to Main as a subtle hint to the swipe gesture,
* to make the gesture feel more natural, and to override the really ugly transition
* used in some of the newer platforms.
*/
overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
PendingTransitionUtils.overrideExitTransition(this);
}
abstract String getSaveSearchFeedId();

View file

@ -65,7 +65,7 @@ open class NbActivity : AppCompatActivity() {
// Facilitates the db updates by the sync service on the UI
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
NbSyncManager.state.collectLatest {
withContext(Dispatchers.Main) {

View file

@ -388,7 +388,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
get() {
// saved stories and global shared stories don't have unreads
if (fs!!.isAllSaved || fs!!.isGlobalShared) return 0
val result = dbHelper.getUnreadCount(fs, intelState)
val result = dbHelper.getUnreadCount(fs!!, intelState)
return if (result < 0) 0 else result
}

View file

@ -7,6 +7,8 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.CancellationSignal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
@ -46,13 +48,13 @@ import java.util.concurrent.Executors;
/**
* Utility class for executing DB operations on the local, private NB database.
*
* It is the intent of this class to be the single location of SQL executed on
* our DB, replacing the deprecated ContentProvider access pattern.
*/
public class BlurDatabaseHelper {
// manual synchro isn't needed if you only use one DBHelper, but at present the app uses several
// Removing the manual synchro will cause ANRs
// because the db transactions are made on the main thread
public final static Object RW_MUTEX = new Object();
private final BlurDatabase dbWrapper;
@ -85,6 +87,7 @@ public class BlurDatabaseHelper {
com.newsblur.util.Log.i(this.getClass().getName(), ". . . tables recreated.");
}
@Nullable
public String getEngineVersion() {
String engineVersion = "";
try {
@ -99,10 +102,12 @@ public class BlurDatabaseHelper {
return engineVersion;
}
@NonNull
public Set<String> getAllFeeds() {
return getAllFeeds(false);
}
@NonNull
private Set<String> getAllFeeds(boolean activeOnly) {
String q1 = "SELECT " + DatabaseConstants.FEED_ID +
" FROM " + DatabaseConstants.FEED_TABLE;
@ -118,10 +123,12 @@ public class BlurDatabaseHelper {
return feedIds;
}
@NonNull
public Set<String> getAllActiveFeeds() {
return getAllFeeds(true);
}
@NonNull
private List<String> getAllSocialFeeds() {
String q1 = "SELECT " + DatabaseConstants.SOCIAL_FEED_ID +
" FROM " + DatabaseConstants.SOCIALFEED_TABLE;
@ -181,20 +188,20 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.execSQL("VACUUM");}
}
public void deleteFeed(String feedId) {
public void deleteFeed(@Nullable String feedId) {
String[] selArgs = new String[] {feedId};
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_TABLE, DatabaseConstants.FEED_ID + " = ?", selArgs);}
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.STORY_TABLE, DatabaseConstants.STORY_FEED_ID + " = ?", selArgs);}
}
public void deleteSocialFeed(String userId) {
public void deleteSocialFeed(@Nullable String userId) {
String[] selArgs = new String[] {userId};
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_TABLE, DatabaseConstants.SOCIAL_FEED_ID + " = ?", selArgs);}
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.STORY_TABLE, DatabaseConstants.STORY_FEED_ID + " = ?", selArgs);}
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE, DatabaseConstants.SOCIALFEED_STORY_USER_ID + " = ?", selArgs);}
}
public void deleteSavedSearch(String feedId, String query) {
public void deleteSavedSearch(@Nullable String feedId, @Nullable String query) {
String q = "DELETE FROM " + DatabaseConstants.SAVED_SEARCH_TABLE +
" WHERE " + DatabaseConstants.SAVED_SEARCH_FEED_ID + " = '" + feedId + "'" +
" AND " + DatabaseConstants.SAVED_SEARCH_QUERY + " = '" + query + "'";
@ -207,7 +214,8 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.STORY_TEXT_TABLE, null, null);}
}
public Feed getFeed(String feedId) {
@Nullable
public Feed getFeed(@Nullable String feedId) {
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, DatabaseConstants.FEED_ID + " = ?", new String[] {feedId}, null, null, null);
Feed result = null;
while (c.moveToNext()) {
@ -217,13 +225,13 @@ public class BlurDatabaseHelper {
return result;
}
public void updateFeed(Feed feed) {
public void updateFeed(@NonNull Feed feed) {
synchronized (RW_MUTEX) {
dbRW.insertWithOnConflict(DatabaseConstants.FEED_TABLE, null, feed.getValues(), SQLiteDatabase.CONFLICT_REPLACE);
}
}
private void bulkInsertValues(String table, List<ContentValues> valuesList) {
private void bulkInsertValues(@NonNull String table, @NonNull List<ContentValues> valuesList) {
if (valuesList.size() < 1) return;
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
@ -239,18 +247,18 @@ public class BlurDatabaseHelper {
}
// just like bulkInsertValues, but leaves sync/transactioning to the caller
private void bulkInsertValuesExtSync(String table, List<ContentValues> valuesList) {
private void bulkInsertValuesExtSync(@NonNull String table, @NonNull List<ContentValues> valuesList) {
if (valuesList.size() < 1) return;
for (ContentValues values : valuesList) {
dbRW.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
}
public void setFeedsFolders(List<ContentValues> folderValues,
List<ContentValues> feedValues,
List<ContentValues> socialFeedValues,
List<ContentValues> starredCountValues,
List<ContentValues> savedSearchValues) {
public void setFeedsFolders(@NonNull List<ContentValues> folderValues,
@NonNull List<ContentValues> feedValues,
@NonNull List<ContentValues> socialFeedValues,
@NonNull List<ContentValues> starredCountValues,
@NonNull List<ContentValues> savedSearchValues) {
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
@ -276,6 +284,7 @@ public class BlurDatabaseHelper {
// note method name: this gets a set rather than a list, in case the caller wants to
// spend the up-front cost of hashing for better lookup speed rather than iteration!
@NonNull
public Set<String> getUnreadStoryHashesAsSet() {
String q = "SELECT " + DatabaseConstants.STORY_HASH +
" FROM " + DatabaseConstants.STORY_TABLE +
@ -289,6 +298,7 @@ public class BlurDatabaseHelper {
return hashes;
}
@NonNull
public Set<String> getStarredStoryHashes() {
String q = "SELECT " + DatabaseConstants.STORY_HASH +
" FROM " + DatabaseConstants.STORY_TABLE +
@ -302,6 +312,7 @@ public class BlurDatabaseHelper {
return hashes;
}
@NonNull
public Set<String> getAllStoryImages() {
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE, new String[]{DatabaseConstants.STORY_IMAGE_URLS}, null, null, null, null, null);
Set<String> urls = new HashSet<String>(c.getCount());
@ -312,6 +323,7 @@ public class BlurDatabaseHelper {
return urls;
}
@NonNull
public Set<String> getAllStoryThumbnails() {
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE, new String[]{DatabaseConstants.STORY_THUMBNAIL_URL}, null, null, null, null, null);
Set<String> urls = new HashSet<String>(c.getCount());
@ -325,7 +337,7 @@ public class BlurDatabaseHelper {
return urls;
}
public void insertStories(StoriesResponse apiResponse, StateFilter stateFilter, boolean forImmediateReading) {
public void insertStories(@NonNull StoriesResponse apiResponse, @NonNull StateFilter stateFilter, boolean forImmediateReading) {
synchronized (RW_MUTEX) {
// do not attempt to use beginTransactionNonExclusive() to reduce lock time for this very heavy set
// of calls. most versions of Android incorrectly implement the underlying SQLite calls and will
@ -428,7 +440,7 @@ public class BlurDatabaseHelper {
}
}
private void insertSingleStoryExtSync(Story story) {
private void insertSingleStoryExtSync(@NonNull Story story) {
// pick a thumbnail for the story
story.thumbnailUrl = Story.guessStoryThumbnailURL(story);
// insert the story data
@ -459,7 +471,7 @@ public class BlurDatabaseHelper {
}
}
private void insertSingleCommentExtSync(Comment comment) {
private void insertSingleCommentExtSync(@NonNull Comment comment) {
// real comments replace placeholders
int count = dbRW.delete(DatabaseConstants.COMMENT_TABLE, DatabaseConstants.COMMENT_ISPLACEHOLDER + " = ?", new String[]{"true"});
// comments always come with an updated set of replies, so remove old ones first
@ -477,7 +489,7 @@ public class BlurDatabaseHelper {
* to reflect a social action, but that the new copy is missing some fields. Attempt to merge the
* new story with the old one.
*/
public void updateStory(StoriesResponse apiResponse, StateFilter stateFilter, boolean forImmediateReading) {
public void updateStory(@NonNull StoriesResponse apiResponse, @NonNull StateFilter stateFilter, boolean forImmediateReading) {
if (apiResponse.story == null) {
com.newsblur.util.Log.e(this, "updateStory called on response with missing single story");
return;
@ -503,7 +515,7 @@ public class BlurDatabaseHelper {
* Update an existing comment and associated replies based upon a new copy received from a social
* API. Most social APIs vend an updated view that replaces any old or placeholder records.
*/
public void updateComment(CommentResponse apiResponse, String storyId) {
public void updateComment(@NonNull CommentResponse apiResponse, @Nullable String storyId) {
synchronized (RW_MUTEX) {
// comments often contain enclosed replies, so batch them.
dbRW.beginTransaction();
@ -528,7 +540,7 @@ public class BlurDatabaseHelper {
}
}
public void fixMissingStoryFeeds(Story[] stories) {
public void fixMissingStoryFeeds(@Nullable Story[] stories) {
// start off with feeds mentioned by the set of stories
Set<String> feedIds = new HashSet<String>();
for (Story story : stories) {
@ -564,7 +576,7 @@ public class BlurDatabaseHelper {
}
}
public Folder getFolder(String folderName) {
public Folder getFolder(@NonNull String folderName) {
String[] selArgs = new String[] {folderName};
String selection = DatabaseConstants.FOLDER_NAME + " = ?";
Cursor c = dbRO.query(DatabaseConstants.FOLDER_TABLE, null, selection, selArgs, null, null, null);
@ -577,13 +589,13 @@ public class BlurDatabaseHelper {
return folder;
}
public void touchStory(String hash) {
public void touchStory(@Nullable String hash) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_LAST_READ_DATE, (new Date()).getTime());
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_LAST_READ_DATE + " < 1 AND " + DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
}
public void markStoryHashesRead(Collection<String> hashes) {
public void markStoryHashesRead(@NonNull Collection<String> hashes) {
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
@ -599,7 +611,7 @@ public class BlurDatabaseHelper {
}
}
public void markStoryHashesStarred(Collection<String> hashes, boolean isStarred) {
public void markStoryHashesStarred(@NonNull Collection<String> hashes, boolean isStarred) {
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
@ -615,7 +627,7 @@ public class BlurDatabaseHelper {
}
}
public void setFeedsActive(Set<String> feedIds, boolean active) {
public void setFeedsActive(@NonNull Set<String> feedIds, boolean active) {
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
@ -631,13 +643,13 @@ public class BlurDatabaseHelper {
}
}
public void setFeedFetchPending(String feedId) {
public void setFeedFetchPending(@NonNull String feedId) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.FEED_FETCH_PENDING, true);
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
}
public boolean isFeedSetFetchPending(FeedSet fs) {
public boolean isFeedSetFetchPending(@NonNull FeedSet fs) {
if (fs.getSingleFeed() != null) {
String feedId = fs.getSingleFeed();
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE,
@ -657,7 +669,7 @@ public class BlurDatabaseHelper {
/**
* Marks a story (un)read but does not adjust counts. Must stay idempotent an time-insensitive.
*/
public void setStoryReadState(String hash, boolean read) {
public void setStoryReadState(@Nullable String hash, boolean read) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_READ, read);
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
@ -668,7 +680,8 @@ public class BlurDatabaseHelper {
*
* @return the set of feed IDs that potentially have counts impacted by the mark.
*/
public Set<FeedSet> setStoryReadState(Story story, boolean read) {
@NonNull
public Set<FeedSet> setStoryReadState(@NonNull Story story, boolean read) {
// calculate the impact surface so the caller can re-check counts if needed
Set<FeedSet> impactedFeeds = new HashSet<FeedSet>();
impactedFeeds.add(FeedSet.singleFeed(story.feedId));
@ -746,7 +759,7 @@ public class BlurDatabaseHelper {
* Marks a range of stories in a subset of feeds as read. Does not update unread counts;
* the caller must use updateLocalFeedCounts() or the /reader/feed_unread_count API.
*/
public void markStoriesRead(FeedSet fs, Long olderThan, Long newerThan) {
public void markStoriesRead(@NonNull FeedSet fs, @Nullable Long olderThan, @Nullable Long newerThan) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_READ, true);
String rangeSelection = null;
@ -774,7 +787,7 @@ public class BlurDatabaseHelper {
/**
* Get the unread count for the given feedset based on the totals in the feeds table.
*/
public int getUnreadCount(FeedSet fs, StateFilter stateFilter) {
public int getUnreadCount(@NonNull FeedSet fs, @NonNull StateFilter stateFilter) {
// if reading in starred-only mode, there are no unreads, since stories vended as starred are never unread
if (fs.isFilterSaved()) return 0;
if (fs.isAllNormal()) {
@ -802,7 +815,7 @@ public class BlurDatabaseHelper {
}
}
private int getFeedsUnreadCount(StateFilter stateFilter, String selection, String[] selArgs) {
private int getFeedsUnreadCount(@NonNull StateFilter stateFilter, @Nullable String selection, @Nullable String[] selArgs) {
int result = 0;
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, selection, selArgs, null, null, null);
while (c.moveToNext()) {
@ -816,7 +829,7 @@ public class BlurDatabaseHelper {
return result;
}
private int getSocialFeedsUnreadCount(StateFilter stateFilter, String selection, String[] selArgs) {
private int getSocialFeedsUnreadCount(@NonNull StateFilter stateFilter, @NonNull String selection, @Nullable String[] selArgs) {
int result = 0;
Cursor c = dbRO.query(DatabaseConstants.SOCIALFEED_TABLE, null, selection, selArgs, null, null, null);
while (c.moveToNext()) {
@ -829,18 +842,18 @@ public class BlurDatabaseHelper {
return result;
}
public void updateFeedCounts(String feedId, ContentValues values) {
public void updateFeedCounts(@Nullable String feedId, @Nullable ContentValues values) {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
}
public void updateSocialFeedCounts(String feedId, ContentValues values) {
public void updateSocialFeedCounts(@Nullable String feedId, @Nullable ContentValues values) {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.SOCIALFEED_TABLE, values, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[]{feedId});}
}
/**
* Refreshes the counts in the feeds/socialfeeds tables by counting stories in the story table.
*/
public void updateLocalFeedCounts(FeedSet fs) {
public void updateLocalFeedCounts(@NonNull FeedSet fs) {
// decompose the FeedSet into a list of single feeds that need to be recounted
List<String> feedIds = new ArrayList<String>();
List<String> socialFeedIds = new ArrayList<String>();
@ -883,7 +896,7 @@ public class BlurDatabaseHelper {
/**
* Get the unread count for the given feedset based on local story state.
*/
public int getLocalUnreadCount(FeedSet fs, StateFilter stateFilter) {
public int getLocalUnreadCount(@NonNull FeedSet fs, @NonNull StateFilter stateFilter) {
StringBuilder sel = new StringBuilder();
ArrayList<String> selArgs = new ArrayList<String>();
getLocalStorySelectionAndArgs(sel, selArgs, fs, stateFilter, ReadFilter.UNREAD);
@ -900,16 +913,17 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, null, null);}
}
public void enqueueAction(ReadingAction ra) {
public void enqueueAction(@NonNull ReadingAction ra) {
synchronized (RW_MUTEX) {dbRW.insertOrThrow(DatabaseConstants.ACTION_TABLE, null, ra.toContentValues());}
}
@NonNull
public Cursor getActions() {
String q = "SELECT * FROM " + DatabaseConstants.ACTION_TABLE;
return dbRO.rawQuery(q, null);
}
public void incrementActionTried(String actionId) {
public void incrementActionTried(@Nullable String actionId) {
synchronized (RW_MUTEX) {
String q = "UPDATE " + DatabaseConstants.ACTION_TABLE +
" SET " + DatabaseConstants.ACTION_TRIED + " = " + DatabaseConstants.ACTION_TRIED + " + 1" +
@ -926,7 +940,7 @@ public class BlurDatabaseHelper {
return result;
}
public void clearAction(String actionId) {
public void clearAction(@Nullable String actionId) {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.ACTION_TABLE, DatabaseConstants.ACTION_ID + " = ?", new String[]{actionId});}
}
@ -978,7 +992,7 @@ public class BlurDatabaseHelper {
}
}
public void setStoryShared(String hash, @Nullable String currentUserId, boolean shared) {
public void setStoryShared(@Nullable String hash, @Nullable String currentUserId, boolean shared) {
// get a fresh copy of the story from the DB so we can append to the shared ID set
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE,
new String[]{DatabaseConstants.STORY_SHARED_USER_IDS},
@ -1007,7 +1021,8 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
}
public String getStoryText(String hash) {
@Nullable
public String getStoryText(@Nullable String hash) {
String q = "SELECT " + DatabaseConstants.STORY_TEXT_STORY_TEXT +
" FROM " + DatabaseConstants.STORY_TEXT_TABLE +
" WHERE " + DatabaseConstants.STORY_TEXT_STORY_HASH + " = ?";
@ -1023,7 +1038,8 @@ public class BlurDatabaseHelper {
}
}
public String getStoryContent(String hash) {
@Nullable
public String getStoryContent(@Nullable String hash) {
String q = "SELECT " + DatabaseConstants.STORY_CONTENT +
" FROM " + DatabaseConstants.STORY_TABLE +
" WHERE " + DatabaseConstants.STORY_HASH + " = ?";
@ -1040,18 +1056,20 @@ public class BlurDatabaseHelper {
}
}
public void putStoryText(String hash, String text) {
public void putStoryText(@Nullable String hash, @NonNull String text) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_TEXT_STORY_HASH, hash);
values.put(DatabaseConstants.STORY_TEXT_STORY_TEXT, text);
synchronized (RW_MUTEX) {dbRW.insertOrThrow(DatabaseConstants.STORY_TEXT_TABLE, null, values);}
}
public Cursor getSocialFeedsCursor(CancellationSignal cancellationSignal) {
@NonNull
public Cursor getSocialFeedsCursor(@NonNull CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.SOCIALFEED_TABLE, null, null, null, null, null, "UPPER(" + DatabaseConstants.SOCIAL_FEED_TITLE + ") ASC", null, cancellationSignal);
}
public SocialFeed getSocialFeed(String feedId) {
@Nullable
public SocialFeed getSocialFeed(@Nullable String feedId) {
Cursor c = dbRO.query(DatabaseConstants.SOCIALFEED_TABLE, null, DatabaseConstants.SOCIAL_FEED_ID + " = ?", new String[] {feedId}, null, null, null);
SocialFeed result = null;
while (c.moveToNext()) {
@ -1062,7 +1080,7 @@ public class BlurDatabaseHelper {
}
@Nullable
public StarredCount getStarredFeedByTag(String tag) {
public StarredCount getStarredFeedByTag(@NonNull String tag) {
Cursor c = dbRO.query(DatabaseConstants.STARREDCOUNTS_TABLE, null, DatabaseConstants.STARREDCOUNTS_TAG + " = ?", new String[] {tag}, null, null, null);
StarredCount result = null;
while (c.moveToNext()) {
@ -1082,30 +1100,37 @@ public class BlurDatabaseHelper {
return folders;
}
public Cursor getFoldersCursor(CancellationSignal cancellationSignal) {
@NonNull
public Cursor getFoldersCursor(@Nullable CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.FOLDER_TABLE, null, null, null, null, null, null, null, cancellationSignal);
}
public Cursor getFeedsCursor(CancellationSignal cancellationSignal) {
@NonNull
public Cursor getFeedsCursor(@NonNull CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.FEED_TABLE, null, null, null, null, null, "UPPER(" + DatabaseConstants.FEED_TITLE + ") ASC", null, cancellationSignal);
}
public Cursor getSavedStoryCountsCursor(CancellationSignal cancellationSignal) {
@NonNull
public Cursor getSavedStoryCountsCursor(@NonNull CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.STARREDCOUNTS_TABLE, null, null, null, null, null, null, null, cancellationSignal);
}
public Cursor getSavedSearchCursor(CancellationSignal cancellationSignal) {
@NonNull
public Cursor getSavedSearchCursor(@NonNull CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.SAVED_SEARCH_TABLE, null, null, null, null, null, null, null, cancellationSignal);
}
@Nullable
public Cursor getNotifyFocusStoriesCursor() {
return rawQuery(DatabaseConstants.NOTIFY_FOCUS_STORY_QUERY, null, null);
}
@Nullable
public Cursor getNotifyUnreadStoriesCursor() {
return rawQuery(DatabaseConstants.NOTIFY_UNREAD_STORY_QUERY, null, null);
}
@NonNull
public Set<String> getNotifyFeeds() {
String q = "SELECT " + DatabaseConstants.FEED_ID + " FROM " + DatabaseConstants.FEED_TABLE +
" WHERE " + DatabaseConstants.FEED_NOTIFICATION_FILTER + " = '" + Feed.NOTIFY_FILTER_FOCUS + "'" +
@ -1122,25 +1147,8 @@ public class BlurDatabaseHelper {
return feedIds;
}
private Cursor getStoriesCursor(@Nullable FeedSet fs, CancellationSignal cancellationSignal) {
StringBuilder q = new StringBuilder(DatabaseConstants.STORY_QUERY_BASE_0);
if (fs != null && !TextUtils.isEmpty(fs.getSingleFeed())) {
q.append(DatabaseConstants.STORY_FEED_ID);
q.append(" = ");
q.append(fs.getSingleFeed());
} else {
q.append(DatabaseConstants.FEED_ACTIVE);
q.append(" = 1");
}
q.append(" ORDER BY ");
q.append(DatabaseConstants.STORY_TIMESTAMP);
q.append(" DESC LIMIT 20");
return rawQuery(q.toString(), null, cancellationSignal);
}
public Cursor getActiveStoriesCursor(FeedSet fs, CursorFilters cursorFilters, CancellationSignal cancellationSignal) {
@NonNull
public Cursor getActiveStoriesCursor(@NonNull FeedSet fs, @NonNull CursorFilters cursorFilters, @NonNull CancellationSignal cancellationSignal) {
// get the stories for this FS
Cursor result = getActiveStoriesCursorNoPrep(fs, cursorFilters.getStoryOrder(), cancellationSignal);
// if the result is blank, try to prime the session table with existing stories, in case we
@ -1154,7 +1162,8 @@ public class BlurDatabaseHelper {
return result;
}
private Cursor getActiveStoriesCursorNoPrep(FeedSet fs, StoryOrder order, CancellationSignal cancellationSignal) {
@NonNull
private Cursor getActiveStoriesCursorNoPrep(@NonNull FeedSet fs, @NonNull StoryOrder order, @NonNull CancellationSignal cancellationSignal) {
// stories aren't actually queried directly via the FeedSet and filters set in the UI. rather,
// those filters are use to push live or cached story hashes into the reading session table, and
// those hashes are used to pull story data from the story table
@ -1182,7 +1191,7 @@ public class BlurDatabaseHelper {
* criteria for the given FeedSet and filters; these hashes will be supplemented by hashes
* fetched via the API and used to actually select story data when rendering story lists.
*/
public void prepareReadingSession(FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) {
public void prepareReadingSession(@NonNull FeedSet fs, @NonNull StateFilter stateFilter, @NonNull ReadFilter readFilter) {
// a selection filter that will be used to pull active story hashes from the stories table into the reading session table
StringBuilder sel = new StringBuilder();
// any selection args that need to be used within the inner select statement
@ -1202,7 +1211,7 @@ public class BlurDatabaseHelper {
* Gets hashes of already-fetched stories that satisfy the given FeedSet and filters. Can be used
* both to populate a reading session or to count local unreads.
*/
private void getLocalStorySelectionAndArgs(StringBuilder sel, List<String> selArgs, FeedSet fs, StateFilter stateFilter, ReadFilter readFilter) {
private void getLocalStorySelectionAndArgs(@NonNull StringBuilder sel, @NonNull List<String> selArgs, @NonNull FeedSet fs, @NonNull StateFilter stateFilter, @NonNull ReadFilter readFilter) {
// if the user has requested saved stories, ignore the unreads filter, as saveds do not have this state
if (fs.isFilterSaved()) {
readFilter = ReadFilter.ALL;
@ -1283,7 +1292,7 @@ public class BlurDatabaseHelper {
}
}
public void setSessionFeedSet(FeedSet fs) {
public void setSessionFeedSet(@Nullable FeedSet fs) {
if (fs == null) {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SYNC_METADATA_TABLE, DatabaseConstants.SYNC_METADATA_KEY + " = ?", new String[] {DatabaseConstants.SYNC_METADATA_KEY_SESSION_FEED_SET});}
} else {
@ -1294,8 +1303,9 @@ public class BlurDatabaseHelper {
}
}
@Nullable
public FeedSet getSessionFeedSet() {
FeedSet fs = null;
FeedSet fs;
Cursor c = dbRO.query(DatabaseConstants.SYNC_METADATA_TABLE, null, DatabaseConstants.SYNC_METADATA_KEY + " = ?", new String[] {DatabaseConstants.SYNC_METADATA_KEY_SESSION_FEED_SET}, null, null, null, null);
if (c.getCount() < 1) return null;
c.moveToFirst();
@ -1304,20 +1314,21 @@ public class BlurDatabaseHelper {
return fs;
}
public boolean isFeedSetReady(FeedSet fs) {
public boolean isFeedSetReady(@Nullable FeedSet fs) {
return fs.equals(getSessionFeedSet());
}
public void clearClassifiersForFeed(String feedId) {
public void clearClassifiersForFeed(@Nullable String feedId) {
String[] selArgs = new String[] {feedId};
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.CLASSIFIER_TABLE, DatabaseConstants.CLASSIFIER_ID + " = ?", selArgs);}
}
public void insertClassifier(Classifier classifier) {
public void insertClassifier(@NonNull Classifier classifier) {
bulkInsertValues(DatabaseConstants.CLASSIFIER_TABLE, classifier.getContentValues());
}
public Classifier getClassifierForFeed(String feedId) {
@NonNull
public Classifier getClassifierForFeed(@Nullable String feedId) {
String[] selArgs = new String[] {feedId};
Cursor c = dbRO.query(DatabaseConstants.CLASSIFIER_TABLE, null, DatabaseConstants.CLASSIFIER_ID + " = ?", selArgs, null, null, null);
Classifier classifier = Classifier.fromCursor(c);
@ -1326,7 +1337,8 @@ public class BlurDatabaseHelper {
return classifier;
}
public List<Comment> getComments(String storyId) {
@NonNull
public List<Comment> getComments(@NonNull String storyId) {
String[] selArgs = new String[] {storyId};
String selection = DatabaseConstants.COMMENT_STORYID + " = ?";
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, null, selection, selArgs, null, null, null);
@ -1338,7 +1350,8 @@ public class BlurDatabaseHelper {
return comments;
}
public Comment getComment(String storyId, String userId) {
@Nullable
public Comment getComment(@Nullable String storyId, @Nullable String userId) {
String selection = DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?";
String[] selArgs = new String[] {storyId, userId};
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, null, selection, selArgs, null, null, null);
@ -1354,7 +1367,7 @@ public class BlurDatabaseHelper {
* will show up in the UI with reduced functionality until the server gets back to us with
* an ID at which time the placeholder will be removed.
*/
public void insertCommentPlaceholder(String storyId, @Nullable String userId, String commentText) {
public void insertCommentPlaceholder(@Nullable String storyId, @Nullable String userId, @Nullable String commentText) {
Comment comment = new Comment();
comment.isPlaceholder = true;
comment.id = Comment.PLACEHOLDER_COMMENT_ID + storyId + userId;
@ -1376,23 +1389,23 @@ public class BlurDatabaseHelper {
}
}
public void editReply(String replyId, String replyText) {
public void editReply(@Nullable String replyId, @Nullable String replyText) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.REPLY_TEXT, replyText);
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.REPLY_TABLE, values, DatabaseConstants.REPLY_ID + " = ?", new String[]{replyId});}
}
public void deleteReply(String replyId) {
public void deleteReply(@Nullable String replyId) {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_ID + " = ?", new String[]{replyId});}
}
public void clearSelfComments(String storyId, @Nullable String userId) {
public void clearSelfComments(@Nullable String storyId, @Nullable String userId) {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.COMMENT_TABLE,
DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?",
new String[]{storyId, userId});}
}
public void setCommentLiked(String storyId, String commentUserId, @Nullable String currentUserId, boolean liked) {
public void setCommentLiked(@Nullable String storyId, @Nullable String commentUserId, @Nullable String currentUserId, boolean liked) {
// get a fresh copy of the story from the DB so we can append to the shared ID set
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
null,
@ -1421,7 +1434,8 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.COMMENT_TABLE, values, DatabaseConstants.COMMENT_ID + " = ?", new String[]{comment.id});}
}
public UserProfile getUserProfile(String userId) {
@Nullable
public UserProfile getUserProfile(@Nullable String userId) {
String[] selArgs = new String[] {userId};
String selection = DatabaseConstants.USER_USERID + " = ?";
Cursor c = dbRO.query(DatabaseConstants.USER_TABLE, null, selection, selArgs, null, null, null);
@ -1430,7 +1444,8 @@ public class BlurDatabaseHelper {
return profile;
}
public List<Reply> getCommentReplies(String commentId) {
@NonNull
public List<Reply> getCommentReplies(@Nullable String commentId) {
String[] selArgs = new String[] {commentId};
String selection = DatabaseConstants.REPLY_COMMENTID+ " = ?";
Cursor c = dbRO.query(DatabaseConstants.REPLY_TABLE, null, selection, selArgs, null, null, DatabaseConstants.REPLY_DATE + " ASC");
@ -1442,7 +1457,7 @@ public class BlurDatabaseHelper {
return replies;
}
public void insertReplyPlaceholder(String storyId, @Nullable String userId, String commentUserId, String replyText) {
public void insertReplyPlaceholder(@Nullable String storyId, @Nullable String userId, @Nullable String commentUserId, @Nullable String replyText) {
// get a fresh copy of the comment so we can discover the ID
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
null,
@ -1467,14 +1482,14 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.REPLY_TABLE, null, reply.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
}
public void putStoryDismissed(String storyHash) {
public void putStoryDismissed(@Nullable String storyHash) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.NOTIFY_DISMISS_STORY_HASH, storyHash);
values.put(DatabaseConstants.NOTIFY_DISMISS_TIME, Calendar.getInstance().getTime().getTime());
synchronized (RW_MUTEX) {dbRW.insertOrThrow(DatabaseConstants.NOTIFY_DISMISS_TABLE, null, values);}
}
public boolean isStoryDismissed(String storyHash) {
public boolean isStoryDismissed(@Nullable String storyHash) {
String[] selArgs = new String[] {storyHash};
String selection = DatabaseConstants.NOTIFY_DISMISS_STORY_HASH + " = ?";
Cursor c = dbRO.query(DatabaseConstants.NOTIFY_DISMISS_TABLE, null, selection, selArgs, null, null, null);
@ -1494,7 +1509,7 @@ public class BlurDatabaseHelper {
}
}
private void putFeedTagsExtSync(String feedId, Collection<String> tags) {
private void putFeedTagsExtSync(@Nullable String feedId, @NonNull Collection<String> tags) {
dbRW.delete(DatabaseConstants.FEED_TAGS_TABLE,
DatabaseConstants.FEED_TAGS_FEEDID + " = ?",
new String[]{feedId}
@ -1509,7 +1524,8 @@ public class BlurDatabaseHelper {
bulkInsertValuesExtSync(DatabaseConstants.FEED_TAGS_TABLE, valuesList);
}
public List<String> getTagsForFeed(String feedId) {
@NonNull
public List<String> getTagsForFeed(@Nullable String feedId) {
Cursor c = dbRO.query(DatabaseConstants.FEED_TAGS_TABLE,
new String[]{DatabaseConstants.FEED_TAGS_TAG},
DatabaseConstants.FEED_TAGS_FEEDID + " = ?",
@ -1526,7 +1542,7 @@ public class BlurDatabaseHelper {
return result;
}
private void putFeedAuthorsExtSync(String feedId, Collection<String> authors) {
private void putFeedAuthorsExtSync(@Nullable String feedId, @NonNull Collection<String> authors) {
dbRW.delete(DatabaseConstants.FEED_AUTHORS_TABLE,
DatabaseConstants.FEED_AUTHORS_FEEDID + " = ?",
new String[]{feedId}
@ -1541,7 +1557,8 @@ public class BlurDatabaseHelper {
bulkInsertValuesExtSync(DatabaseConstants.FEED_AUTHORS_TABLE, valuesList);
}
public List<String> getAuthorsForFeed(String feedId) {
@NonNull
public List<String> getAuthorsForFeed(@Nullable String feedId) {
Cursor c = dbRO.query(DatabaseConstants.FEED_AUTHORS_TABLE,
new String[]{DatabaseConstants.FEED_AUTHORS_AUTHOR},
DatabaseConstants.FEED_AUTHORS_FEEDID + " = ?",
@ -1558,18 +1575,19 @@ public class BlurDatabaseHelper {
return result;
}
public void renameFeed(String feedId, String newFeedName) {
public void renameFeed(@Nullable String feedId, @Nullable String newFeedName) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.FEED_TITLE, newFeedName);
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.FEED_TABLE, values, DatabaseConstants.FEED_ID + " = ?", new String[]{feedId});}
}
public static void closeQuietly(Cursor c) {
public static void closeQuietly(@Nullable Cursor c) {
if (c == null) return;
try {c.close();} catch (Exception e) {
}
}
@Nullable
private static String conjoinSelections(CharSequence... args) {
StringBuilder s = null;
for (CharSequence c : args) {
@ -1589,7 +1607,8 @@ public class BlurDatabaseHelper {
* Invoke the rawQuery() method on our read-only SQLiteDatabase memeber using the provided CancellationSignal
* only if the device's platform provides support.
*/
private Cursor rawQuery(String sql, String[] selectionArgs, CancellationSignal cancellationSignal) {
@Nullable
private Cursor rawQuery(@NonNull String sql, @Nullable String[] selectionArgs, @Nullable CancellationSignal cancellationSignal) {
if (AppConstants.VERBOSE_LOG_DB) {
Log.d(this.getClass().getName(), String.format("DB rawQuery: '%s' with args: %s", sql, java.util.Arrays.toString(selectionArgs)));
}
@ -1600,15 +1619,18 @@ public class BlurDatabaseHelper {
* Invoke the query() method on our read-only SQLiteDatabase memeber using the provided CancellationSignal
* only if the device's platform provides support.
*/
private Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
@NonNull
private Cursor query(boolean distinct, @NonNull String table, @Nullable String[] columns, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String groupBy, @Nullable String having, @Nullable String orderBy, @Nullable String limit, @NonNull CancellationSignal cancellationSignal) {
return dbRO.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal);
}
public FeedSet feedSetFromFolderName(String folderName) {
@NonNull
public FeedSet feedSetFromFolderName(@NonNull String folderName) {
return FeedSet.folder(folderName, getFeedIdsRecursive(folderName));
}
private Set<String> getFeedIdsRecursive(String folderName) {
@NonNull
private Set<String> getFeedIdsRecursive(@NonNull String folderName) {
Folder folder = getFolder(folderName);
if (folder == null) return emptySet();
Set<String> feedIds = new HashSet<>(folder.feedIds);

View file

@ -275,7 +275,7 @@ public class ItemSetFragment extends NbFragment {
}
protected FeedSet getFeedSet() {
return ((ItemsList) getActivity()).getFeedSet();
return ((ItemsList) requireActivity()).getFeedSet();
}
public void hasUpdated() {
@ -299,7 +299,7 @@ public class ItemSetFragment extends NbFragment {
ensureSufficientStories();
}
private void setCursor(Cursor cursor) {
private void setCursor(@Nullable Cursor cursor) {
if (cursor != null) {
if (!dbHelper.isFeedSetReady(getFeedSet())) {
// the DB hasn't caught up yet from the last story list; don't display stale stories.

View file

@ -1,6 +1,8 @@
package com.newsblur.fragment
import android.content.*
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Typeface
@ -9,8 +11,12 @@ import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.*
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView.HitTestResult
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
@ -38,8 +44,20 @@ import com.newsblur.service.NbSyncManager.UPDATE_SOCIAL
import com.newsblur.service.NbSyncManager.UPDATE_STORY
import com.newsblur.service.NbSyncManager.UPDATE_TEXT
import com.newsblur.service.OriginalTextService
import com.newsblur.util.*
import com.newsblur.util.DefaultFeedView
import com.newsblur.util.FeedSet
import com.newsblur.util.FeedUtils
import com.newsblur.util.FileCache
import com.newsblur.util.Font
import com.newsblur.util.ImageLoader
import com.newsblur.util.MarkStoryReadBehavior
import com.newsblur.util.PrefConstants.ThemeValue
import com.newsblur.util.PrefsUtils
import com.newsblur.util.ReadingTextSize
import com.newsblur.util.StoryChangesState
import com.newsblur.util.StoryUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import java.util.regex.Pattern
import javax.inject.Inject
@ -107,6 +125,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
private lateinit var binding: FragmentReadingitemBinding
private lateinit var readingItemActionsBinding: ReadingItemActionsBinding
private lateinit var markStoryReadBehavior: MarkStoryReadBehavior
private var sampledQueue: SampledQueue? = null
@ -148,13 +167,6 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
super.onDestroyView()
}
override fun onDestroy() {
binding.readingWebview.setOnTouchListener(null)
binding.root.setOnTouchListener(null)
requireActivity().window.decorView.setOnSystemUiVisibilityChangeListener(null)
super.onDestroy()
}
// WebViews don't automatically pause content like audio and video when they lose focus. Chain our own
// state into the webview so it behaves.
override fun onPause() {
@ -169,8 +181,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_readingitem, container, false)
binding = FragmentReadingitemBinding.bind(view)
binding = FragmentReadingitemBinding.inflate(inflater, container, false)
readingItemActionsBinding = ReadingItemActionsBinding.bind(binding.root)
val readingActivity = requireActivity() as Reading
@ -194,7 +205,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
binding.readingScrollview.registerScrollChangeListener(readingActivity)
return view
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -423,8 +434,10 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
R.id.menu_go_to_feed -> {
val feed = dbHelper.getFeed(story!!.feedId)
val fs = FeedSet.singleFeed(feed.feedId)
FeedItemsList.startActivity(requireContext(), fs, feed, null, null)
feed?.let {
val fs = FeedSet.singleFeed(it.feedId)
FeedItemsList.startActivity(requireContext(), fs, it, null, null)
}
true
}
else -> {

View file

@ -56,7 +56,7 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
private fun doInBackground() {
if (context == null || story == null || story.id.isNullOrEmpty()) return
comments.addAll(fragment.dbHelper.getComments(story.id))
comments.addAll(fragment.dbHelper.getComments(story.id!!))
// users by whom we saw non-pseudo comments
val commentingUserIds: MutableSet<String> = HashSet()

View file

@ -144,7 +144,7 @@ public class NotificationUtils {
NotificationCompat.Builder nb = new NotificationCompat.Builder(context, context.getString(R.string.story_notification_channel_id))
.setContentTitle(title.toString())
.setContentText(story.shortContent)
.setSmallIcon(R.drawable.logo_monochrome)
.setSmallIcon(R.drawable.ic_logo_monochrome)
.setContentIntent(pendingIntent)
.setDeleteIntent(dismissPendingIntent)
.setAutoCancel(true)

View file

@ -0,0 +1,68 @@
@file:Suppress("DEPRECATION")
package com.newsblur.util
import android.app.Activity
import android.os.Build
import com.newsblur.R
object PendingTransitionUtils {
@JvmStatic
fun overrideEnterTransition(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
activity.overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_OPEN,
R.anim.slide_in_from_right,
R.anim.slide_out_to_left,
)
} else {
activity.overridePendingTransition(
R.anim.slide_in_from_right,
R.anim.slide_out_to_left,
)
}
}
@JvmStatic
fun overrideNoEnterTransition(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
activity.overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_OPEN,
0,
0,
)
} else {
activity.overridePendingTransition(
0,
0,
)
}
}
@JvmStatic
fun overrideExitTransition(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
activity.overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_CLOSE,
R.anim.slide_in_from_left,
R.anim.slide_out_to_right,
)
} else {
activity.overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right)
}
}
@JvmStatic
fun overrideNoExitTransition(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
activity.overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_CLOSE,
0,
0,
)
} else {
activity.overridePendingTransition(0, 0)
}
}
}

View file

@ -9,6 +9,9 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Serializable;
import com.newsblur.database.BlurDatabaseHelper;
@ -88,21 +91,21 @@ public class ReadingAction implements Serializable {
return tried;
}
public static ReadingAction markStoryRead(String hash) {
public static ReadingAction markStoryRead(@Nullable String hash) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_READ;
ra.storyHash = hash;
return ra;
}
public static ReadingAction markStoryUnread(String hash) {
public static ReadingAction markStoryUnread(@Nullable String hash) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_UNREAD;
ra.storyHash = hash;
return ra;
}
public static ReadingAction saveStory(String hash, List<String> userTags) {
public static ReadingAction saveStory(@Nullable String hash, @Nullable List<String> userTags) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.SAVE;
ra.storyHash = hash;
@ -114,14 +117,14 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction unsaveStory(String hash) {
public static ReadingAction unsaveStory(@Nullable String hash) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UNSAVE;
ra.storyHash = hash;
return ra;
}
public static ReadingAction markFeedRead(FeedSet fs, Long olderThan, Long newerThan) {
public static ReadingAction markFeedRead(@NonNull FeedSet fs, @Nullable Long olderThan, @Nullable Long newerThan) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MARK_READ;
ra.feedSet = fs;
@ -130,7 +133,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction shareStory(String hash, String storyId, String feedId, String sourceUserId, String commentReplyText) {
public static ReadingAction shareStory(@Nullable String hash, @Nullable String storyId, @Nullable String feedId, @Nullable String sourceUserId, @Nullable String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.SHARE;
ra.storyHash = hash;
@ -141,7 +144,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction unshareStory(String hash, String storyId, String feedId) {
public static ReadingAction unshareStory(@Nullable String hash, @Nullable String storyId, @Nullable String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UNSHARE;
ra.storyHash = hash;
@ -150,7 +153,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction likeComment(String storyId, String commentUserId, String feedId) {
public static ReadingAction likeComment(@Nullable String storyId, @Nullable String commentUserId, @Nullable String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.LIKE_COMMENT;
ra.storyId = storyId;
@ -159,7 +162,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction unlikeComment(String storyId, String commentUserId, String feedId) {
public static ReadingAction unlikeComment(@Nullable String storyId, @Nullable String commentUserId, @Nullable String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UNLIKE_COMMENT;
ra.storyId = storyId;
@ -168,7 +171,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction replyToComment(String storyId, String feedId, String commentUserId, String commentReplyText) {
public static ReadingAction replyToComment(@Nullable String storyId, @Nullable String feedId, @Nullable String commentUserId, @Nullable String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.REPLY;
ra.storyId = storyId;
@ -178,7 +181,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction updateReply(String storyId, String feedId, String commentUserId, String replyId, String commentReplyText) {
public static ReadingAction updateReply(@Nullable String storyId, @Nullable String feedId, @Nullable String commentUserId, @Nullable String replyId, @Nullable String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.EDIT_REPLY;
ra.storyId = storyId;
@ -189,7 +192,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction deleteReply(String storyId, String feedId, String commentUserId, String replyId) {
public static ReadingAction deleteReply(@Nullable String storyId, @Nullable String feedId, @Nullable String commentUserId, @Nullable String replyId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.DELETE_REPLY;
ra.storyId = storyId;
@ -199,7 +202,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction muteFeeds(Set<String> activeFeedIds, Set<String> modifiedFeedIds) {
public static ReadingAction muteFeeds(@NonNull Set<String> activeFeedIds, @NonNull Set<String> modifiedFeedIds) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MUTE_FEEDS;
ra.activeFeedIds = activeFeedIds;
@ -207,7 +210,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction unmuteFeeds(Set<String> activeFeedIds, Set<String> modifiedFeedIds) {
public static ReadingAction unmuteFeeds(@NonNull Set<String> activeFeedIds, @NonNull Set<String> modifiedFeedIds) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UNMUTE_FEEDS;
ra.activeFeedIds = activeFeedIds;
@ -215,7 +218,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction setNotify(String feedId, List<String> notifyTypes, String notifyFilter) {
public static ReadingAction setNotify(@Nullable String feedId, @Nullable List<String> notifyTypes, @Nullable String notifyFilter) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.SET_NOTIFY;
ra.feedId = feedId;
@ -228,14 +231,14 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction instaFetch(String feedId) {
public static ReadingAction instaFetch(@Nullable String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.INSTA_FETCH;
ra.feedId = feedId;
return ra;
}
public static ReadingAction updateIntel(String feedId, Classifier classifier, FeedSet fs) {
public static ReadingAction updateIntel(@Nullable String feedId, @Nullable Classifier classifier, @Nullable FeedSet fs) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UPDATE_INTEL;
ra.feedId = feedId;
@ -244,7 +247,7 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction renameFeed(String feedId, String newFeedName) {
public static ReadingAction renameFeed(@Nullable String feedId, @Nullable String newFeedName) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.RENAME_FEED;
ra.feedId = feedId;
@ -265,7 +268,7 @@ public class ReadingAction implements Serializable {
return values;
}
public static ReadingAction fromCursor(Cursor c) {
public static ReadingAction fromCursor(@NonNull Cursor c) {
long time = c.getLong(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_TIME));
int tried = c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_TRIED));
String params = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_PARAMS));
@ -278,7 +281,7 @@ public class ReadingAction implements Serializable {
/**
* Execute this action remotely via the API.
*/
public NewsBlurResponse doRemote(APIManager apiManager, BlurDatabaseHelper dbHelper, StateFilter stateFilter) {
public NewsBlurResponse doRemote(@NonNull APIManager apiManager, @NonNull BlurDatabaseHelper dbHelper, @NonNull StateFilter stateFilter) {
// generic response to return
NewsBlurResponse result = null;
// optional specific responses that are locally actionable
@ -392,7 +395,7 @@ public class ReadingAction implements Serializable {
return result;
}
public int doLocal(Context context, BlurDatabaseHelper dbHelper) {
public int doLocal(@NonNull Context context, @NonNull BlurDatabaseHelper dbHelper) {
return doLocal(context, dbHelper, false);
}
@ -403,7 +406,7 @@ public class ReadingAction implements Serializable {
*
* @return the union of update impact flags that resulted from this action.
*/
public int doLocal(Context context, BlurDatabaseHelper dbHelper, boolean isFollowup) {
public int doLocal(@NonNull Context context, @NonNull BlurDatabaseHelper dbHelper, boolean isFollowup) {
String userId = PrefsUtils.getUserId(context);
int impact = 0;
switch (type) {

View file

@ -232,10 +232,10 @@ public class UIUtils {
public void run() {
Intent intent = activity.getIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.overridePendingTransition(0, 0);
PendingTransitionUtils.overrideNoExitTransition(activity);
activity.finish();
activity.overridePendingTransition(0, 0);
PendingTransitionUtils.overrideNoEnterTransition(activity);
activity.startActivity(intent);
}
});

View file

@ -2,6 +2,7 @@ package com.newsblur.viewModel
import android.database.Cursor
import android.os.CancellationSignal
import android.os.OperationCanceledException
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -9,6 +10,7 @@ import androidx.lifecycle.viewModelScope
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.util.CursorFilters
import com.newsblur.util.FeedSet
import com.newsblur.util.Log
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -24,9 +26,13 @@ class StoriesViewModel
fun getActiveStories(fs: FeedSet, cursorFilters: CursorFilters) {
viewModelScope.launch(Dispatchers.IO) {
try {
dbHelper.getActiveStoriesCursor(fs, cursorFilters, cancellationSignal).let {
_activeStoriesLiveData.postValue(it)
}
} catch (e: OperationCanceledException) {
Log.e(this.javaClass.name, "Caught ${e.javaClass.name} in getActiveStories.")
}
}
}

View file

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="48dp"
android:height="48dp"
android:viewportWidth="432"
android:viewportHeight="432">
<path android:pathData="M0,0h432v432h-432z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="216"
android:endY="432"
android:startX="216"
android:startY="0"
android:type="linear">
<item
android:color="#FFE6A33F"
android:offset="0" />
<item
android:color="#FFC35F28"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</vector>

View file

@ -0,0 +1,51 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="48dp"
android:height="48dp"
android:viewportWidth="432"
android:viewportHeight="432">
<path
android:fillType="evenOdd"
android:pathData="M237.9,135.78L243.89,148.68L257.15,143.83C270.23,151.34 281.11,162.28 288.54,175.42L283.74,188.53L296.37,194.4C298.31,201.45 299.34,208.88 299.34,216.55C299.34,224.35 298.27,231.9 296.27,239.07L283.74,244.89L288.46,257.8C280.97,271 270,281.97 256.8,289.46L243.89,284.74L238.07,297.27C230.9,299.27 223.35,300.34 215.55,300.34C207.88,300.34 200.45,299.31 193.39,297.37L187.53,284.74L174.42,289.54C161.28,282.11 150.34,271.23 142.83,258.15L147.68,244.89L134.78,238.9C132.8,231.78 131.75,224.28 131.75,216.55C131.75,208.94 132.77,201.58 134.67,194.57L147.68,188.53L142.75,175.07C150.2,162.05 161.04,151.2 174.07,143.75L187.53,148.68L193.57,135.67C200.57,133.77 207.94,132.75 215.55,132.75C223.28,132.75 230.78,133.8 237.9,135.78ZM263.87,141.37C274.76,148.4 284.04,157.73 291.01,168.67L295.09,157.49C289.4,149.84 282.62,143.03 275,137.3L263.87,141.37ZM302.88,197.42L313.65,202.42C314.31,207.03 314.65,211.75 314.65,216.55C314.65,221.46 314.29,226.3 313.6,231.02L302.8,236.04C304.2,229.76 304.94,223.24 304.94,216.55C304.94,209.98 304.23,203.58 302.88,197.42ZM321.27,216.55C321.27,220.32 321.07,224.06 320.68,227.74L330.49,223.18C330.61,220.99 330.68,218.77 330.68,216.55C330.68,214.43 330.62,212.33 330.51,210.25L320.71,205.7C321.08,209.27 321.27,212.88 321.27,216.55ZM304.48,131.84L306.72,125.7L300.59,127.94C301.2,128.52 301.8,129.11 302.39,129.7C303.1,130.41 303.79,131.12 304.48,131.84ZM218.39,93.76L215.71,88L213.04,93.75C213.87,93.74 214.71,93.73 215.55,93.73C216.49,93.73 217.44,93.74 218.39,93.76ZM209.4,101.58C211.44,101.47 213.49,101.42 215.55,101.42C217.72,101.42 219.88,101.47 222.02,101.6L226.57,111.4C222.95,111.02 219.27,110.82 215.55,110.82C211.94,110.82 208.38,111.01 204.86,111.36L209.4,101.58ZM138.75,130.84L148.89,134.54C143.27,139.12 138.12,144.27 133.54,149.89L129.84,139.75C132.65,136.62 135.62,133.65 138.75,130.84ZM130.59,127.85L124.7,125.7L126.85,131.59C127.46,130.95 128.08,130.32 128.7,129.7C129.32,129.08 129.96,128.46 130.59,127.85ZM292.43,130.92C295.62,133.78 298.64,136.82 301.49,140.01L297.77,150.17C293.17,144.47 287.98,139.26 282.31,134.63L292.43,130.92ZM338.33,213.88C338.35,214.77 338.36,215.66 338.36,216.55C338.36,217.55 338.35,218.54 338.33,219.54L344.42,216.71L338.33,213.88ZM301.4,293.17L297.69,283.03C293.03,288.78 287.78,294.03 282.03,298.69L292.17,302.4C295.42,299.5 298.5,296.42 301.4,293.17ZM300.35,305.39C301.03,304.73 301.71,304.07 302.39,303.39C303.07,302.71 303.73,302.03 304.39,301.35L306.72,307.72L300.35,305.39ZM222.18,331.49L226.74,321.68C223.06,322.07 219.32,322.27 215.55,322.27C211.88,322.27 208.27,322.08 204.7,321.71L209.25,331.51C211.33,331.62 213.43,331.68 215.55,331.68C217.77,331.68 219.99,331.61 222.18,331.49ZM212.88,339.33C213.77,339.35 214.66,339.36 215.55,339.36C216.55,339.36 217.54,339.35 218.54,339.33L215.71,345.42L212.88,339.33ZM139.01,302.49L149.17,298.77C143.47,294.17 138.26,288.98 133.63,283.31L129.92,293.43C132.78,296.62 135.82,299.64 139.01,302.49ZM100.6,223.02C100.48,220.88 100.42,218.72 100.42,216.55C100.42,214.49 100.47,212.44 100.58,210.4L110.36,205.86C110.01,209.38 109.82,212.94 109.82,216.55C109.82,220.27 110.02,223.95 110.4,227.58L100.6,223.02ZM92.75,214.04L87,216.71L92.76,219.39C92.74,218.44 92.73,217.49 92.73,216.54C92.73,215.71 92.74,214.87 92.75,214.04ZM126.94,301.59C127.52,302.2 128.11,302.8 128.7,303.39C129.41,304.1 130.12,304.79 130.84,305.48L124.7,307.72L126.94,301.59ZM290.93,264.54L295.01,275.7C289.26,283.41 282.41,290.26 274.7,296.01L263.54,291.93C274.54,284.9 283.9,275.54 290.93,264.54ZM235.04,303.79L230.02,314.6C225.3,315.29 220.46,315.65 215.55,315.65C210.75,315.65 206.03,315.31 201.42,314.65L196.41,303.88C202.58,305.22 208.98,305.94 215.55,305.94C222.24,305.94 228.76,305.2 235.04,303.79ZM167.67,292.01L156.5,296.09C148.84,290.4 142.03,283.62 136.3,276L140.37,264.87C147.4,275.76 156.73,285.04 167.67,292.01ZM128.26,235.87L117.47,230.86C116.79,226.19 116.44,221.41 116.44,216.55C116.44,211.81 116.78,207.15 117.42,202.58L128.18,197.59C126.85,203.7 126.16,210.04 126.16,216.55C126.16,223.18 126.88,229.65 128.26,235.87ZM167.34,141.29L156.2,137.22C148.63,142.9 141.9,149.63 136.22,157.2L140.29,168.34C147.26,157.51 156.51,148.26 167.34,141.29ZM215.55,117.44C210.81,117.44 206.15,117.78 201.58,118.42L196.59,129.18C202.7,127.85 209.04,127.16 215.55,127.16C222.18,127.16 228.65,127.88 234.87,129.26L229.86,118.47C225.19,117.79 220.41,117.44 215.55,117.44ZM215.55,283.71C178.51,283.71 148.38,253.58 148.38,216.55C148.38,179.51 178.51,149.38 215.55,149.38C252.58,149.38 282.71,179.51 282.71,216.55C282.71,253.58 252.58,283.71 215.55,283.71ZM215.55,160.48C184.63,160.48 159.48,185.63 159.48,216.55C159.48,247.46 184.63,272.61 215.55,272.61C246.46,272.61 271.61,247.46 271.61,216.55C271.61,185.63 246.46,160.48 215.55,160.48Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="124.5"
android:endY="52"
android:startX="252.5"
android:startY="276.5"
android:type="linear">
<item
android:color="#FFEEDA5C"
android:offset="0" />
<item
android:color="#FFF0DE81"
android:offset="0.23" />
<item
android:color="#FFF2E19C"
android:offset="0.45" />
<item
android:color="#FFF3E3B0"
android:offset="0.66" />
<item
android:color="#FFF4E5BC"
android:offset="0.85" />
<item
android:color="#FFF3E3AA"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="432"
android:viewportHeight="432">
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M237.9,135.78L243.89,148.68L257.15,143.83C270.23,151.34 281.11,162.28 288.54,175.42L283.74,188.53L296.37,194.4C298.31,201.45 299.34,208.88 299.34,216.55C299.34,224.35 298.27,231.9 296.27,239.07L283.74,244.89L288.46,257.8C280.97,271 270,281.97 256.8,289.46L243.89,284.74L238.07,297.27C230.9,299.27 223.35,300.34 215.55,300.34C207.88,300.34 200.45,299.31 193.39,297.37L187.53,284.74L174.42,289.54C161.28,282.11 150.34,271.23 142.83,258.15L147.68,244.89L134.78,238.9C132.8,231.78 131.75,224.28 131.75,216.55C131.75,208.94 132.77,201.58 134.67,194.57L147.68,188.53L142.75,175.07C150.2,162.05 161.04,151.2 174.07,143.75L187.53,148.68L193.57,135.67C200.57,133.77 207.94,132.75 215.55,132.75C223.28,132.75 230.78,133.8 237.9,135.78ZM263.87,141.37C274.76,148.4 284.04,157.73 291.01,168.67L295.09,157.49C289.4,149.84 282.62,143.03 275,137.3L263.87,141.37ZM302.88,197.42L313.65,202.42C314.31,207.03 314.65,211.75 314.65,216.55C314.65,221.46 314.29,226.3 313.6,231.02L302.8,236.04C304.2,229.76 304.94,223.24 304.94,216.55C304.94,209.98 304.23,203.58 302.88,197.42ZM321.27,216.55C321.27,220.32 321.07,224.06 320.68,227.74L330.49,223.18C330.61,220.99 330.68,218.77 330.68,216.55C330.68,214.43 330.62,212.33 330.51,210.25L320.71,205.7C321.08,209.27 321.27,212.88 321.27,216.55ZM304.48,131.84L306.72,125.7L300.59,127.94C301.2,128.52 301.8,129.11 302.39,129.7C303.1,130.41 303.79,131.12 304.48,131.84ZM218.39,93.76L215.71,88L213.04,93.75C213.87,93.74 214.71,93.73 215.55,93.73C216.49,93.73 217.44,93.74 218.39,93.76ZM209.4,101.58C211.44,101.47 213.49,101.42 215.55,101.42C217.72,101.42 219.88,101.47 222.02,101.6L226.57,111.4C222.95,111.02 219.27,110.82 215.55,110.82C211.94,110.82 208.38,111.01 204.86,111.36L209.4,101.58ZM138.75,130.84L148.89,134.54C143.27,139.12 138.12,144.27 133.54,149.89L129.84,139.75C132.65,136.62 135.62,133.65 138.75,130.84ZM130.59,127.85L124.7,125.7L126.85,131.59C127.46,130.95 128.08,130.32 128.7,129.7C129.32,129.08 129.96,128.46 130.59,127.85ZM292.43,130.92C295.62,133.78 298.64,136.82 301.49,140.01L297.77,150.17C293.17,144.47 287.98,139.26 282.31,134.63L292.43,130.92ZM338.33,213.88C338.35,214.77 338.36,215.66 338.36,216.55C338.36,217.55 338.35,218.54 338.33,219.54L344.42,216.71L338.33,213.88ZM301.4,293.17L297.69,283.03C293.03,288.78 287.78,294.03 282.03,298.69L292.17,302.4C295.42,299.5 298.5,296.42 301.4,293.17ZM300.35,305.39C301.03,304.73 301.71,304.07 302.39,303.39C303.07,302.71 303.73,302.03 304.39,301.35L306.72,307.72L300.35,305.39ZM222.18,331.49L226.74,321.68C223.06,322.07 219.32,322.27 215.55,322.27C211.88,322.27 208.27,322.08 204.7,321.71L209.25,331.51C211.33,331.62 213.43,331.68 215.55,331.68C217.77,331.68 219.99,331.61 222.18,331.49ZM212.88,339.33C213.77,339.35 214.66,339.36 215.55,339.36C216.55,339.36 217.54,339.35 218.54,339.33L215.71,345.42L212.88,339.33ZM139.01,302.49L149.17,298.77C143.47,294.17 138.26,288.98 133.63,283.31L129.92,293.43C132.78,296.62 135.82,299.64 139.01,302.49ZM100.6,223.02C100.48,220.88 100.42,218.72 100.42,216.55C100.42,214.49 100.47,212.44 100.58,210.4L110.36,205.86C110.01,209.38 109.82,212.94 109.82,216.55C109.82,220.27 110.02,223.95 110.4,227.58L100.6,223.02ZM92.75,214.04L87,216.71L92.76,219.39C92.74,218.44 92.73,217.49 92.73,216.54C92.73,215.71 92.74,214.87 92.75,214.04ZM126.94,301.59C127.52,302.2 128.11,302.8 128.7,303.39C129.41,304.1 130.12,304.79 130.84,305.48L124.7,307.72L126.94,301.59ZM290.93,264.54L295.01,275.7C289.26,283.41 282.41,290.26 274.7,296.01L263.54,291.93C274.54,284.9 283.9,275.54 290.93,264.54ZM235.04,303.79L230.02,314.6C225.3,315.29 220.46,315.65 215.55,315.65C210.75,315.65 206.03,315.31 201.42,314.65L196.41,303.88C202.58,305.22 208.98,305.94 215.55,305.94C222.24,305.94 228.76,305.2 235.04,303.79ZM167.67,292.01L156.5,296.09C148.84,290.4 142.03,283.62 136.3,276L140.37,264.87C147.4,275.76 156.73,285.04 167.67,292.01ZM128.26,235.87L117.47,230.86C116.79,226.19 116.44,221.41 116.44,216.55C116.44,211.81 116.78,207.15 117.42,202.58L128.18,197.59C126.85,203.7 126.16,210.04 126.16,216.55C126.16,223.18 126.88,229.65 128.26,235.87ZM167.34,141.29L156.2,137.22C148.63,142.9 141.9,149.63 136.22,157.2L140.29,168.34C147.26,157.51 156.51,148.26 167.34,141.29ZM215.55,117.44C210.81,117.44 206.15,117.78 201.58,118.42L196.59,129.18C202.7,127.85 209.04,127.16 215.55,127.16C222.18,127.16 228.65,127.88 234.87,129.26L229.86,118.47C225.19,117.79 220.41,117.44 215.55,117.44ZM215.55,283.71C178.51,283.71 148.38,253.58 148.38,216.55C148.38,179.51 178.51,149.38 215.55,149.38C252.58,149.38 282.71,179.51 282.71,216.55C282.71,253.58 252.58,283.71 215.55,283.71ZM215.55,160.48C184.63,160.48 159.48,185.63 159.48,216.55C159.48,247.46 184.63,272.61 215.55,272.61C246.46,272.61 271.61,247.46 271.61,216.55C271.61,185.63 246.46,160.48 215.55,160.48Z" />
</vector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_logo_background" />
<foreground android:drawable="@drawable/ic_logo_foreground" />
<monochrome android:drawable="@drawable/ic_logo_monochrome" />
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_logo_background" />
<foreground android:drawable="@drawable/ic_logo_foreground" />
</adaptive-icon>

View file

@ -746,6 +746,8 @@
<string name="notification_permissions_context">Permissions is required for posting notifications</string>
<string name="notification_permissions_rationale">Notifications permission must be added manually in the app\'s settings before trying again to enable notifications</string>
<string name="write_storage_permission_opml">Write storage permission is required for OPML export</string>
<string name="story_saved">Story marked as saved</string>
<string name="story_unsaved">Story marked as unsaved</string>
<string name="story_read">Story marked as read</string>

View file

@ -3,12 +3,12 @@ import org.gradle.api.JavaVersion
object Config {
const val compileSdk = 34
const val minSdk = 24
const val minSdk = 26
const val targetSdk = 34
const val versionCode = 220
const val versionName = "13.2.4"
const val versionCode = 230
const val versionName = "13.3.2"
const val androidTestInstrumentation = "androidx.test.runner.AndroidJUnitRunner"
val javaVersion = JavaVersion.VERSION_17
val javaVersion = JavaVersion.VERSION_21
}

View file

@ -6,7 +6,6 @@ object Dependencies {
const val okHttp = "com.squareup.okhttp3:okhttp:${Version.okHttp}"
const val gson = "com.google.code.gson:gson:${Version.gson}"
const val billing = "com.android.billingclient:billing:${Version.billing}"
const val playCore = "com.google.android.play:core:${Version.playCore}"
const val material = "com.google.android.material:material:${Version.material}"
const val preference = "androidx.preference:preference-ktx:${Version.preference}"
const val browser = "androidx.browser:browser:${Version.browser}"
@ -16,6 +15,7 @@ object Dependencies {
const val hiltAndroid = "com.google.dagger:hilt-android:${Version.hilt}"
const val hiltCompiler = "com.google.dagger:hilt-compiler:${Version.hilt}"
const val profileInstaller = "androidx.profileinstaller:profileinstaller:${Version.profileInstaller}"
const val playReview = "com.google.android.play:review:${Version.playReview}"
// test
const val junit = "junit:junit:${Version.junit}"

View file

@ -1,35 +1,35 @@
object Version {
const val android = "8.3.1"
const val android = "8.5.0"
const val kotlin = "1.9.23"
const val fragment = "1.6.2"
const val fragment = "1.8.0"
const val recyclerView = "1.3.2"
const val swipeRefreshLayout = "1.1.0"
const val okHttp = "4.12.0"
const val gson = "2.10.1"
const val gson = "2.11.0"
const val billing = "6.2.0"
const val playCore = "1.10.3"
const val material = "1.11.0"
const val playReview = "2.0.1"
const val material = "1.12.0"
const val preference = "1.2.1"
const val browser = "1.8.0"
const val lifecycle = "2.7.0"
const val lifecycle = "2.8.2"
const val splashScreen = "1.0.1"
const val hilt = "2.51"
const val hilt = "2.51.1"
const val profileInstaller = "1.3.1"
const val junit = "4.13.2"
const val mockk = "1.13.10"
const val mockk = "1.13.11"
const val junitExt = "1.1.5"
const val espresso = "3.5.1"
const val uiAutomator = "2.3.0"
const val benchmarkMacroJunit4 = "1.2.3"
const val benchmarkMacroJunit4 = "1.2.4"
const val benManesVersions = "0.51.0"
}