Dependency Injection (#10)

HILT Dependency Injection.
This commit is contained in:
Andrei 2022-03-10 21:26:23 -08:00 committed by GitHub
parent 4a43b0c1f3
commit 2d260e9324
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 1104 additions and 544 deletions

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.6.0'
ext.kotlin_version = '1.6.10'
repositories {
mavenCentral()
maven {
@ -9,8 +9,9 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:2.38.1"
}
}
@ -25,6 +26,8 @@ repositories {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
dependencies {
implementation 'androidx.fragment:fragment-ktx:1.4.1'
@ -41,6 +44,9 @@ dependencies {
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-beta01'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-compiler:2.38.1"
}
android {

View file

@ -4,7 +4,9 @@ import android.app.Application
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class NbApplication : Application(), DefaultLifecycleObserver {
override fun onCreate() {

View file

@ -10,6 +10,7 @@ import com.newsblur.domain.UserDetails;
import com.newsblur.fragment.ProfileActivitiesFragment;
import com.newsblur.fragment.ProfileActivityDetailsFragment;
import com.newsblur.fragment.ProfileInteractionsFragment;
import com.newsblur.util.ImageLoader;
/**
* Created by mark on 15/06/15.
@ -55,8 +56,8 @@ public class ActivityDetailsPagerAdapter extends FragmentPagerAdapter {
}
}
public void setUser(UserDetails user) {
interactionsFragment.setUser(profile, user);
activitiesFragment.setUser(profile, user);
public void setUser(UserDetails user, ImageLoader iconLoader) {
interactionsFragment.setUser(profile, user, iconLoader);
activitiesFragment.setUser(profile, user, iconLoader);
}
}

View file

@ -12,6 +12,9 @@ import com.newsblur.R;
import com.newsblur.fragment.AddSocialFragment;
import com.newsblur.util.UIUtils;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class AddSocial extends NbActivity {
private FragmentManager fragmentManager;

View file

@ -18,6 +18,7 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedOrderFilter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.FolderViewFilter;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.ListOrderFilter;
import com.newsblur.util.PrefsUtils;
@ -45,14 +46,18 @@ public class FeedChooserAdapter extends BaseExpandableListAdapter {
protected FolderViewFilter folderViewFilter;
protected ListOrderFilter listOrderFilter;
protected FeedOrderFilter feedOrderFilter;
protected final FeedUtils feedUtils;
protected final ImageLoader iconLoader;
protected float textSize;
FeedChooserAdapter(Context context) {
FeedChooserAdapter(Context context, FeedUtils feedUtils, ImageLoader iconLoader) {
folderViewFilter = PrefsUtils.getFeedChooserFolderView(context);
listOrderFilter = PrefsUtils.getFeedChooserListOrder(context);
feedOrderFilter = PrefsUtils.getFeedChooserFeedOrder(context);
textSize = PrefsUtils.getListTextSize(context);
this.feedUtils = feedUtils;
this.iconLoader = iconLoader;
}
@Override
@ -141,7 +146,7 @@ public class FeedChooserAdapter extends BaseExpandableListAdapter {
}
}
FeedUtils.iconLoader.displayImage(feed.faviconUrl, img, img.getHeight(), true);
iconLoader.displayImage(feed.faviconUrl, img, img.getHeight(), true);
return convertView;
}

View file

@ -46,7 +46,7 @@ public class FeedItemsList extends ItemsList {
super.onCreate(bundle);
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, false);
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
checkInAppReview();
}
@ -79,19 +79,19 @@ public class FeedItemsList extends ItemsList {
return true;
}
if (item.getItemId() == R.id.menu_notifications_disable) {
FeedUtils.disableNotifications(this, feed);
feedUtils.disableNotifications(this, feed);
return true;
}
if (item.getItemId() == R.id.menu_notifications_focus) {
FeedUtils.enableFocusNotifications(this, feed);
feedUtils.enableFocusNotifications(this, feed);
return true;
}
if (item.getItemId() == R.id.menu_notifications_unread) {
FeedUtils.enableUnreadNotifications(this, feed);
feedUtils.enableUnreadNotifications(this, feed);
return true;
}
if (item.getItemId() == R.id.menu_instafetch_feed) {
FeedUtils.instaFetchFeed(this, feed.feedId);
feedUtils.instaFetchFeed(this, feed.feedId);
this.finish();
return true;
}
@ -108,7 +108,7 @@ public class FeedItemsList extends ItemsList {
// the name change won't be reflected until the activity finishes.
}
if (item.getItemId() == R.id.menu_statistics) {
FeedUtils.openStatistics(this, feed.feedId);
feedUtils.openStatistics(this, feed.feedId);
return true;
}
return false;

View file

@ -3,7 +3,6 @@ package com.newsblur.activity;
import android.os.Bundle;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
public class FeedReading extends Reading {
@ -16,14 +15,14 @@ public class FeedReading extends Reading {
// if the activity got launch with a missing FeedSet, it will be in the process of cancelling
return;
}
Feed feed = FeedUtils.dbHelper.getFeed(fs.getSingleFeed());
Feed feed = dbHelper.getFeed(fs.getSingleFeed());
if (feed == null) {
// if this is somehow an intent so stale that the feed no longer exists, bail.
finish();
return;
}
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, false);
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
}
}

View file

@ -10,17 +10,29 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.newsblur.activity.FeedSearchAdapter.OnFeedSearchResultClickListener
import com.newsblur.databinding.ActivityFeedSearchBinding
import com.newsblur.di.IconLoader
import com.newsblur.domain.FeedResult
import com.newsblur.fragment.AddFeedFragment
import com.newsblur.fragment.AddFeedFragment.AddFeedProgressListener
import com.newsblur.network.APIManager
import com.newsblur.util.ImageLoader
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import java.net.MalformedURLException
import java.net.URL
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFeedProgressListener {
@Inject
lateinit var apiManager: APIManager
@IconLoader
@Inject
lateinit var iconLoader: ImageLoader
private val supportedUrlProtocols: MutableSet<String> = HashSet(2)
init {
@ -30,7 +42,6 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
private lateinit var adapter: FeedSearchAdapter
private lateinit var binding: ActivityFeedSearchBinding
private lateinit var apiManager: APIManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -38,7 +49,6 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
setContentView(binding.root)
setupViews()
setupListeners()
apiManager = APIManager(this)
binding.inputSearchQuery.requestFocus()
}
@ -55,7 +65,7 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayShowHomeEnabled(false)
adapter = FeedSearchAdapter(this)
adapter = FeedSearchAdapter(this, iconLoader)
binding.feedResultList.adapter = adapter
}

View file

@ -9,9 +9,11 @@ import com.newsblur.R
import com.newsblur.databinding.ViewFeedSearchRowBinding
import com.newsblur.domain.FeedResult
import com.newsblur.util.FeedUtils
import com.newsblur.util.ImageLoader
class FeedSearchAdapter(
private val onClickListener: OnFeedSearchResultClickListener
private val onClickListener: OnFeedSearchResultClickListener,
private val iconLoader: ImageLoader
) : RecyclerView.Adapter<FeedSearchAdapter.ViewHolder>() {
private val resultsList: MutableList<FeedResult> = mutableListOf()
@ -44,7 +46,7 @@ class FeedSearchAdapter(
fun bind(result: FeedResult) {
val resultFaviconUrl = result.faviconUrl
if (resultFaviconUrl.isNotEmpty()) {
FeedUtils.iconLoader?.displayImage(resultFaviconUrl, binding.imgFeedIcon)
iconLoader.displayImage(resultFaviconUrl, binding.imgFeedIcon)
}
binding.textTitle.text = result.label

View file

@ -6,7 +6,6 @@ import android.view.MenuItem;
import com.newsblur.R;
import com.newsblur.fragment.InfrequentCutoffDialogFragment;
import com.newsblur.fragment.InfrequentCutoffDialogFragment.InfrequentCutoffChangedListener;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -33,7 +32,7 @@ public class InfrequentItemsList extends ItemsList implements InfrequentCutoffCh
@Override
public void infrequentCutoffChanged(int newValue) {
PrefsUtils.setInfrequentCutoff(this, newValue);
FeedUtils.dbHelper.clearInfrequentSession();
dbHelper.clearInfrequentSession();
restartReadingSession();
}

View file

@ -5,34 +5,32 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.service.SubscriptionSyncService
import com.newsblur.util.*
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* The very first activity we launch. Checks to see if there is a user logged in yet and then
* either loads the Main UI or a Login screen as needed. Also responsible for warming up the
* DB connection used by all other Activities.
*/
@AndroidEntryPoint
class InitActivity : AppCompatActivity() {
@Inject
lateinit var dbHelper: BlurDatabaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen().also {
it.setKeepOnScreenCondition() {
// keep showing the splash screen until FeedUtils.offerInitContext(...)
// finishes and UI ready to display
FeedUtils.dbHelper != null || FeedUtils.thumbnailLoader != null
}
}
installSplashScreen()
lifecycleScope.executeAsyncTask(doInBackground = { start() })
Log.i(this, "cold launching version " + PrefsUtils.getVersion(this))
}
private fun start() {
// this is the first Activity launched; use it to init the global singletons in FeedUtils
FeedUtils.offerInitContext(this)
// it is safe to call repeatedly because creating an existing notification performs
// no operation
NotificationUtils.createNotificationChannel(this)
@ -60,7 +58,7 @@ class InitActivity : AppCompatActivity() {
private fun upgradeCheck() {
val upgrade = PrefsUtils.checkForUpgrade(this)
if (upgrade) {
FeedUtils.dbHelper!!.dropAndRecreateTables()
dbHelper.dropAndRecreateTables()
// don't actually unset the upgrade flag, the sync service will do this same check and
// update everything
}

View file

@ -18,7 +18,9 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.databinding.ActivityItemslistBinding;
import com.newsblur.di.IconLoader;
import com.newsblur.fragment.ItemSetFragment;
import com.newsblur.fragment.ReadFilterDialogFragment;
import com.newsblur.fragment.SaveSearchFragment;
@ -28,6 +30,7 @@ import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefConstants.ThemeValue;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
@ -40,8 +43,23 @@ import com.newsblur.util.StoryOrderChangedListener;
import com.newsblur.util.ThumbnailStyle;
import com.newsblur.util.UIUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public abstract class ItemsList extends NbActivity implements StoryOrderChangedListener, ReadFilterChangedListener, OnSeekBarChangeListener {
@Inject
BlurDatabaseHelper dbHelper;
@Inject
FeedUtils feedUtils;
@Inject
@IconLoader
ImageLoader iconLoader;
public static final String EXTRA_FEED_SET = "feed_set";
public static final String EXTRA_STORY_HASH = "story_hash";
public static final String EXTRA_WIDGET_STORY = "widget_story";
@ -68,13 +86,13 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
// this is not strictly necessary, since our first refresh with the fs will swap in
// the correct session, but that can be delayed by sync backup, so we try here to
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
FeedUtils.prepareReadingSession(fs, false);
feedUtils.prepareReadingSession(fs, false);
if (getIntent().getBooleanExtra(EXTRA_WIDGET_STORY, false)) {
String hash = (String) getIntent().getSerializableExtra(EXTRA_STORY_HASH);
UIUtils.startReadingActivity(fs, hash, this);
} else if (PrefsUtils.isAutoOpenFirstUnread(this)) {
if (FeedUtils.dbHelper.getUnreadCount(fs, intelState) > 0) {
if (dbHelper.getUnreadCount(fs, intelState) > 0) {
UIUtils.startReadingActivity(fs, Reading.FIND_FIRST_UNREAD, this);
}
}
@ -282,7 +300,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
finish();
return true;
} else if (item.getItemId() == R.id.menu_mark_all_as_read) {
FeedUtils.markRead(this, fs, null, null, R.array.mark_all_read_options, true);
feedUtils.markRead(this, fs, null, null, R.array.mark_all_read_options, true);
return true;
} else if (item.getItemId() == R.id.menu_story_order) {
StoryOrder currentValue = getStoryOrder();
@ -432,7 +450,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
String oldQuery = fs.getSearchQuery();
fs.setSearchQuery(q);
if (!TextUtils.equals(q, oldQuery)) {
FeedUtils.prepareReadingSession(fs, true);
feedUtils.prepareReadingSession(fs, true);
triggerSync();
itemSetFragment.resetEmptyState();
itemSetFragment.hasUpdated();
@ -474,7 +492,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
protected void restartReadingSession() {
NBSyncService.resetFetchState(fs);
FeedUtils.prepareReadingSession(fs, true);
feedUtils.prepareReadingSession(fs, true);
triggerSync();
itemSetFragment.resetEmptyState();
itemSetFragment.hasUpdated();

View file

@ -15,11 +15,16 @@ import com.newsblur.service.SubscriptionSyncService
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class LoginProgress : FragmentActivity() {
@Inject
lateinit var apiManager: APIManager
private lateinit var binding: ActivityLoginProgressBinding
private lateinit var apiManager: APIManager
override fun onCreate(bundle: Bundle?) {
PrefsUtils.applyThemePreference(this)
@ -31,8 +36,6 @@ class LoginProgress : FragmentActivity() {
val username = intent.getStringExtra("username")
val password = intent.getStringExtra("password")
apiManager = APIManager(this)
lifecycleScope.executeAsyncTask(
onPreExecute = {
val a = AnimationUtils.loadAnimation(this, R.anim.text_up)

View file

@ -28,6 +28,7 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.databinding.ActivityMainBinding;
import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
import com.newsblur.fragment.FolderListFragment;
@ -45,8 +46,19 @@ import com.newsblur.util.UIUtils;
import com.newsblur.view.StateToggleButton.StateChangedListener;
import com.newsblur.widget.WidgetUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener, OnSeekBarChangeListener {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
public static final String EXTRA_FORCE_SHOW_FEED_ID = "force_show_feed_id";
private FolderListFragment folderFeedList;
@ -110,7 +122,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
});
FeedUtils.currentFolderName = null;
feedUtils.currentFolderName = null;
binding.mainMenuButton.setOnClickListener(v -> onClickMenuButton());
binding.mainAddButton.setOnClickListener(v -> onClickAddButton());
@ -151,7 +163,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
// will be required, however inefficient
folderFeedList.hasUpdated();
NBSyncService.resetReadingSession(FeedUtils.dbHelper);
NBSyncService.resetReadingSession(dbHelper);
NBSyncService.flushRecounts();
updateStatusIndicators();
@ -296,12 +308,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
startActivity(widgetIntent);
return true;
} else if (item.getItemId() == R.id.menu_feedback_email) {
PrefsUtils.sendLogEmail(this);
PrefsUtils.sendLogEmail(this, dbHelper);
return true;
} else if (item.getItemId() == R.id.menu_feedback_post) {
try {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(PrefsUtils.createFeedbackLink(this)));
i.setData(Uri.parse(PrefsUtils.createFeedbackLink(this, dbHelper)));
startActivity(i);
} catch (Exception e) {
Log.wtf(this.getClass().getName(), "device cannot even open URLs to report feedback");

View file

@ -16,11 +16,13 @@ import androidx.core.content.ContextCompat;
import com.newsblur.R;
import com.newsblur.databinding.ActivityMuteConfigBinding;
import com.newsblur.di.IconLoader;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -29,8 +31,17 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedStateChangedListener {
@Inject
FeedUtils feedUtils;
@Inject
@IconLoader
ImageLoader iconLoader;
private ActivityMuteConfigBinding binding;
private boolean checkedInitFeedsLimit = false;
@ -66,7 +77,7 @@ public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedSta
@Override
void setupList() {
adapter = new MuteConfigAdapter(this, this);
adapter = new MuteConfigAdapter(this, feedUtils, iconLoader, this);
binding.listView.setAdapter(adapter);
}
@ -162,8 +173,8 @@ public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedSta
}
adapter.notifyDataSetChanged();
if (isMute) FeedUtils.muteFeeds(this, adapter.feedIds);
else FeedUtils.unmuteFeeds(this, adapter.feedIds);
if (isMute) feedUtils.muteFeeds(this, adapter.feedIds);
else feedUtils.unmuteFeeds(this, adapter.feedIds);
}
private void showAccountFeedsLimitDialog(int exceededLimitCount) {
@ -211,8 +222,8 @@ public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedSta
inactiveFeedIds.add(feed.feedId);
}
}
FeedUtils.unmuteFeeds(this, activeFeedIds);
FeedUtils.muteFeeds(this, inactiveFeedIds);
feedUtils.unmuteFeeds(this, activeFeedIds);
feedUtils.muteFeeds(this, inactiveFeedIds);
finish();
}

View file

@ -9,6 +9,7 @@ import android.widget.ImageView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import java.util.ArrayList;
import java.util.HashSet;
@ -18,8 +19,8 @@ public class MuteConfigAdapter extends FeedChooserAdapter {
private FeedStateChangedListener listener;
MuteConfigAdapter(Context context, FeedStateChangedListener listener) {
super(context);
MuteConfigAdapter(Context context, FeedUtils feedUtils, ImageLoader imageLoader, FeedStateChangedListener listener) {
super(context, feedUtils, imageLoader);
this.listener = listener;
}
@ -45,8 +46,8 @@ public class MuteConfigAdapter extends FeedChooserAdapter {
}
// if allAreMute initially, we need to unMute feeds
if (allAreMute) FeedUtils.unmuteFeeds(groupView.getContext(), feedIds);
else FeedUtils.muteFeeds(groupView.getContext(), feedIds);
if (allAreMute) feedUtils.unmuteFeeds(groupView.getContext(), feedIds);
else feedUtils.muteFeeds(groupView.getContext(), feedIds);
listener.onFeedStateChanged();
notifyDataChanged();
@ -70,8 +71,8 @@ public class MuteConfigAdapter extends FeedChooserAdapter {
feed.active = !feed.active;
Set<String> feedIds = new HashSet<>(1);
feedIds.add(feed.feedId);
if (feed.active) FeedUtils.unmuteFeeds(childView.getContext(), feedIds);
else FeedUtils.muteFeeds(childView.getContext(), feedIds);
if (feed.active) feedUtils.unmuteFeeds(childView.getContext(), feedIds);
else feedUtils.muteFeeds(childView.getContext(), feedIds);
listener.onFeedStateChanged();
notifyDataChanged();

View file

@ -1,22 +1,28 @@
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.database.BlurDatabaseHelper
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.service.NBSyncReceiver
import com.newsblur.util.FeedUtils
import com.newsblur.util.Log
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* The base class for all Activities in the NewsBlur app. Handles enforcement of
* login state and tracking of sync/update broadcasts.
*/
@AndroidEntryPoint
open class NbActivity : AppCompatActivity() {
@Inject
lateinit var dbHelper: BlurDatabaseHelper
private var uniqueLoginKey: String? = null
private var lastTheme: ThemeValue? = null
@ -37,13 +43,12 @@ open class NbActivity : AppCompatActivity() {
lastTheme = PrefsUtils.getSelectedTheme(this)
super.onCreate(bundle)
offerInitContext(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)
PrefsUtils.logout(this, dbHelper)
finish()
}
@ -99,7 +104,7 @@ open class NbActivity : AppCompatActivity() {
* Pokes the sync service to perform any pending sync actions.
*/
protected fun triggerSync() {
triggerSync(this)
FeedUtils.triggerSync(this)
}
/**

View file

@ -11,18 +11,26 @@ import androidx.lifecycle.lifecycleScope
import com.android.billingclient.api.*
import com.newsblur.R
import com.newsblur.databinding.ActivityPremiumBinding
import com.newsblur.di.IconLoader
import com.newsblur.subscription.SubscriptionManager
import com.newsblur.subscription.SubscriptionManagerImpl
import com.newsblur.subscription.SubscriptionsListener
import com.newsblur.util.*
import dagger.hilt.android.AndroidEntryPoint
import nl.dionsegijn.konfetti.emitters.StreamEmitter
import nl.dionsegijn.konfetti.models.Shape.Circle
import nl.dionsegijn.konfetti.models.Shape.Square
import nl.dionsegijn.konfetti.models.Size
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class Premium : NbActivity() {
@IconLoader
@Inject
lateinit var iconLoader: ImageLoader
private lateinit var binding: ActivityPremiumBinding
private lateinit var subscriptionManager: SubscriptionManager
@ -64,7 +72,7 @@ class Premium : NbActivity() {
}
binding.textPolicies.text = UIUtils.fromHtml(getString(R.string.premium_policies))
binding.textSubTitle.paintFlags = binding.textSubTitle.paintFlags or Paint.UNDERLINE_TEXT_FLAG
FeedUtils.iconLoader!!.displayImage(AppConstants.SHILOH_PHOTO_URL, binding.imgShiloh)
iconLoader.displayImage(AppConstants.SHILOH_PHOTO_URL, binding.imgShiloh)
}
private fun setupBilling() {

View file

@ -6,21 +6,32 @@ import android.view.MenuItem
import androidx.lifecycle.lifecycleScope
import com.newsblur.R
import com.newsblur.databinding.ActivityProfileBinding
import com.newsblur.di.IconLoader
import com.newsblur.fragment.ProfileDetailsFragment
import com.newsblur.network.APIManager
import com.newsblur.util.ImageLoader
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class Profile : NbActivity() {
@Inject
lateinit var apiManager: APIManager
@Inject
@IconLoader
lateinit var iconLoader: ImageLoader
private val detailsTag = "details"
private var detailsFragment: ProfileDetailsFragment? = null
private var activityDetailsPagerAdapter: ActivityDetailsPagerAdapter? = null
private var userId: String? = null
private lateinit var binding: ActivityProfileBinding
private lateinit var apiManager: APIManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -28,7 +39,6 @@ class Profile : NbActivity() {
setContentView(binding.root)
UIUtils.setupToolbar(this, R.drawable.logo, getString(R.string.profile), true)
apiManager = APIManager(this)
userId = if (savedInstanceState == null) {
intent.getStringExtra(USER_ID)
} else {
@ -84,7 +94,7 @@ class Profile : NbActivity() {
onPostExecute = { userDetails ->
if (userDetails != null && detailsFragment != null && activityDetailsPagerAdapter != null) {
detailsFragment!!.setUser(this, userDetails, TextUtils.isEmpty(userId))
activityDetailsPagerAdapter!!.setUser(userDetails)
activityDetailsPagerAdapter!!.setUser(userDetails, iconLoader)
}
}
)

View file

@ -21,6 +21,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator
import com.newsblur.R
import com.newsblur.database.ReadingAdapter
import com.newsblur.databinding.ActivityReadingBinding
import com.newsblur.di.IconLoader
import com.newsblur.domain.Story
import com.newsblur.fragment.ReadingItemFragment
import com.newsblur.fragment.ReadingPagerFragment
@ -32,12 +33,22 @@ import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue
import com.newsblur.view.ReadingScrollView.ScrollChangeListener
import com.newsblur.viewModel.StoriesViewModel
import dagger.hilt.android.AndroidEntryPoint
import java.lang.Runnable
import javax.inject.Inject
import kotlin.math.abs
@AndroidEntryPoint
abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeListener,
ScrollChangeListener, ReadingFontChangedListener {
@Inject
lateinit var feedUtils: FeedUtils
@Inject
@IconLoader
lateinit var iconLoader: ImageLoader
@JvmField
var fs: FeedSet? = null
@ -149,7 +160,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
// this is not strictly necessary, since our first refresh with the fs will swap in
// the correct session, but that can be delayed by sync backup, so we try here to
// reduce UI lag, or in case somehow we got redisplayed in a zero-story state
FeedUtils.prepareReadingSession(fs, false)
feedUtils.prepareReadingSession(fs, false)
}
override fun onPause() {
@ -216,7 +227,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
}
private fun setCursorData(cursor: Cursor) {
if (!FeedUtils.dbHelper!!.isFeedSetReady(fs)) {
if (!dbHelper.isFeedSetReady(fs)) {
com.newsblur.util.Log.i(this.javaClass.name, "stale load")
// the system can and will re-use activities, so during the initial mismatch of
// data, don't show the old stories
@ -317,7 +328,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
if (fs!!.isSingleNormal) showFeedMetadata = false
var sourceUserId: String? = null
if (fs!!.singleSocialFeed != null) sourceUserId = fs!!.singleSocialFeed.key
readingAdapter = ReadingAdapter(childFragmentManager, sourceUserId, showFeedMetadata, this)
readingAdapter = ReadingAdapter(childFragmentManager, sourceUserId, showFeedMetadata, this, dbHelper)
pager.adapter = readingAdapter
@ -339,7 +350,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
get() {
// saved stories and global shared stories don't have unreads
if (fs!!.isAllSaved || fs!!.isGlobalShared) return 0
val result = FeedUtils.dbHelper!!.getUnreadCount(fs, intelState)
val result = dbHelper.getUnreadCount(fs, intelState)
return if (result < 0) 0 else result
}
@ -395,7 +406,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
}
}
if (isMarkStoryReadImmediately) {
FeedUtils.markStoryAsRead(story, this@Reading)
feedUtils.markStoryAsRead(story, this@Reading)
}
}
checkStoryCount(position)
@ -712,7 +723,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
private fun overlaySendClick() {
if (readingAdapter == null || pager == null) return
val story = readingAdapter!!.getStory(pager!!.currentItem)
FeedUtils.sendStoryUrl(story, this)
feedUtils.sendStoryUrl(story, this)
}
private fun overlayTextClick() {

View file

@ -11,15 +11,20 @@ import com.newsblur.databinding.ActivityRegisterProgressBinding
import com.newsblur.network.APIManager
import com.newsblur.util.PrefsUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* Show progress screen while registering request is being processed. This
* Activity doesn't extend NbActivity because it is one of the few
* Activities that will be shown while the user is still logged out.
*/
@AndroidEntryPoint
class RegisterProgress : FragmentActivity() {
private lateinit var apiManager: APIManager
@Inject
lateinit var apiManager: APIManager
private lateinit var binding: ActivityRegisterProgressBinding
override fun onCreate(bundle: Bundle?) {
@ -28,8 +33,6 @@ class RegisterProgress : FragmentActivity() {
binding = ActivityRegisterProgressBinding.inflate(layoutInflater)
setContentView(binding.root)
apiManager = APIManager(this)
val username = intent.getStringExtra("username")
val password = intent.getStringExtra("password")
val email = intent.getStringExtra("email")

View file

@ -16,7 +16,7 @@ public class SocialFeedItemsList extends ItemsList {
socialFeed = (SocialFeed) getIntent().getSerializableExtra(EXTRA_SOCIAL_FEED);
super.onCreate(bundle);
UIUtils.setupToolbar(this, socialFeed.photoUrl, socialFeed.feedTitle, false);
UIUtils.setupToolbar(this, socialFeed.photoUrl, socialFeed.feedTitle, iconLoader, false);
}
@Override

View file

@ -11,9 +11,9 @@ public class SocialFeedReading extends Reading {
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
SocialFeed socialFeed = FeedUtils.dbHelper.getSocialFeed(fs.getSingleSocialFeed().getKey());
SocialFeed socialFeed = dbHelper.getSocialFeed(fs.getSingleSocialFeed().getKey());
if (socialFeed == null) finish(); // don't open fatally stale intents
UIUtils.setupToolbar(this, socialFeed.photoUrl, socialFeed.feedTitle, false);
UIUtils.setupToolbar(this, socialFeed.photoUrl, socialFeed.feedTitle, iconLoader, false);
}
}

View file

@ -8,8 +8,11 @@ import android.view.View;
import com.newsblur.R;
import com.newsblur.databinding.ActivityWidgetConfigBinding;
import com.newsblur.di.IconLoader;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
import com.newsblur.widget.WidgetUtils;
@ -19,8 +22,17 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
public class WidgetConfig extends FeedChooser {
@Inject
FeedUtils feedUtils;
@Inject
@IconLoader
ImageLoader iconLoader;
private ActivityWidgetConfigBinding binding;
@Override
@ -68,7 +80,7 @@ public class WidgetConfig extends FeedChooser {
@Override
void setupList() {
adapter = new WidgetConfigAdapter(this);
adapter = new WidgetConfigAdapter(this, feedUtils, iconLoader);
binding.listView.setAdapter(adapter);
}

View file

@ -8,14 +8,16 @@ import android.widget.ImageView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import java.util.ArrayList;
public class WidgetConfigAdapter extends FeedChooserAdapter {
WidgetConfigAdapter(Context context) {
super(context);
WidgetConfigAdapter(Context context, FeedUtils feedUtils, ImageLoader iconLoader) {
super(context, feedUtils, iconLoader);
}
@Override

View file

@ -1,5 +1,7 @@
package com.newsblur.database;
import static java.util.Collections.emptySet;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -30,6 +32,7 @@ import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StateFilter;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.UIUtils;
import java.util.Arrays;
import java.util.ArrayList;
@ -1585,7 +1588,7 @@ public class BlurDatabaseHelper {
}
public void sendSyncUpdate(int updateType) {
FeedUtils.syncUpdateStatus(context, updateType);
UIUtils.syncUpdateStatus(context, updateType);
}
private static String conjoinSelections(CharSequence... args) {
@ -1621,4 +1624,16 @@ 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);
}
public FeedSet feedSetFromFolderName(String folderName) {
return FeedSet.folder(folderName, getFeedIdsRecursive(folderName));
}
private Set<String> getFeedIdsRecursive(String folderName) {
Folder folder = getFolder(folderName);
if (folder == null) return emptySet();
Set<String> feedIds = new HashSet<>(folder.feedIds);
for (String child : folder.children) feedIds.addAll(getFeedIdsRecursive(child));
return feedIds;
}
}

View file

@ -37,6 +37,7 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedListOrder;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
@ -124,6 +125,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
private Context context;
private LayoutInflater inflater;
private StateFilter currentState;
private final ImageLoader iconLoader;
private final BlurDatabaseHelper dbHelper;
// since we want to implement a custom expando that does group collapse/expand, we need
// a way to call back to those functions on the listview from the onclick listener of
@ -139,10 +142,12 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
public String activeSearchQuery;
public FolderListAdapter(Context context, StateFilter currentState) {
public FolderListAdapter(Context context, StateFilter currentState, ImageLoader iconLoader, BlurDatabaseHelper dbHelper) {
this.currentState = currentState;
this.context = context;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.iconLoader = iconLoader;
this.dbHelper = dbHelper;
textSize = PrefsUtils.getListTextSize(context);
}
@ -253,7 +258,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
nameView.setText(f.feedTitle);
nameView.setTextSize(textSize * defaultTextSize_childName);
ImageView iconView = (ImageView) v.findViewById(R.id.row_socialfeed_icon);
FeedUtils.iconLoader.displayImage(f.photoUrl, iconView);
iconLoader.displayImage(f.photoUrl, iconView);
TextView neutCounter = ((TextView) v.findViewById(R.id.row_socialsumneu));
if (f.neutralCount > 0 && currentState != StateFilter.BEST) {
neutCounter.setVisibility(View.VISIBLE);
@ -293,8 +298,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
TextView nameView = v.findViewById(R.id.row_saved_search_title);
nameView.setText(UIUtils.fromHtml(ss.feedTitle));
ImageView iconView = v.findViewById(R.id.row_saved_search_icon);
FeedUtils.iconLoader.preCheck(ss.faviconUrl, iconView);
FeedUtils.iconLoader.displayImage(ss.faviconUrl, iconView);
iconLoader.preCheck(ss.faviconUrl, iconView);
iconLoader.displayImage(ss.faviconUrl, iconView);
} else {
if (v == null) v = inflater.inflate(R.layout.row_feed, parent, false);
Feed f = activeFolderChildren.get(groupPosition).get(childPosition);
@ -307,8 +312,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
nameView.setText(f.title);
nameView.setTextSize(textSize * defaultTextSize_childName);
ImageView iconView = (ImageView) v.findViewById(R.id.row_feedfavicon);
FeedUtils.iconLoader.preCheck(f.faviconUrl, iconView);
FeedUtils.iconLoader.displayImage(f.faviconUrl, iconView);
iconLoader.preCheck(f.faviconUrl, iconView);
iconLoader.displayImage(f.faviconUrl, iconView);
TextView neutCounter = ((TextView) v.findViewById(R.id.row_feedneutral));
TextView posCounter = ((TextView) v.findViewById(R.id.row_feedpositive));
TextView savedCounter = ((TextView) v.findViewById(R.id.row_feedsaved));
@ -407,7 +412,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return FeedSet.allSaved();
} else {
String folderName = getGroupFolderName(groupPosition);
FeedSet fs = FeedUtils.feedSetFromFolderName(folderName);
FeedSet fs = dbHelper.feedSetFromFolderName(folderName);
if (currentState == StateFilter.SAVED) fs.setFilterSaved(true);
return fs;
}

View file

@ -1,7 +1,5 @@
package com.newsblur.database;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
@ -17,7 +15,7 @@ import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.fragment.LoadingFragment;
import com.newsblur.fragment.ReadingItemFragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.service.NBSyncReceiver;
import java.util.ArrayList;
import java.util.HashMap;
@ -53,12 +51,14 @@ public class ReadingAdapter extends PagerAdapter {
private Map<String,Classifier> classifiers = new HashMap<String,Classifier>(0);
private final ExecutorService executorService;
private final BlurDatabaseHelper dbHelper;
public ReadingAdapter(FragmentManager fm, String sourceUserId, boolean showFeedMetadata, Reading activity) {
public ReadingAdapter(FragmentManager fm, String sourceUserId, boolean showFeedMetadata, Reading activity, BlurDatabaseHelper dbHelper) {
this.sourceUserId = sourceUserId;
this.showFeedMetadata = showFeedMetadata;
this.fm = fm;
this.activity = activity;
this.dbHelper = dbHelper;
this.fragments = new HashMap<String,ReadingItemFragment>();
this.states = new HashMap<String,Fragment.SavedState>();
@ -109,7 +109,7 @@ public class ReadingAdapter extends PagerAdapter {
feedIdsSeen.add(s.feedId);
}
for (String feedId : feedIdsSeen) {
classifiers.put(feedId, FeedUtils.dbHelper.getClassifierForFeed(feedId));
classifiers.put(feedId, dbHelper.getClassifierForFeed(feedId));
}
}
} catch (Exception e) {
@ -284,7 +284,7 @@ public class ReadingAdapter extends PagerAdapter {
ReadingItemFragment rif = fragments.get(s.storyHash);
if (rif != null ) {
rif.offerStoryUpdate(s);
rif.handleUpdate(UPDATE_STORY);
rif.handleUpdate(NBSyncReceiver.UPDATE_STORY);
}
}
}

View file

@ -32,6 +32,8 @@ import java.util.concurrent.Executors;
import com.newsblur.R;
import com.newsblur.activity.FeedItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.di.IconLoader;
import com.newsblur.di.ThumbnailLoader;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.fragment.ItemSetFragment;
@ -75,24 +77,35 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private Parcelable oldScrollState;
private final ImageLoader iconLoader;
private final ImageLoader thumbnailLoader;
private final FeedUtils feedUtils;
private final ExecutorService executorService;
private NbActivity context;
private ItemSetFragment fragment;
private final NbActivity context;
private final ItemSetFragment fragment;
private FeedSet fs;
private StoryListStyle listStyle;
private boolean ignoreReadStatus;
private boolean ignoreIntel;
private boolean singleFeed;
private float textSize;
private UserDetails user;
private final UserDetails user;
private ThumbnailStyle thumbnailStyle;
public StoryViewAdapter(NbActivity context, ItemSetFragment fragment, FeedSet fs, StoryListStyle listStyle) {
public StoryViewAdapter(NbActivity context,
ItemSetFragment fragment,
FeedSet fs,
StoryListStyle listStyle,
ImageLoader iconLoader,
ImageLoader thumbnailLoader,
FeedUtils feedUtils) {
this.context = context;
this.fragment = fragment;
this.fs = fs;
this.listStyle = listStyle;
this.iconLoader = iconLoader;
this.thumbnailLoader = thumbnailLoader;
this.feedUtils = feedUtils;
if (fs.isGlobalShared()) {ignoreReadStatus = false; ignoreIntel = true; singleFeed = false;}
if (fs.isAllSocial()) {ignoreReadStatus = false; ignoreIntel = false; singleFeed = false;}
@ -401,36 +414,36 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_mark_story_as_read:
FeedUtils.markStoryAsRead(story, context);
feedUtils.markStoryAsRead(story, context);
return true;
case R.id.menu_mark_story_as_unread:
FeedUtils.markStoryUnread(story, context);
feedUtils.markStoryUnread(story, context);
return true;
case R.id.menu_mark_older_stories_as_read:
FeedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options, false);
feedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options, false);
return true;
case R.id.menu_mark_newer_stories_as_read:
FeedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options, false);
feedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options, false);
return true;
case R.id.menu_send_story:
FeedUtils.sendStoryUrl(story, context);
feedUtils.sendStoryUrl(story, context);
return true;
case R.id.menu_send_story_full:
FeedUtils.sendStoryFull(story, context);
feedUtils.sendStoryFull(story, context);
return true;
case R.id.menu_save_story:
//TODO get folder name
FeedUtils.setStorySaved(story, true, context, null);
feedUtils.setStorySaved(story, true, context, null);
return true;
case R.id.menu_unsave_story:
FeedUtils.setStorySaved(story, false, context, null);
feedUtils.setStorySaved(story, false, context, null);
return true;
case R.id.menu_intel:
@ -442,7 +455,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
case R.id.menu_go_to_feed:
FeedSet fs = FeedSet.singleFeed(story.feedId);
FeedItemsList.startActivity(context, fs,
FeedUtils.getFeed(story.feedId), null);
feedUtils.getFeed(story.feedId), null);
return true;
default:
return false;
@ -477,19 +490,19 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
switch (action) {
case GEST_ACTION_MARKREAD:
FeedUtils.markStoryAsRead(story, context);
feedUtils.markStoryAsRead(story, context);
break;
case GEST_ACTION_MARKUNREAD:
FeedUtils.markStoryUnread(story, context);
feedUtils.markStoryUnread(story, context);
break;
case GEST_ACTION_SAVE:
FeedUtils.setStorySaved(story, true, context, null);
feedUtils.setStorySaved(story, true, context, null);
break;
case GEST_ACTION_UNSAVE:
FeedUtils.setStorySaved(story, false, context, null);
feedUtils.setStorySaved(story, false, context, null);
break;
case GEST_ACTION_STATISTICS:
FeedUtils.openStatistics(context, story.feedId);
feedUtils.openStatistics(context, story.feedId);
break;
case GEST_ACTION_NONE:
default:
@ -575,7 +588,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
// lists with mixed feeds get added info, but single feeds do not
if (!singleFeed) {
FeedUtils.iconLoader.displayImage(story.extern_faviconUrl, vh.feedIconView);
iconLoader.displayImage(story.extern_faviconUrl, vh.feedIconView);
vh.feedTitleView.setText(story.extern_feedTitle);
vh.feedIconView.setVisibility(View.VISIBLE);
vh.feedTitleView.setVisibility(View.VISIBLE);
@ -644,7 +657,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
// the view will display a stale, recycled thumb before the new one loads if the old is not cleared
int thumbSizeGuess = vh.thumbTileView.getMeasuredHeight();
vh.thumbTileView.setImageBitmap(null);
vh.thumbLoader = FeedUtils.thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbTileView, thumbSizeGuess, true);
vh.thumbLoader = thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbTileView, thumbSizeGuess, true);
vh.lastThumbUrl = story.thumbnailUrl;
}
}
@ -681,13 +694,13 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
if (thumbnailStyle == ThumbnailStyle.LEFT_LARGE || thumbnailStyle == ThumbnailStyle.LEFT_SMALL) {
int thumbSizeGuess = vh.thumbViewLeft.getMeasuredHeight();
vh.thumbViewLeft.setImageBitmap(null);
vh.thumbLoader = FeedUtils.thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbViewLeft, thumbSizeGuess, true);
vh.thumbLoader = thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbViewLeft, thumbSizeGuess, true);
vh.thumbViewRight.setVisibility(View.GONE);
vh.thumbViewLeft.setVisibility(View.VISIBLE);
} else if (thumbnailStyle == ThumbnailStyle.RIGHT_LARGE || thumbnailStyle == ThumbnailStyle.RIGHT_SMALL) {
int thumbSizeGuess = vh.thumbViewRight.getMeasuredHeight();
vh.thumbViewRight.setImageBitmap(null);
vh.thumbLoader = FeedUtils.thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbViewRight, thumbSizeGuess, true);
vh.thumbLoader = thumbnailLoader.displayImage(story.thumbnailUrl, vh.thumbViewRight, thumbSizeGuess, true);
vh.thumbViewLeft.setVisibility(View.GONE);
vh.thumbViewRight.setVisibility(View.VISIBLE);
}

View file

@ -0,0 +1,25 @@
package com.newsblur.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class StoryFileCache
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IconFileCache
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ThumbnailFileCache
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IconLoader
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ThumbnailLoader

View file

@ -0,0 +1,21 @@
package com.newsblur.di
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.network.APIManager
import com.newsblur.util.FeedUtils
import com.newsblur.util.ImageLoader
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class FeedModule {
@Singleton
@Provides
fun provideFeedUtils(dbHelper: BlurDatabaseHelper, apiManager: APIManager) =
FeedUtils(dbHelper, apiManager)
}

View file

@ -0,0 +1,30 @@
package com.newsblur.di
import android.content.Context
import com.newsblur.util.FileCache
import com.newsblur.util.ImageLoader
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class ImageModule {
@Singleton
@Provides
@IconLoader
fun provideIconLoader(@ApplicationContext context: Context): ImageLoader =
ImageLoader.asIconLoader(context)
@Singleton
@Provides
@ThumbnailLoader
fun provideThumbnailLoader(
@ApplicationContext context: Context,
@StoryFileCache fileCache: FileCache,
): ImageLoader = ImageLoader.asThumbnailLoader(context, fileCache)
}

View file

@ -0,0 +1,75 @@
package com.newsblur.di
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.newsblur.domain.Classifier
import com.newsblur.domain.Feed
import com.newsblur.domain.Story
import com.newsblur.network.APIConstants
import com.newsblur.network.APIManager
import com.newsblur.serialization.BooleanTypeAdapter
import com.newsblur.serialization.ClassifierMapTypeAdapter
import com.newsblur.serialization.DateStringTypeAdapter
import com.newsblur.serialization.FeedListTypeAdapter
import com.newsblur.serialization.StoryTypeAdapter
import com.newsblur.util.AppConstants
import com.newsblur.util.NetworkUtils
import com.newsblur.util.PrefConstants
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
private typealias CustomUserAgent = String
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideGson(): Gson = GsonBuilder().apply {
registerTypeAdapter(Date::class.java, DateStringTypeAdapter())
registerTypeAdapter(Boolean::class.java, BooleanTypeAdapter())
registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
registerTypeAdapter(Story::class.java, StoryTypeAdapter())
registerTypeAdapter(object : TypeToken<List<Feed?>?>() {}.type, FeedListTypeAdapter())
registerTypeAdapter(object : TypeToken<Map<String?, Classifier?>?>() {}.type, ClassifierMapTypeAdapter())
}.create()
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().apply {
connectTimeout(AppConstants.API_CONN_TIMEOUT_SECONDS, TimeUnit.SECONDS)
readTimeout(AppConstants.API_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
followSslRedirects(true)
}.build()
@Singleton
@Provides
fun provideCustomUserAgent(sharedPreferences: SharedPreferences): CustomUserAgent {
val appVersion: String = sharedPreferences.getString(AppConstants.LAST_APP_VERSION, "unknown_version")!!
return NetworkUtils.getCustomUserAgent(appVersion)
}
@Singleton
@Provides
fun provideApiManager(
@ApplicationContext context: Context,
customUserAgent: CustomUserAgent,
gson: Gson,
okHttpClient: OkHttpClient): APIManager =
APIManager(context,
gson,
customUserAgent,
okHttpClient)
}

View file

@ -0,0 +1,40 @@
package com.newsblur.di
import android.content.Context
import android.content.SharedPreferences
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.util.FileCache
import com.newsblur.util.PrefConstants
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class StorageModule {
@Singleton
@Provides
fun provideSharedPrefs(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences(PrefConstants.PREFERENCES, Context.MODE_PRIVATE)
@Singleton
@Provides
fun provideBlurDbHelper(@ApplicationContext context: Context): BlurDatabaseHelper =
BlurDatabaseHelper(context)
@Singleton
@Provides
@StoryFileCache
fun provideStoryCache(@ApplicationContext context: Context): FileCache =
FileCache.asStoryImageCache(context)
@Singleton
@Provides
@IconFileCache
fun provideIconCache(@ApplicationContext context: Context): FileCache =
FileCache.asIconCache(context)
}

View file

@ -4,9 +4,9 @@ import android.content.ContentValues;
import android.database.Cursor;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import java.util.Comparator;
@ -24,11 +24,11 @@ public class SavedSearch {
public String feedTitle;
public String faviconUrl;
public ContentValues getValues() {
public ContentValues getValues(BlurDatabaseHelper dbHelper) {
ContentValues values = new ContentValues();
String feedTitle = "\"<b>" + query + "</b>\" in <b>" + getFeedTitle() + "</b>";
String feedTitle = "\"<b>" + query + "</b>\" in <b>" + getFeedTitle(dbHelper) + "</b>";
values.put(DatabaseConstants.SAVED_SEARCH_FEED_TITLE, feedTitle);
values.put(DatabaseConstants.SAVED_SEARCH_FAVICON, getFaviconUrl());
values.put(DatabaseConstants.SAVED_SEARCH_FAVICON, getFaviconUrl(dbHelper));
values.put(DatabaseConstants.SAVED_SEARCH_ADDRESS, feedAddress);
values.put(DatabaseConstants.SAVED_SEARCH_QUERY, query);
values.put(DatabaseConstants.SAVED_SEARCH_FEED_ID, feedId);
@ -48,7 +48,7 @@ public class SavedSearch {
return savedSearch;
}
private String getFeedTitle() {
private String getFeedTitle(BlurDatabaseHelper dbHelper) {
String feedTitle = null;
if (feedId.equals("river:")) {
@ -57,14 +57,14 @@ public class SavedSearch {
feedTitle = "Infrequent Site Stories";
} else if (feedId.startsWith("river:")) {
String folderName = feedId.replace("river:", "");
FeedSet fs = FeedUtils.feedSetFromFolderName(folderName);
FeedSet fs = dbHelper.feedSetFromFolderName(folderName);
feedTitle = fs.getFolderName();
} else if (feedId.equals("read")) {
feedTitle = "Read Stories";
} else if (feedId.startsWith("starred")) {
feedTitle = "Saved Stories";
String tag = feedId.replace("starred:", "");
StarredCount starredFeed = FeedUtils.getStarredFeedByTag(tag);
StarredCount starredFeed = dbHelper.getStarredFeedByTag(tag);
if (starredFeed != null) {
String tagSlug = tag.replace(" ", "-");
if (starredFeed.tag.equals(tag) || starredFeed.tag.equals(tagSlug)) {
@ -72,11 +72,11 @@ public class SavedSearch {
}
}
} else if (feedId.startsWith("feed:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("feed:", ""));
Feed feed = dbHelper.getFeed(feedId.replace("feed:", ""));
if (feed == null) return null;
feedTitle = feed.title;
} else if (feedId.startsWith("social:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("social:", ""));
Feed feed = dbHelper.getFeed(feedId.replace("social:", ""));
if (feed == null) return null;
feedTitle = feed.title;
}
@ -84,7 +84,7 @@ public class SavedSearch {
return feedTitle;
}
private String getFaviconUrl() {
private String getFaviconUrl(BlurDatabaseHelper dbHelper) {
String url = null;
if (feedId.equals("river:") || feedId.equals("river:infrequent")) {
url = "https://newsblur.com/media/img/icons/circular/ak-icon-allstories.png";
@ -97,12 +97,12 @@ public class SavedSearch {
} else if (feedId.startsWith("starred:")) {
url = "https://newsblur.com/media/img/reader/tag.png";
} else if (feedId.startsWith("feed:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("feed:", ""));
Feed feed = dbHelper.getFeed(feedId.replace("feed:", ""));
if (feed != null) {
url = feed.faviconUrl;
}
} else if (feedId.startsWith("social:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("social:", ""));
Feed feed = dbHelper.getFeed(feedId.replace("social:", ""));
if (feed != null) {
url = feed.faviconUrl;
}

View file

@ -16,6 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.newsblur.R
import com.newsblur.activity.Main
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.databinding.DialogAddFeedBinding
import com.newsblur.databinding.RowAddFeedFolderBinding
import com.newsblur.domain.Folder
@ -23,17 +24,24 @@ import com.newsblur.fragment.AddFeedFragment.AddFeedAdapter.FolderViewHolder
import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncService
import com.newsblur.util.AppConstants
import com.newsblur.util.FeedUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class AddFeedFragment : DialogFragment() {
@Inject
lateinit var apiManager: APIManager
@Inject
lateinit var dbHelper: BlurDatabaseHelper
private lateinit var binding: DialogAddFeedBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val apiManager = APIManager(requireContext())
val v = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_add_feed, null)
binding = DialogAddFeedBinding.bind(v)
@ -61,7 +69,7 @@ class AddFeedFragment : DialogFragment() {
}
binding.recyclerViewFolders.addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL))
binding.recyclerViewFolders.adapter = adapter
adapter.setFolders(FeedUtils.dbHelper!!.folders)
adapter.setFolders(dbHelper.folders)
return builder.create()
}

View file

@ -13,10 +13,15 @@ import com.newsblur.activity.AddTwitter
import com.newsblur.databinding.FragmentAddsocialBinding
import com.newsblur.network.APIManager
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class AddSocialFragment : Fragment() {
private lateinit var apiManager: APIManager
@Inject
lateinit var apiManager: APIManager
private lateinit var binding: FragmentAddsocialBinding
private var twitterAuthed = false
@ -25,7 +30,6 @@ class AddSocialFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
apiManager = APIManager(requireActivity())
}
fun setTwitterAuthed() {

View file

@ -9,6 +9,9 @@ import androidx.fragment.app.DialogFragment;
import com.newsblur.R;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class AlertDialogFragment extends DialogFragment {
private static final String MESSAGE = "message";

View file

@ -22,13 +22,25 @@ import android.widget.CheckBox;
import android.widget.ListAdapter;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.databinding.DialogChoosefoldersBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ChooseFoldersFragment extends DialogFragment {
@Inject
BlurDatabaseHelper dbHelper;
@Inject
FeedUtils feedUtils;
private Feed feed;
public static ChooseFoldersFragment newInstance(Feed feed) {
@ -43,7 +55,7 @@ public class ChooseFoldersFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
feed = (Feed) getArguments().getSerializable("feed");
final List<Folder> folders = FeedUtils.dbHelper.getFolders();
final List<Folder> folders = dbHelper.getFolders();
Collections.sort(folders, Folder.FolderComparator);
final Set<String> newFolders = new HashSet<String>();
@ -73,7 +85,7 @@ public class ChooseFoldersFragment extends DialogFragment {
builder.setPositiveButton(R.string.dialog_folders_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.moveFeedToFolders(activity, feed.feedId, newFolders, oldFolders);
feedUtils.moveFeedToFolders(activity, feed.feedId, newFolders, oldFolders);
ChooseFoldersFragment.this.dismiss();
}
});

View file

@ -16,7 +16,16 @@ import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class DeleteFeedFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String FEED_TYPE = "feed_type";
private static final String FEED_ID = "feed_id";
private static final String FEED_NAME = "feed_name";
@ -73,11 +82,11 @@ 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), 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), getActivity());
feedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity());
} else {
FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity());
feedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity());
}
// if called from a feed view, end it
Activity activity = DeleteFeedFragment.this.getActivity();

View file

@ -10,12 +10,19 @@ import androidx.fragment.app.DialogFragment;
import android.text.TextUtils;
import com.newsblur.R;
import com.newsblur.network.APIManager;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class DeleteFolderFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String FOLDER_NAME = "folder_name";
private static final String FOLDER_PARENT = "folder_parent";
@ -42,7 +49,7 @@ public class DeleteFolderFragment extends DialogFragment {
if (!TextUtils.isEmpty(folderParent) && !folderParent.equals(AppConstants.ROOT_FOLDER)) {
inFolder = folderParent;
}
FeedUtils.deleteFolder(folderName, inFolder, getActivity(), new APIManager(getActivity()));
feedUtils.deleteFolder(folderName, inFolder, getActivity());
DeleteFolderFragment.this.dismiss();
}
});

View file

@ -15,8 +15,16 @@ import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class EditReplyDialogFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String STORY = "story";
private static final String COMMENT_USER_ID = "comment_user_id";
private static final String REPLY_ID = "reply_id";
@ -54,14 +62,14 @@ public class EditReplyDialogFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String replyText = reply.getText().toString();
FeedUtils.updateReply(activity, story, commentUserId, replyId, replyText);
feedUtils.updateReply(activity, story, commentUserId, replyId, replyText);
EditReplyDialogFragment.this.dismiss();
}
});
builder.setNegativeButton(R.string.edit_reply_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.deleteReply(activity, story, commentUserId, replyId);
feedUtils.deleteReply(activity, story, commentUserId, replyId);
EditReplyDialogFragment.this.dismiss();
}
});

View file

@ -16,6 +16,7 @@ import android.view.View;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.databinding.DialogTrainfeedBinding;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
@ -23,8 +24,19 @@ import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class FeedIntelTrainerFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
private Feed feed;
private FeedSet fs;
private Classifier classifier;
@ -44,7 +56,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
super.onCreate(savedInstanceState);
feed = (Feed) getArguments().getSerializable("feed");
fs = (FeedSet) getArguments().getSerializable("feedset");
classifier = FeedUtils.dbHelper.getClassifierForFeed(feed.feedId);
classifier = dbHelper.getClassifierForFeed(feed.feedId);
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
@ -62,7 +74,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
if (classifier.title.size() < 1) binding.intelTitleHeader.setVisibility(View.GONE);
// get the list of suggested tags
List<String> allTags = FeedUtils.dbHelper.getTagsForFeed(feed.feedId);
List<String> allTags = dbHelper.getTagsForFeed(feed.feedId);
// augment that list with known trained tags
for (Map.Entry<String, Integer> rule : classifier.tags.entrySet()) {
if (!allTags.contains(rule.getKey())) {
@ -79,7 +91,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
if (allTags.size() < 1) binding.intelTagHeader.setVisibility(View.GONE);
// get the list of suggested authors
List<String> allAuthors = FeedUtils.dbHelper.getAuthorsForFeed(feed.feedId);
List<String> allAuthors = dbHelper.getAuthorsForFeed(feed.feedId);
// augment that list with known trained authors
for (Map.Entry<String, Integer> rule : classifier.authors.entrySet()) {
if (!allAuthors.contains(rule.getKey())) {
@ -115,7 +127,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
builder.setPositiveButton(R.string.dialog_story_intel_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.updateClassifier(feed.feedId, classifier, fs, activity);
feedUtils.updateClassifier(feed.feedId, classifier, fs, activity);
FeedIntelTrainerFragment.this.dismiss();
}
});

View file

@ -43,8 +43,10 @@ import com.newsblur.activity.NbActivity;
import com.newsblur.activity.ReadStoriesItemsList;
import com.newsblur.activity.SavedStoriesItemsList;
import com.newsblur.activity.SocialFeedItemsList;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.FolderListAdapter;
import com.newsblur.databinding.FragmentFolderfeedlistBinding;
import com.newsblur.di.IconLoader;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
@ -52,18 +54,33 @@ import com.newsblur.domain.SocialFeed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
import com.newsblur.viewModel.AllFoldersViewModel;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class FolderListFragment extends NbFragment implements OnCreateContextMenuListener,
OnChildClickListener,
OnGroupClickListener,
OnGroupCollapseListener,
OnGroupExpandListener {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
@Inject
@IconLoader
ImageLoader iconLoader;
private AllFoldersViewModel allFoldersViewModel;
private FolderListAdapter adapter;
public StateFilter currentState = StateFilter.SOME;
@ -80,9 +97,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
super.onCreate(savedInstanceState);
allFoldersViewModel = new ViewModelProvider(this).get(AllFoldersViewModel.class);
currentState = PrefsUtils.getStateFilter(getActivity());
adapter = new FolderListAdapter(getActivity(), currentState);
adapter = new FolderListAdapter(getActivity(), currentState, iconLoader, dbHelper);
sharedPreferences = getActivity().getSharedPreferences(PrefConstants.PREFERENCES, 0);
FeedUtils.currentFolderName = null;
feedUtils.currentFolderName = null;
// NB: it is by design that loaders are not started until we get a
// ping from the sync service indicating that it has initialised
}
@ -291,15 +308,15 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
return true;
}
if (item.getItemId() == R.id.menu_notifications_disable) {
FeedUtils.disableNotifications(getActivity(), lastMenuFeed);
feedUtils.disableNotifications(getActivity(), lastMenuFeed);
return true;
}
if (item.getItemId() == R.id.menu_notifications_focus) {
FeedUtils.enableFocusNotifications(getActivity(), lastMenuFeed);
feedUtils.enableFocusNotifications(getActivity(), lastMenuFeed);
return true;
}
if (item.getItemId() == R.id.menu_notifications_unread) {
FeedUtils.enableUnreadNotifications(getActivity(), lastMenuFeed);
feedUtils.enableUnreadNotifications(getActivity(), lastMenuFeed);
return true;
}
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) item.getMenuInfo();
@ -339,17 +356,17 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
} else if (item.getItemId() == R.id.menu_mute_feed) {
Set<String> feedIds = new HashSet<String>();
feedIds.add(adapter.getFeed(groupPosition, childPosition).feedId);
FeedUtils.muteFeeds(getActivity(), feedIds);
feedUtils.muteFeeds(getActivity(), feedIds);
} else if (item.getItemId() == R.id.menu_unmute_feed) {
Set<String> feedIds = new HashSet<String>();
feedIds.add(adapter.getFeed(groupPosition, childPosition).feedId);
FeedUtils.unmuteFeeds(getActivity(), feedIds);
feedUtils.unmuteFeeds(getActivity(), feedIds);
} else if (item.getItemId() == R.id.menu_mute_folder) {
FeedUtils.muteFeeds(getActivity(), adapter.getAllFeedsForFolder(groupPosition));
feedUtils.muteFeeds(getActivity(), adapter.getAllFeedsForFolder(groupPosition));
} else if (item.getItemId() == R.id.menu_unmute_folder) {
FeedUtils.unmuteFeeds(getActivity(), adapter.getAllFeedsForFolder(groupPosition));
feedUtils.unmuteFeeds(getActivity(), adapter.getAllFeedsForFolder(groupPosition));
} else if (item.getItemId() == R.id.menu_instafetch_feed) {
FeedUtils.instaFetchFeed(getActivity(), adapter.getFeed(groupPosition, childPosition).feedId);
feedUtils.instaFetchFeed(getActivity(), adapter.getFeed(groupPosition, childPosition).feedId);
} else if (item.getItemId() == R.id.menu_intel) {
FeedIntelTrainerFragment intelFrag = FeedIntelTrainerFragment.newInstance(adapter.getFeed(groupPosition, childPosition), adapter.getChild(groupPosition, childPosition));
intelFrag.show(getParentFragmentManager(), FeedIntelTrainerFragment.class.getName());
@ -375,7 +392,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
}
private void markFeedsAsRead(FeedSet fs) {
FeedUtils.markRead(((NbActivity) getActivity()), fs, null, null, R.array.mark_all_read_options, false);
feedUtils.markRead(((NbActivity) getActivity()), fs, null, null, R.array.mark_all_read_options, false);
adapter.lastFeedViewedId = fs.getSingleFeed();
adapter.lastFolderViewed = fs.getFolderName();
}
@ -498,7 +515,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
@Override
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
FeedUtils.currentFolderName = null;
feedUtils.currentFolderName = null;
FeedSet fs = adapter.getChild(groupPosition, childPosition);
if (adapter.isRowAllSharedStories(groupPosition)) {
SocialFeed socialFeed = adapter.getSocialFeed(groupPosition, childPosition);
@ -519,10 +536,10 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
// and the folder name is a bit of metadata needed by the UI/API
String folderName = adapter.getGroupFolderName(groupPosition);
if(folderName == null || folderName.equals(AppConstants.ROOT_FOLDER)){
FeedUtils.currentFolderName = null;
feedUtils.currentFolderName = null;
}else{
FeedUtils.currentFolderName = folderName;
feedUtils.currentFolderName = folderName;
}
FeedItemsList.startActivity(getActivity(), fs, feed, folderName);
adapter.lastFeedViewedId = feed.feedId;
@ -555,7 +572,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
} else if (feedId.startsWith("river:")) {
intent = new Intent(getActivity(), FolderItemsList.class);
String folderName = feedId.replace("river:", "");
fs = FeedUtils.feedSetFromFolderName(folderName);
fs = dbHelper.feedSetFromFolderName(folderName);
intent.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, folderName);
} else if (feedId.equals("read")) {
intent = new Intent(getActivity(), ReadStoriesItemsList.class);
@ -569,14 +586,14 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
} else if (feedId.startsWith("feed:")) {
intent = new Intent(getActivity(), FeedItemsList.class);
String cleanFeedId = feedId.replace("feed:", "");
Feed feed = FeedUtils.getFeed(cleanFeedId);
Feed feed = feedUtils.getFeed(cleanFeedId);
fs = FeedSet.singleFeed(cleanFeedId);
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
} else if (feedId.startsWith("social:")) {
intent = new Intent(getActivity(), SocialFeedItemsList.class);
String cleanFeedId = feedId.replace("social:", "");
fs = FeedSet.singleFeed(cleanFeedId);
Feed feed = FeedUtils.getFeed(cleanFeedId);
Feed feed = feedUtils.getFeed(cleanFeedId);
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
}

View file

@ -23,13 +23,17 @@ import android.widget.FrameLayout;
import com.newsblur.R;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.StoryViewAdapter;
import com.newsblur.databinding.FragmentItemgridBinding;
import com.newsblur.databinding.RowFleuronBinding;
import com.newsblur.di.IconLoader;
import com.newsblur.di.ThumbnailLoader;
import com.newsblur.domain.Story;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryListStyle;
@ -39,8 +43,27 @@ import com.newsblur.util.ViewUtils;
import com.newsblur.view.ProgressThrobber;
import com.newsblur.viewModel.StoriesViewModel;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ItemSetFragment extends NbFragment {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
@Inject
@IconLoader
ImageLoader iconLoader;
@Inject
@ThumbnailLoader
ImageLoader thumbnailLoader;
private static final String BUNDLE_GRIDSTATE = "gridstate";
protected boolean cursorSeenYet = false; // have we yet seen a valid cursor for our particular feedset?
@ -164,7 +187,7 @@ public class ItemSetFragment extends NbFragment {
}
});
adapter = new StoryViewAdapter(((NbActivity) getActivity()), this, getFeedSet(), listStyle);
adapter = new StoryViewAdapter(((NbActivity) getActivity()), this, getFeedSet(), listStyle, iconLoader, thumbnailLoader, feedUtils);
adapter.addFooterView(footerView);
adapter.addFooterView(fleuronBinding.getRoot());
binding.itemgridfragmentGrid.setAdapter(adapter);
@ -267,7 +290,7 @@ public class ItemSetFragment extends NbFragment {
private void setCursor(Cursor cursor) {
if (cursor != null) {
if (!FeedUtils.dbHelper.isFeedSetReady(getFeedSet())) {
if (!dbHelper.isFeedSetReady(getFeedSet())) {
// the DB hasn't caught up yet from the last story list; don't display stale stories.
com.newsblur.util.Log.i(this.getClass().getName(), "stale load");
updateAdapter(null);
@ -451,7 +474,7 @@ public class ItemSetFragment extends NbFragment {
int index = markEnd - i;
Story story = adapter.getStory(index);
if (story != null) {
FeedUtils.markStoryAsRead(story, requireContext());
feedUtils.markStoryAsRead(story, requireContext());
}
}
}

View file

@ -10,14 +10,24 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.newsblur.R
import com.newsblur.activity.Main
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.databinding.LoginasDialogBinding
import com.newsblur.network.APIManager
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class LoginAsDialogFragment : DialogFragment() {
@Inject
lateinit var apiManager: APIManager
@Inject
lateinit var dbHelper: BlurDatabaseHelper
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireActivity())
builder.setTitle(R.string.loginas_title)
@ -27,13 +37,12 @@ class LoginAsDialogFragment : DialogFragment() {
builder.setView(binding.root)
builder.setPositiveButton(R.string.alert_dialog_ok) { _, _ ->
val apiManager = APIManager(requireActivity())
val username = binding.usernameField.text.toString()
lifecycleScope.executeAsyncTask(
doInBackground = {
val result = apiManager.loginAs(username)
if (result) {
PrefsUtils.clearPrefsAndDbForLoginAs(requireActivity())
PrefsUtils.clearPrefsAndDbForLoginAs(requireActivity(), dbHelper)
apiManager.updateUserProfile()
}
result

View file

@ -8,10 +8,19 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.util.PrefsUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class LogoutDialogFragment extends DialogFragment {
@Inject
BlurDatabaseHelper dbHelper;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@ -19,7 +28,7 @@ public class LogoutDialogFragment extends DialogFragment {
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
PrefsUtils.logout(getActivity());
PrefsUtils.logout(getActivity(), dbHelper);
// make sure the instance of Main that called us is killed now, or else the system
// might try to recycle it with a stale login ID, which will cause it to self-destruct
getActivity().finish();

View file

@ -5,6 +5,7 @@ import android.content.Context;
import com.newsblur.domain.ActivityDetails;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.domain.ActivitiesResponse;
import com.newsblur.util.ImageLoader;
import com.newsblur.view.ActivitiesAdapter;
import com.newsblur.view.ActivityDetailsAdapter;
@ -14,8 +15,8 @@ import com.newsblur.view.ActivityDetailsAdapter;
public class ProfileActivitiesFragment extends ProfileActivityDetailsFragment {
@Override
protected ActivityDetailsAdapter createAdapter(Context context, UserDetails user) {
return new ActivitiesAdapter(context, user);
protected ActivityDetailsAdapter createAdapter(Context context, UserDetails user, ImageLoader imageLoader) {
return new ActivitiesAdapter(context, user, iconLoader);
}
@Override

View file

@ -13,29 +13,37 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.newsblur.R
import com.newsblur.activity.Profile
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.databinding.FragmentProfileactivityBinding
import com.newsblur.databinding.RowLoadingThrobberBinding
import com.newsblur.di.IconLoader
import com.newsblur.domain.ActivityDetails
import com.newsblur.domain.UserDetails
import com.newsblur.network.APIManager
import com.newsblur.util.*
import com.newsblur.view.ActivityDetailsAdapter
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener {
@Inject
lateinit var apiManager: APIManager
@Inject
lateinit var dbHelper: BlurDatabaseHelper
@Inject
@IconLoader
lateinit var iconLoader: ImageLoader
private lateinit var binding: FragmentProfileactivityBinding
private lateinit var footerBinding: RowLoadingThrobberBinding
@JvmField
protected var apiManager: APIManager? = null
private var adapter: ActivityDetailsAdapter? = null
private var user: UserDetails? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
apiManager = APIManager(requireActivity())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_profileactivity, null)
binding = FragmentProfileactivityBinding.bind(view)
@ -59,13 +67,13 @@ abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener
return view
}
fun setUser(context: Context?, user: UserDetails?) {
fun setUser(context: Context?, user: UserDetails?, iconLoader: ImageLoader) {
this.user = user
adapter = createAdapter(context, user)
adapter = createAdapter(context, user, iconLoader)
displayActivities()
}
protected abstract fun createAdapter(context: Context?, user: UserDetails?): ActivityDetailsAdapter?
protected abstract fun createAdapter(context: Context?, user: UserDetails?, iconLoader: ImageLoader): ActivityDetailsAdapter?
private fun displayActivities() {
binding.profileDetailsActivitylist.adapter = adapter
loadPage(1)
@ -116,7 +124,7 @@ abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener
i.putExtra(Profile.USER_ID, activity.withUserId)
context.startActivity(i)
} else if (activity.category == ActivityDetails.Category.FEED_SUBSCRIPTION) {
val feed = FeedUtils.getFeed(activity.feedId)
val feed = dbHelper.getFeed(activity.feedId)
if (feed == null) {
Toast.makeText(context, R.string.profile_feed_not_available, Toast.LENGTH_SHORT).show()
} else {
@ -133,7 +141,7 @@ abstract class ProfileActivityDetailsFragment : Fragment(), OnItemClickListener
} else if (isSocialFeedCategory(activity)) {
// Strip the social: prefix from feedId
val socialFeedId = activity.feedId.substring(7)
val feed = FeedUtils.getSocialFeed(socialFeedId)
val feed = dbHelper.getSocialFeed(socialFeedId)
if (feed == null) {
Toast.makeText(context, R.string.profile_do_not_follow, Toast.LENGTH_SHORT).show()
} else {

View file

@ -10,26 +10,32 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.newsblur.R
import com.newsblur.databinding.FragmentProfiledetailsBinding
import com.newsblur.di.IconLoader
import com.newsblur.domain.UserDetails
import com.newsblur.network.APIManager
import com.newsblur.util.FeedUtils
import com.newsblur.util.ImageLoader
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class ProfileDetailsFragment : Fragment() {
@Inject
lateinit var apiManager: APIManager
@IconLoader
@Inject
lateinit var iconLoader: ImageLoader
private var user: UserDetails? = null
private var viewingSelf = false
private lateinit var apiManager: APIManager
private lateinit var binding: FragmentProfiledetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
apiManager = APIManager(requireContext())
}
fun setUser(context: Context, user: UserDetails?, viewingSelf: Boolean) {
this.user = user
this.viewingSelf = viewingSelf
@ -69,7 +75,7 @@ class ProfileDetailsFragment : Fragment() {
binding.profileUserStatistics.profileFollowercount.text = user!!.followerCount.toString()
binding.profileUserStatistics.profileFollowingcount.text = user!!.followingCount.toString()
if (!viewingSelf) {
FeedUtils.iconLoader!!.displayImage(user!!.photoUrl, binding.profilePicture)
iconLoader.displayImage(user!!.photoUrl, binding.profilePicture)
if (user!!.followedByYou) {
binding.profileUnfollowButton.visibility = View.VISIBLE
binding.profileFollowButton.visibility = View.GONE

View file

@ -5,6 +5,7 @@ import android.content.Context;
import com.newsblur.domain.ActivityDetails;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.domain.InteractionsResponse;
import com.newsblur.util.ImageLoader;
import com.newsblur.view.ActivityDetailsAdapter;
import com.newsblur.view.InteractionsAdapter;
@ -14,8 +15,8 @@ import com.newsblur.view.InteractionsAdapter;
public class ProfileInteractionsFragment extends ProfileActivityDetailsFragment {
@Override
protected ActivityDetailsAdapter createAdapter(Context context, UserDetails user) {
return new InteractionsAdapter(context, user);
protected ActivityDetailsAdapter createAdapter(Context context, UserDetails user, ImageLoader iconLoader) {
return new InteractionsAdapter(context, user, iconLoader);
}
@Override

View file

@ -10,8 +10,16 @@ import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ReadingActionConfirmationFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String READING_ACTION = "reading_action";
private static final String DIALOG_TITLE = "dialog_title";
private static final String DIALOG_MESSAGE = "dialog_message";
@ -46,7 +54,7 @@ public class ReadingActionConfirmationFragment extends DialogFragment {
builder.setItems(choicesId, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
FeedUtils.doAction(ra, getActivity());
feedUtils.doAction(ra, getActivity());
if (finishAfter) {
getActivity().finish();
}

View file

@ -22,8 +22,11 @@ import com.google.android.material.chip.Chip
import com.newsblur.R
import com.newsblur.activity.FeedItemsList
import com.newsblur.activity.Reading
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.databinding.FragmentReadingitemBinding
import com.newsblur.databinding.ReadingItemActionsBinding
import com.newsblur.di.IconLoader
import com.newsblur.di.StoryFileCache
import com.newsblur.domain.Classifier
import com.newsblur.domain.Story
import com.newsblur.domain.UserDetails
@ -35,11 +38,32 @@ import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_TEXT
import com.newsblur.service.OriginalTextService
import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import java.util.regex.Pattern
import javax.inject.Inject
import kotlin.math.roundToInt
@AndroidEntryPoint
class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
@Inject
lateinit var apiManager: APIManager
@Inject
lateinit var dbHelper: BlurDatabaseHelper
@Inject
lateinit var feedUtils: FeedUtils
@Inject
@IconLoader
lateinit var iconLoader: ImageLoader
@Inject
@StoryFileCache
lateinit var storyImageCache: FileCache
@JvmField
var story: Story? = null
@ -271,11 +295,11 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
true
}
R.id.menu_send_story -> {
FeedUtils.sendStoryUrl(story, requireContext())
feedUtils.sendStoryUrl(story, requireContext())
true
}
R.id.menu_send_story_full -> {
FeedUtils.sendStoryFull(story, requireContext())
feedUtils.sendStoryFull(story, requireContext())
true
}
R.id.menu_textsize -> {
@ -290,14 +314,14 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
R.id.menu_reading_save -> {
if (story!!.starred) {
FeedUtils.setStorySaved(story!!, false, requireContext(), null)
feedUtils.setStorySaved(story!!, false, requireContext(), null)
} else {
FeedUtils.setStorySaved(story!!.storyHash, true, requireContext())
feedUtils.setStorySaved(story!!.storyHash, true, requireContext())
}
true
}
R.id.menu_reading_markunread -> {
FeedUtils.markStoryUnread(story!!, requireContext())
feedUtils.markStoryUnread(story!!, requireContext())
true
}
R.id.menu_theme_auto -> {
@ -328,7 +352,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
true
}
R.id.menu_go_to_feed -> {
FeedItemsList.startActivity(context, fs, FeedUtils.getFeed(story!!.feedId), null)
FeedItemsList.startActivity(context, fs, dbHelper.getFeed(story!!.feedId), null)
true
}
else -> {
@ -337,8 +361,8 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
private fun clickMarkStoryRead() {
if (story!!.read) FeedUtils.markStoryUnread(story!!, requireContext())
else FeedUtils.markStoryAsRead(story!!, requireContext())
if (story!!.read) feedUtils.markStoryUnread(story!!, requireContext())
else feedUtils.markStoryAsRead(story!!, requireContext())
}
private fun updateMarkReadButton() {
@ -361,9 +385,9 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
private fun clickSave() {
if (story!!.starred) {
FeedUtils.setStorySaved(story!!.storyHash, false, requireContext())
feedUtils.setStorySaved(story!!.storyHash, false, requireContext())
} else {
FeedUtils.setStorySaved(story!!.storyHash, true, requireContext())
feedUtils.setStorySaved(story!!.storyHash, true, requireContext())
}
}
@ -387,7 +411,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
private fun setupItemCommentsAndShares() {
SetupCommentSectionTask(this, binding.root, layoutInflater, story).execute()
SetupCommentSectionTask(this, binding.root, layoutInflater, story, iconLoader).execute()
}
private fun setupItemMetadata() {
@ -412,7 +436,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
binding.readingFeedTitle.visibility = View.GONE
binding.readingFeedIcon.visibility = View.GONE
} else {
FeedUtils.iconLoader!!.displayImage(feedIconUrl, binding.readingFeedIcon)
iconLoader.displayImage(feedIconUrl, binding.readingFeedIcon)
binding.readingFeedTitle.text = feedTitle
}
@ -634,7 +658,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
setupItemCommentsAndShares()
}
if (updateType and UPDATE_INTEL != 0) {
classifier = FeedUtils.dbHelper!!.getClassifierForFeed(story!!.feedId)
classifier = dbHelper.getClassifierForFeed(story!!.feedId)
setupTagsAndIntel()
}
}
@ -643,7 +667,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
story?.let { story ->
lifecycleScope.executeAsyncTask(
doInBackground = {
FeedUtils.getStoryText(story.storyHash)
feedUtils.getStoryText(story.storyHash)
},
onPostExecute = { result ->
if (result != null) {
@ -669,7 +693,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
story?.let { story ->
lifecycleScope.executeAsyncTask(
doInBackground = {
FeedUtils.getStoryContent(story.storyHash)
feedUtils.getStoryContent(story.storyHash)
},
onPostExecute = { result ->
if (result != null) {
@ -692,7 +716,6 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
binding.readingStoryChanges.setText(R.string.story_changes_loading)
},
doInBackground = {
val apiManager = APIManager(requireContext())
apiManager.getStoryChanges(story.storyHash, showChanges)
},
onPostExecute = { response ->
@ -813,7 +836,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
val imageTagMatcher = imgSniff.matcher(html)
while (imageTagMatcher.find()) {
val url = imageTagMatcher.group(2)
val localPath = FeedUtils.storyImageCache!!.getCachedLocation(url) ?: continue
val localPath = storyImageCache.getCachedLocation(url) ?: continue
html = html.replace(imageTagMatcher.group(1) + "\"" + url + "\"", "src=\"$localPath\"")
imageUrlRemaps!![localPath] = url
}

View file

@ -15,12 +15,19 @@ import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.databinding.DialogRenameBinding;
import com.newsblur.domain.Feed;
import com.newsblur.network.APIManager;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class RenameDialogFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String FEED = "feed";
private static final String FOLDER = "folder";
private static final String FOLDER_NAME = "folder_name";
@ -71,7 +78,7 @@ public class RenameDialogFragment extends DialogFragment {
builder.setPositiveButton(R.string.feed_name_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.renameFeed(activity, feed.feedId, binding.inputName.getText().toString());
feedUtils.renameFeed(activity, feed.feedId, binding.inputName.getText().toString());
RenameDialogFragment.this.dismiss();
}
});
@ -95,7 +102,7 @@ public class RenameDialogFragment extends DialogFragment {
if (!TextUtils.isEmpty(folderParentName) && !folderParentName.equals(AppConstants.ROOT_FOLDER)) {
inFolder = folderParentName;
}
FeedUtils.renameFolder(folderName, newFolderName, inFolder, activity, new APIManager(activity));
feedUtils.renameFolder(folderName, newFolderName, inFolder, activity);
RenameDialogFragment.this.dismiss();
}
});

View file

@ -15,8 +15,16 @@ import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ReplyDialogFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String STORY = "story";
private static final String COMMENT_USER_ID = "comment_user_id";
private static final String COMMENT_USERNAME = "comment_username";
@ -53,7 +61,7 @@ public class ReplyDialogFragment extends DialogFragment {
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.replyToComment(story.id, story.feedId, commentUserId, reply.getText().toString(), activity);
feedUtils.replyToComment(story.id, story.feedId, commentUserId, reply.getText().toString(), activity);
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {

View file

@ -8,11 +8,18 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.newsblur.R;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class SaveSearchFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
private static final String FEED_ID = "feed_id";
private static final String QUERY = "query";
@ -33,7 +40,7 @@ public class SaveSearchFragment extends DialogFragment {
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.saveSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity(), new APIManager(getActivity()));
feedUtils.saveSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity());
SaveSearchFragment.this.dismiss();
}
});

View file

@ -7,11 +7,18 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import javax.inject.Inject;
public class SettingsFragment extends PreferenceFragmentCompat {
@Inject
BlurDatabaseHelper dbHelper;
private Preference deleteOfflineStoriesPref;
@Override
@ -32,9 +39,12 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private void deleteOfflineStories() {
if (deleteOfflineStoriesPref != null) {
deleteOfflineStoriesPref.setOnPreferenceClickListener(null);
FeedUtils.syncOfflineStories(requireContext());
deleteOfflineStoriesPref.setSummary("");
deleteOfflineStoriesPref.setTitle(R.string.menu_delete_offline_stories_confirmation);
dbHelper.deleteStories();
NBSyncService.forceFeedsFolders();
FeedUtils.triggerSync(requireContext());
}
}
}

View file

@ -23,7 +23,7 @@ import com.newsblur.view.RoundedImageView
import java.lang.ref.WeakReference
import java.util.*
class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: View, inflater: LayoutInflater, story: Story?) {
class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: View, inflater: LayoutInflater, story: Story?, iconLoader: ImageLoader) {
private var topCommentViews: ArrayList<View>? = null
private var topShareViews: ArrayList<View>? = null
@ -36,6 +36,7 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
private val context: Context?
private val user: UserDetails
private val manager: FragmentManager
private val iconLoader: ImageLoader
private var comments: MutableList<Comment>? = null
/**
@ -54,7 +55,7 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
private fun doInBackground() {
if (context == null || story == null) return
comments = FeedUtils.dbHelper!!.getComments(story.id)
comments = fragment.dbHelper.getComments(story.id)
topCommentViews = ArrayList()
topShareViews = ArrayList()
publicCommentViews = ArrayList()
@ -70,7 +71,7 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
if (!comment.byFriend && !PrefsUtils.showPublicComments(context)) {
continue
}
val commentUser = FeedUtils.dbHelper!!.getUserProfile(comment.userId)
val commentUser = fragment.dbHelper.getUserProfile(comment.userId)
// rarely, we get a comment but never got the user's profile, so we can't display it
if (commentUser == null) {
Log.w(this.javaClass.name, "cannot display comment from missing user ID: " + comment.userId)
@ -94,9 +95,9 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
}
for (id in comment.likingUsers) {
val favouriteImage = RoundedImageView(context)
val user = FeedUtils.dbHelper!!.getUserProfile(id)
val user = fragment.dbHelper.getUserProfile(id)
if (user != null) {
FeedUtils.iconLoader!!.displayImage(user.photoUrl, favouriteImage)
fragment.iconLoader.displayImage(user.photoUrl, favouriteImage)
favouriteContainer.addView(favouriteImage)
}
}
@ -107,9 +108,9 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
} else {
favouriteIcon.setOnClickListener {
if (!mutableListOf<String>(*comment.likingUsers).contains(user.id)) {
FeedUtils.likeComment(story, comment.userId, context)
fragment.feedUtils.likeComment(story, comment.userId, context)
} else {
FeedUtils.unlikeComment(story, comment.userId, context)
fragment.feedUtils.unlikeComment(story, comment.userId, context)
}
}
}
@ -118,22 +119,22 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
replyIcon.visibility = View.INVISIBLE
} else {
replyIcon.setOnClickListener {
val user = FeedUtils.dbHelper!!.getUserProfile(comment.userId)
val user = fragment.dbHelper.getUserProfile(comment.userId)
if (user != null) {
val newFragment: DialogFragment = ReplyDialogFragment.newInstance(story, comment.userId, user.username)
newFragment.show(manager, "dialog")
}
}
}
val replies = FeedUtils.dbHelper!!.getCommentReplies(comment.id)
val replies = fragment.dbHelper.getCommentReplies(comment.id)
for (reply in replies) {
val replyView = inflater.inflate(R.layout.include_reply, null)
val replyText = replyView.findViewById<View>(R.id.reply_text) as TextView
replyText.text = UIUtils.fromHtml(reply.text)
val replyImage = replyView.findViewById<View>(R.id.reply_user_image) as RoundedImageView
val replyUser = FeedUtils.dbHelper!!.getUserProfile(reply.userId)
val replyUser = fragment.dbHelper.getUserProfile(reply.userId)
if (replyUser != null) {
FeedUtils.iconLoader!!.displayImage(replyUser.photoUrl, replyImage)
fragment.iconLoader.displayImage(replyUser.photoUrl, replyImage)
replyImage.setOnClickListener {
val i = Intent(context, Profile::class.java)
i.putExtra(Profile.USER_ID, replyUser.userId)
@ -176,13 +177,13 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
sourceUserImage.visibility = View.VISIBLE
usershareImage.visibility = View.VISIBLE
commentImage.visibility = View.INVISIBLE
val sourceUser = FeedUtils.dbHelper!!.getUserProfile(comment.sourceUserId)
val sourceUser = fragment.dbHelper.getUserProfile(comment.sourceUserId)
if (sourceUser != null) {
FeedUtils.iconLoader!!.displayImage(sourceUser.photoUrl, sourceUserImage)
FeedUtils.iconLoader!!.displayImage(userPhoto, usershareImage)
fragment.iconLoader.displayImage(sourceUser.photoUrl, sourceUserImage)
fragment.iconLoader.displayImage(userPhoto, usershareImage)
}
} else {
FeedUtils.iconLoader!!.displayImage(userPhoto, commentImage)
fragment.iconLoader.displayImage(userPhoto, commentImage)
}
commentImage.setOnClickListener {
val i = Intent(context, Profile::class.java)
@ -200,7 +201,7 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
// for actual comments, also populate the upper icon bar
if (!comment.isPseudo) {
val image = ViewUtils.createSharebarImage(context, commentUser.photoUrl, commentUser.userId)
val image = ViewUtils.createSharebarImage(context, commentUser.photoUrl, commentUser.userId, iconLoader)
topCommentViews!!.add(image)
commentingUserIds.add(comment.userId)
}
@ -216,12 +217,12 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
// now that we have all shares from the comments table and story object, populate the shares row
for (userId in sharingUserIds) {
val user = FeedUtils.dbHelper!!.getUserProfile(userId)
val user = fragment.dbHelper.getUserProfile(userId)
if (user == null) {
Log.w(this.javaClass.name, "cannot display share from missing user ID: $userId")
continue
}
val image = ViewUtils.createSharebarImage(context, user.photoUrl, user.userId)
val image = ViewUtils.createSharebarImage(context, user.photoUrl, user.userId, iconLoader)
topShareViews!!.add(image)
}
}
@ -344,5 +345,6 @@ class SetupCommentSectionTask(private val fragment: ReadingItemFragment, view: V
this.story = story
viewHolder = WeakReference(view)
user = PrefsUtils.getUserDetails(context)
this.iconLoader = iconLoader
}
}

View file

@ -13,6 +13,7 @@ import android.view.View;
import android.widget.EditText;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.domain.Comment;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
@ -20,8 +21,19 @@ import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class ShareDialogFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
private static final String STORY = "story";
private static final String SOURCE_USER_ID = "sourceUserId";
private Story story;
@ -55,7 +67,7 @@ public class ShareDialogFragment extends DialogFragment {
}
if (hasBeenShared) {
previousComment = FeedUtils.dbHelper.getComment(story.id, user.id);
previousComment = dbHelper.getComment(story.id, user.id);
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@ -80,7 +92,7 @@ public class ShareDialogFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String shareComment = commentEditText.getText().toString();
FeedUtils.shareStory(story, shareComment, sourceUserId, activity);
feedUtils.shareStory(story, shareComment, sourceUserId, activity);
ShareDialogFragment.this.dismiss();
}
});
@ -89,7 +101,7 @@ public class ShareDialogFragment extends DialogFragment {
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.unshareStory(story, activity);
feedUtils.unshareStory(story, activity);
ShareDialogFragment.this.dismiss();
}
});

View file

@ -18,6 +18,7 @@ import android.view.View.OnClickListener;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.databinding.DialogTrainstoryBinding;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
@ -25,8 +26,19 @@ import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class StoryIntelTrainerFragment extends DialogFragment {
@Inject
FeedUtils feedUtils;
@Inject
BlurDatabaseHelper dbHelper;
private Story story;
private FeedSet fs;
private Classifier classifier;
@ -50,7 +62,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
super.onCreate(savedInstanceState);
story = (Story) getArguments().getSerializable("story");
fs = (FeedSet) getArguments().getSerializable("feedset");
classifier = FeedUtils.dbHelper.getClassifierForFeed(story.feedId);
classifier = dbHelper.getClassifierForFeed(story.feedId);
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
@ -132,7 +144,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
// the intel identifier is the feed ID
View rowFeed = inflater.inflate(R.layout.include_intel_row, null);
TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label);
labelFeed.setText(FeedUtils.getFeedTitle(story.feedId));
labelFeed.setText(feedUtils.getFeedTitle(story.feedId));
UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, story.feedId);
binding.existingFeedIntelContainer.addView(rowFeed);
@ -152,7 +164,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
if ((newTitleTraining != null) && (!TextUtils.isEmpty(binding.intelTitleSelection.getSelection()))) {
classifier.title.put(binding.intelTitleSelection.getSelection(), newTitleTraining);
}
FeedUtils.updateClassifier(story.feedId, classifier, fs, activity);
feedUtils.updateClassifier(story.feedId, classifier, fs, activity);
StoryIntelTrainerFragment.this.dismiss();
}
});

View file

@ -20,13 +20,19 @@ import com.newsblur.util.FeedSet
import com.newsblur.util.FeedUtils
import com.newsblur.util.TagsAdapter
import com.newsblur.viewModel.StoryUserTagsViewModel
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
@AndroidEntryPoint
class StoryUserTagsFragment : DialogFragment(), TagsAdapter.OnTagClickListener {
@Inject
lateinit var feedUtils: FeedUtils
private lateinit var story: Story
private lateinit var fs: FeedSet
private lateinit var binding: DialogStoryUserTagsBinding
@ -228,6 +234,6 @@ class StoryUserTagsFragment : DialogFragment(), TagsAdapter.OnTagClickListener {
private fun saveTags() {
val savedTagList = getSavedTagsList()
NBSyncService.forceFeedsFolders()
FeedUtils.setStorySaved(story, true, requireContext(), savedTagList)
feedUtils.setStorySaved(story, true, requireContext(), savedTagList)
}
}

View file

@ -3,29 +3,22 @@ package com.newsblur.network;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.FeedResult;
import com.newsblur.domain.Story;
import com.newsblur.domain.ValueMultimap;
import static com.newsblur.network.APIConstants.buildUrl;
import com.newsblur.network.domain.ActivitiesResponse;
@ -43,11 +36,6 @@ import com.newsblur.network.domain.StoryChangesResponse;
import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.network.domain.UnreadCountResponse;
import com.newsblur.network.domain.UnreadStoryHashesResponse;
import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.ClassifierMapTypeAdapter;
import com.newsblur.serialization.DateStringTypeAdapter;
import com.newsblur.serialization.FeedListTypeAdapter;
import com.newsblur.serialization.StoryTypeAdapter;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.NetworkUtils;
@ -65,37 +53,17 @@ import okhttp3.Response;
public class APIManager {
private Context context;
private Gson gson;
private final Context context;
private final Gson gson;
private final OkHttpClient httpClient;
private String customUserAgent;
private OkHttpClient httpClient;
public APIManager(final Context context) {
this.context = context;
public APIManager(final Context context, Gson gson, String customUserAgent, OkHttpClient httpClient) {
APIConstants.setCustomServer(PrefsUtils.getCustomServer(context));
this.gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateStringTypeAdapter())
.registerTypeAdapter(Boolean.class, new BooleanTypeAdapter())
.registerTypeAdapter(boolean.class, new BooleanTypeAdapter())
.registerTypeAdapter(Story.class, new StoryTypeAdapter())
.registerTypeAdapter(new TypeToken<List<Feed>>(){}.getType(), new FeedListTypeAdapter())
.registerTypeAdapter(new TypeToken<Map<String,Classifier>>(){}.getType(), new ClassifierMapTypeAdapter())
.create();
String appVersion = context.getSharedPreferences(PrefConstants.PREFERENCES, 0).getString(AppConstants.LAST_APP_VERSION, "unknown_version");
this.customUserAgent = "NewsBlur Android app" +
" (" + Build.MANUFACTURER + " " +
Build.MODEL + " " +
Build.VERSION.RELEASE + " " +
appVersion + ")";
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(AppConstants.API_CONN_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(AppConstants.API_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.followSslRedirects(true)
.build();
this.context = context;
this.gson = gson;
this.customUserAgent = customUserAgent;
this.httpClient = httpClient;
}
public LoginResponse login(final String username, final String password) {
@ -684,6 +652,10 @@ public class APIManager {
return response.getResponse(gson, NewsBlurResponse.class);
}
public void updateCustomUserAgent(String customUserAgent) {
this.customUserAgent = customUserAgent;
}
/* HTTP METHODS */
private APIResponse get(final String urlString) {
@ -798,5 +770,4 @@ public class APIManager {
com.newsblur.util.Log.w(this.getClass().getName(), "Abandoning API backoff due to interrupt.");
}
}
}

View file

@ -22,6 +22,7 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import androidx.annotation.NonNull;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.di.IconFileCache;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
@ -59,6 +60,10 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* A background service to handle synchronisation with the NB servers.
*
@ -74,6 +79,7 @@ import java.util.concurrent.TimeUnit;
* after sync operations are performed. Activities can then refresh views and
* query this class to see if progress indicators should be active.
*/
@AndroidEntryPoint
public class NBSyncService extends JobService {
private static final Object COMPLETION_CALLBACKS_MUTEX = new Object();
@ -147,8 +153,14 @@ public class NBSyncService extends JobService {
ImagePrefetchService imagePrefetchService;
private boolean forceHalted = false;
@Inject
APIManager apiManager;
@Inject
BlurDatabaseHelper dbHelper;
@IconFileCache
@Inject
FileCache iconCache;
/** The time of the last hard API failure we encountered. Used to implement back-off so that the sync
@ -170,10 +182,7 @@ public class NBSyncService extends JobService {
* parts of construction in onCreate, but save them for when we are in our own thread.
*/
private void finishConstruction() {
if ((apiManager == null) || (dbHelper == null)) {
apiManager = new APIManager(this);
dbHelper = new BlurDatabaseHelper(this);
iconCache = FileCache.asIconCache(this);
if (cleanupService == null || imagePrefetchService == null) {
cleanupService = new CleanupService(this);
starredService = new StarredService(this);
originalTextService = new OriginalTextService(this);
@ -356,7 +365,11 @@ public class NBSyncService extends JobService {
// v61+ is widely deployed
FileCache.cleanUpOldCache1(this);
FileCache.cleanUpOldCache2(this);
PrefsUtils.updateVersion(this);
String appVersion = PrefsUtils.getVersion(this);
PrefsUtils.updateVersion(this, appVersion);
// update user agent on api calls with latest app version
String customUserAgent = NetworkUtils.getCustomUserAgent(appVersion);
apiManager.updateCustomUserAgent(customUserAgent);
}
boolean autoVac = PrefsUtils.isTimeToVacuum(this);
@ -516,7 +529,7 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.w(this.getClass().getName(), "Server ignored or rejected auth cookie.");
if (authFails >= AppConstants.MAX_API_TRIES) {
com.newsblur.util.Log.w(this.getClass().getName(), "too many auth fails, resetting cookie");
PrefsUtils.logout(this);
PrefsUtils.logout(this, dbHelper);
}
DoFeedsFolders = true;
return;
@ -604,7 +617,7 @@ public class NBSyncService extends JobService {
// saved searches table
List<ContentValues> savedSearchesValues = new ArrayList<>();
for (SavedSearch savedSearch : feedResponse.savedSearches) {
savedSearchesValues.add(savedSearch.getValues());
savedSearchesValues.add(savedSearch.getValues(dbHelper));
}
// the API vends the starred total as a different element, roll it into
// the starred counts table using a special tag
@ -951,7 +964,7 @@ public class NBSyncService extends JobService {
Cursor cFocus = dbHelper.getNotifyFocusStoriesCursor();
Cursor cUnread = dbHelper.getNotifyUnreadStoriesCursor();
NotificationUtils.notifyStories(cFocus, cUnread, this, iconCache);
NotificationUtils.notifyStories(this, cFocus, cUnread, iconCache, dbHelper);
closeQuietly(cFocus);
closeQuietly(cUnread);
}
@ -1222,10 +1235,6 @@ public class NBSyncService extends JobService {
Thread.currentThread().interrupt();
}
}
if (dbHelper != null) {
dbHelper.close();
dbHelper = null;
}
com.newsblur.util.Log.d(this, "onDestroy done");
} catch (Exception ex) {
com.newsblur.util.Log.e(this, "unclean shutdown", ex);

View file

@ -21,6 +21,10 @@ import com.newsblur.util.Log
import com.newsblur.util.NBScope
import com.newsblur.util.PrefsUtils
import com.newsblur.util.executeAsyncTask
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@ -79,6 +83,13 @@ interface SubscriptionsListener {
fun onBillingConnectionError(message: String? = null) {}
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface SubscriptionManagerEntryPoint {
fun apiManager(): APIManager
}
class SubscriptionManagerImpl(
private val context: Context,
private val scope: CoroutineScope = NBScope,
@ -199,10 +210,11 @@ class SubscriptionManagerImpl(
override fun saveReceipt(purchase: Purchase) {
Log.d(this, "saveReceipt: ${purchase.orderId}")
val apiManager = APIManager(context)
val hiltEntryPoint = EntryPointAccessors
.fromApplication(context.applicationContext, SubscriptionManagerEntryPoint::class.java)
scope.executeAsyncTask(
doInBackground = {
apiManager.saveReceipt(purchase.orderId, purchase.skus.first())
hiltEntryPoint.apiManager().saveReceipt(purchase.orderId, purchase.skus.first())
},
onPostExecute = {
if (!it.isError) {

View file

@ -4,7 +4,6 @@ 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
@ -13,27 +12,16 @@ import com.newsblur.fragment.ReadingActionConfirmationFragment
import com.newsblur.network.APIConstants
import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncService
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 com.newsblur.util.UIUtils.syncUpdateStatus
import java.util.*
object FeedUtils {
// these are app-level singletons stored here for convenience. however, they
// cannot be created lazily or via static init, they have to be created when
// the main app context is created and it offers a reference
@JvmField
var dbHelper: BlurDatabaseHelper? = null
@JvmField
var iconLoader: ImageLoader? = null
@JvmField
var thumbnailLoader: ImageLoader? = null
var storyImageCache: FileCache? = null
class FeedUtils(
private val dbHelper: BlurDatabaseHelper,
private val apiManager: APIManager,
) {
// this is gross, but the feedset can't hold a folder title
// without being mistaken for a folder feed.
@ -42,37 +30,6 @@ object FeedUtils {
@JvmField
var currentFolderName: String? = null
@JvmStatic
fun offerInitContext(context: Context) {
if (dbHelper == null) {
dbHelper = BlurDatabaseHelper(context.applicationContext)
}
if (iconLoader == null) {
iconLoader = ImageLoader.asIconLoader(context.applicationContext)
}
if (storyImageCache == null) {
storyImageCache = FileCache.asStoryImageCache(context.applicationContext)
}
if (thumbnailLoader == null) {
thumbnailLoader = ImageLoader.asThumbnailLoader(context.applicationContext, storyImageCache)
}
}
@JvmStatic
fun triggerSync(c: Context) {
// NB: when our minSDKversion hits 28, it could be possible to start the service via the JobScheduler
// with the setImportantWhileForeground() flag via an enqueue() and get rid of all legacy startService
// code paths
val i = Intent(c, NBSyncService::class.java)
c.startService(i)
}
@JvmStatic
fun dropAndRecreateTables() {
dbHelper!!.dropAndRecreateTables()
}
@JvmStatic
fun prepareReadingSession(fs: FeedSet?, resetFirst: Boolean) {
NBScope.executeAsyncTask(
doInBackground = {
@ -94,7 +51,6 @@ object FeedUtils {
setStorySaved(storyHash, saved, context, userTags)
}
@JvmStatic
fun setStorySaved(story: Story, saved: Boolean, context: Context, userTags: List<String?>?) {
setStorySaved(story.storyHash, saved, context, userTags)
}
@ -105,29 +61,27 @@ object FeedUtils {
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_STORY)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
triggerSync(context)
}
)
}
@JvmStatic
fun deleteSavedSearch(feedId: String?, query: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
APIManager(context).deleteSearch(feedId, query)
apiManager.deleteSearch(feedId, query)
},
onPostExecute = { newsBlurResponse ->
if (!newsBlurResponse.isError) {
dbHelper!!.deleteSavedSearch(feedId, query)
dbHelper.deleteSavedSearch(feedId, query)
syncUpdateStatus(context, UPDATE_METADATA)
}
}
)
}
@JvmStatic
fun saveSearch(feedId: String?, query: String?, context: Context, apiManager: APIManager) {
fun saveSearch(feedId: String?, query: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.saveSearch(feedId, query)
@ -141,36 +95,33 @@ object FeedUtils {
)
}
@JvmStatic
fun deleteFeed(feedId: String?, folderName: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
APIManager(context).deleteFeed(feedId, folderName)
apiManager.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)
dbHelper.deleteFeed(feedId)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@JvmStatic
fun deleteSocialFeed(userId: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
APIManager(context).unfollowUser(userId)
apiManager.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)
dbHelper.deleteSocialFeed(userId)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@JvmStatic
fun deleteFolder(folderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
fun deleteFolder(folderName: String?, inFolder: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.deleteFolder(folderName, inFolder)
@ -184,15 +135,7 @@ object FeedUtils {
)
}
@JvmStatic
fun syncOfflineStories(context: Context) {
dbHelper!!.deleteStories()
NBSyncService.forceFeedsFolders()
triggerSync(context)
}
@JvmStatic
fun renameFolder(folderName: String?, newFolderName: String?, inFolder: String?, context: Context, apiManager: APIManager) {
fun renameFolder(folderName: String?, newFolderName: String?, inFolder: String?, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
apiManager.renameFolder(folderName, newFolderName, inFolder)
@ -206,7 +149,6 @@ object FeedUtils {
)
}
@JvmStatic
fun markStoryUnread(story: Story, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
@ -215,7 +157,6 @@ object FeedUtils {
)
}
@JvmStatic
fun markStoryAsRead(story: Story, context: Context) {
NBScope.executeAsyncTask(
doInBackground = {
@ -228,7 +169,7 @@ object FeedUtils {
try {
// this shouldn't throw errors, but crash logs suggest something is racing it for DB resources.
// capture logs in hopes of finding the correlated action
dbHelper!!.touchStory(story.storyHash)
dbHelper.touchStory(story.storyHash)
} catch (e: Exception) {
Log.e(FeedUtils::class.java.name, "error touching story state in DB", e)
}
@ -238,10 +179,10 @@ object FeedUtils {
// tell the sync service we need to mark read
val ra = if (read) ReadingAction.markStoryRead(story.storyHash) else ReadingAction.markStoryUnread(story.storyHash)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
// update unread state and unread counts in the local DB
val impactedFeeds = dbHelper!!.setStoryReadState(story, read)
val impactedFeeds = dbHelper.setStoryReadState(story, read)
syncUpdateStatus(context, UPDATE_STORY)
NBSyncService.addRecountCandidates(impactedFeeds)
@ -256,7 +197,7 @@ object FeedUtils {
*/
fun setStoryReadStateExternal(storyHash: String?, context: Context, read: Boolean) {
val ra = if (read) ReadingAction.markStoryRead(storyHash) else ReadingAction.markStoryUnread(storyHash)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
val feedId = inferFeedId(storyHash)
val impactedFeed = FeedSet.singleFeed(feedId)
@ -268,12 +209,11 @@ object FeedUtils {
/**
* Marks some or all of the stories in a FeedSet as read for an activity, handling confirmation dialogues as necessary.
*/
@JvmStatic
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int, finishAfter: Boolean) {
val ra: ReadingAction = if (fs.isAllNormal && (olderThan != null || newerThan != null)) {
// the mark-all-read API doesn't support range bounding, so we need to pass each and every
// feed ID to the API instead.
val newFeedSet = FeedSet.folder("all", dbHelper!!.allActiveFeeds)
val newFeedSet = FeedSet.folder("all", dbHelper.allActiveFeeds)
ReadingAction.markFeedRead(newFeedSet, olderThan, newerThan)
} else {
if (fs.singleFeed != null) {
@ -286,7 +226,7 @@ object FeedUtils {
}
} else if (fs.isFolder) {
val feedIds = fs.multipleFeeds
val allActiveFeedIds = dbHelper!!.allActiveFeeds
val allActiveFeedIds = dbHelper.allActiveFeeds
val activeFeedIds: MutableSet<String> = HashSet()
activeFeedIds.addAll(feedIds)
activeFeedIds.retainAll(allActiveFeedIds)
@ -328,10 +268,10 @@ object FeedUtils {
fs.folderName
}
fs.isSingleSocial -> {
getSocialFeed(fs.singleSocialFeed.key)?.feedTitle ?: ""
dbHelper.getSocialFeed(fs.singleSocialFeed.key)?.feedTitle ?: ""
}
else -> {
getFeed(fs.singleFeed)?.title ?: ""
dbHelper.getFeed(fs.singleFeed)?.title ?: ""
}
}
val dialog = ReadingActionConfirmationFragment.newInstance(ra, title, optionalOverrideMessage, choicesRid, finishAfter)
@ -339,17 +279,14 @@ object FeedUtils {
}
}
@JvmStatic
fun disableNotifications(context: Context, feed: Feed) {
updateFeedNotifications(context, feed, enable = false, focusOnly = false)
}
@JvmStatic
fun enableUnreadNotifications(context: Context, feed: Feed) {
updateFeedNotifications(context, feed, enable = true, focusOnly = false)
}
@JvmStatic
fun enableFocusNotifications(context: Context, feed: Feed) {
updateFeedNotifications(context, feed, enable = true, focusOnly = true)
}
@ -363,19 +300,18 @@ object FeedUtils {
feed.setNotifyUnread()
}
feed.enableAndroidNotifications(enable)
dbHelper!!.updateFeed(feed)
dbHelper.updateFeed(feed)
val ra = ReadingAction.setNotify(feed.feedId, feed.notificationTypes, feed.notificationFilter)
doAction(ra, context)
}
)
}
@JvmStatic
fun doAction(ra: ReadingAction?, context: Context) {
requireNotNull(ra) { "ReadingAction must not be null" }
NBScope.executeAsyncTask(
doInBackground = {
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
syncUpdateStatus(context, impact)
triggerSync(context)
@ -383,13 +319,11 @@ object FeedUtils {
)
}
@JvmStatic
fun updateClassifier(feedId: String?, classifier: Classifier?, fs: FeedSet?, context: Context) {
val ra = ReadingAction.updateIntel(feedId, classifier, fs)
doAction(ra, context)
}
@JvmStatic
fun sendStoryUrl(story: Story?, context: Context) {
if (story == null) return
val intent = Intent(Intent.ACTION_SEND)
@ -400,7 +334,6 @@ object FeedUtils {
context.startActivity(Intent.createChooser(intent, "Send using"))
}
@JvmStatic
fun sendStoryFull(story: Story?, context: Context) {
if (story == null) return
var body = getStoryText(story.storyHash)
@ -413,32 +346,29 @@ object FeedUtils {
context.startActivity(Intent.createChooser(intent, "Send using"))
}
@JvmStatic
fun shareStory(story: Story, comment: String?, sourceUserIdString: String?, context: Context) {
var sourceUserId = sourceUserIdString
if (story.sourceUserId != null) {
sourceUserId = story.sourceUserId
}
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
}
@JvmStatic
fun renameFeed(context: Context, feedId: String?, newFeedName: String?) {
val ra = ReadingAction.renameFeed(feedId, newFeedName)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
syncUpdateStatus(context, impact)
triggerSync(context)
}
@JvmStatic
fun unshareStory(story: Story, context: Context) {
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
@ -446,7 +376,7 @@ object FeedUtils {
fun likeComment(story: Story, commentUserId: String?, context: Context) {
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
@ -454,45 +384,40 @@ object FeedUtils {
fun unlikeComment(story: Story, commentUserId: String?, context: Context) {
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@JvmStatic
fun replyToComment(storyId: String?, feedId: String?, commentUserId: String?, replyText: String?, context: Context) {
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@JvmStatic
fun updateReply(context: Context, story: Story, commentUserId: String?, replyId: String?, replyText: String?) {
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@JvmStatic
fun deleteReply(context: Context, story: Story, commentUserId: String?, replyId: String?) {
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@JvmStatic
fun moveFeedToFolders(context: Context, feedId: String?, toFolders: Set<String?>, inFolders: Set<String?>?) {
if (toFolders.isEmpty()) return
NBScope.executeAsyncTask(
doInBackground = {
val apiManager = APIManager(context)
apiManager.moveFeedToFolders(feedId, toFolders, inFolders)
},
onPostExecute = {
@ -502,12 +427,10 @@ object FeedUtils {
)
}
@JvmStatic
fun muteFeeds(context: Context, feedIds: Set<String>) {
updateFeedActiveState(context, feedIds, false)
}
@JvmStatic
fun unmuteFeeds(context: Context, feedIds: Set<String>) {
updateFeedActiveState(context, feedIds, true)
}
@ -515,7 +438,7 @@ object FeedUtils {
private fun updateFeedActiveState(context: Context, feedIds: Set<String>, active: Boolean) {
NBScope.executeAsyncTask(
doInBackground = {
val activeFeeds = dbHelper!!.allActiveFeeds
val activeFeeds = dbHelper.allActiveFeeds
for (feedId in feedIds) {
if (active) {
activeFeeds.add(feedId)
@ -530,7 +453,7 @@ object FeedUtils {
ReadingAction.muteFeeds(activeFeeds, feedIds)
}
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_METADATA)
@ -539,73 +462,53 @@ object FeedUtils {
)
}
@JvmStatic
fun instaFetchFeed(context: Context, feedId: String?) {
val ra = ReadingAction.instaFetch(feedId)
dbHelper!!.enqueueAction(ra)
dbHelper.enqueueAction(ra)
ra.doLocal(dbHelper)
syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context)
}
@JvmStatic
fun feedSetFromFolderName(folderName: String): FeedSet =
FeedSet.folder(folderName, getFeedIdsRecursive(folderName))
fun getStoryText(hash: String?): String? = dbHelper.getStoryText(hash)
private fun getFeedIdsRecursive(folderName: String): Set<String> {
val folder = dbHelper!!.getFolder(folderName) ?: return emptySet()
val feedIds: MutableSet<String> = HashSet(folder.feedIds.size)
for (id in folder.feedIds) feedIds.add(id)
for (child in folder.children) feedIds.addAll(getFeedIdsRecursive(child))
return feedIds
}
fun getStoryText(hash: String?): String? = dbHelper!!.getStoryText(hash)
fun getStoryContent(hash: String?): String? = dbHelper!!.getStoryContent(hash)
/**
* Infer the feed ID for a story from the story's hash. Useful for APIs
* that takes a feed ID and story ID and only the story hash is known.
*
* TODO: this has a smell to it. can't all APIs just accept story hashes?
*/
@JvmStatic
fun inferFeedId(storyHash: String?): String? {
val parts = TextUtils.split(storyHash, ":")
return if (parts.size != 2) null else parts[0]
}
fun getStoryContent(hash: String?): String? = dbHelper.getStoryContent(hash)
/**
* Because story objects have to join on the feeds table to get feed metadata, there are times
* where standalone stories are missing this info and it must be re-fetched. This is costly
* and should be avoided where possible.
*/
@JvmStatic
fun getFeedTitle(feedId: String?): String? = getFeed(feedId)?.title
fun getFeedTitle(feedId: String?): String? = dbHelper.getFeed(feedId)?.title
@JvmStatic
fun getFeed(feedId: String?): Feed? = dbHelper!!.getFeed(feedId)
fun getFeed(feedId: String?): Feed? = dbHelper.getFeed(feedId)
fun getSocialFeed(feedId: String?): SocialFeed? = dbHelper!!.getSocialFeed(feedId)
@JvmStatic
fun getStarredFeedByTag(feedId: String?): StarredCount? = dbHelper!!.getStarredFeedByTag(feedId)
@JvmStatic
fun openStatistics(context: Context?, feedId: String) {
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)
}
companion object {
@JvmStatic
fun triggerSync(context: Context) {
// NB: when our minSDKversion hits 28, it could be possible to start the service via the JobScheduler
// with the setImportantWhileForeground() flag via an enqueue() and get rid of all legacy startService
// code paths
val i = Intent(context, NBSyncService::class.java)
context.startService(i)
}
/**
* Infer the feed ID for a story from the story's hash. Useful for APIs
* that takes a feed ID and story ID and only the story hash is known.
*
* TODO: this has a smell to it. can't all APIs just accept story hashes?
*/
@JvmStatic
fun inferFeedId(storyHash: String?): String? {
val parts = TextUtils.split(storyHash, ":")
return if (parts.size != 2) null else parts[0]
}
}
}
}

View file

@ -19,6 +19,8 @@ import android.widget.RemoteViews;
import com.newsblur.R;
import com.newsblur.network.APIConstants;
import dagger.hilt.android.internal.managers.FragmentComponentManager;
public class ImageLoader {
private final MemoryCache memoryCache;
@ -194,7 +196,8 @@ public class ImageLoader {
private void setViewImage(Bitmap bitmap, PhotoToLoad photoToLoad) {
BitmapDisplayer bitmapDisplayer = new BitmapDisplayer(bitmap, photoToLoad);
Activity a = (Activity) photoToLoad.imageView.getContext();
FragmentComponentManager.findActivity(photoToLoad.imageView.getContext());
Activity a = (Activity) FragmentComponentManager.findActivity(photoToLoad.imageView.getContext());
a.runOnUiThread(bitmapDisplayer);
}

View file

@ -3,6 +3,7 @@ package com.newsblur.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import java.io.File;
import java.io.IOException;
@ -53,6 +54,7 @@ public class NetworkUtils {
}
}
} catch (Throwable t) {
Log.d("NetworkUtils.loadURL", t.getMessage());
// a huge number of things could go wrong fetching and storing an image. don't spam logs with them
}
return bytesRead;
@ -67,4 +69,16 @@ public class NetworkUtils {
}
}
public static String getCustomUserAgent(String appVersion) {
return "NewsBlur Android app (" +
Build.MANUFACTURER +
" " +
Build.MODEL +
" " +
Build.VERSION.RELEASE +
" " +
appVersion +
")";
}
}

View file

@ -16,6 +16,7 @@ import androidx.core.app.NotificationManagerCompat;
import com.newsblur.R;
import com.newsblur.activity.FeedReading;
import com.newsblur.activity.Reading;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Story;
@ -30,8 +31,7 @@ public class NotificationUtils {
* @param storiesFocus a cursor of unread, focus stories to notify, ordered newest to oldest
* @param storiesUnread a cursor of unread, neutral stories to notify, ordered newest to oldest
*/
public static synchronized void notifyStories(Cursor storiesFocus, Cursor storiesUnread, Context context, FileCache iconCache) {
FeedUtils.offerInitContext(context);
public static synchronized void notifyStories(Context context, Cursor storiesFocus, Cursor storiesUnread, FileCache iconCache, BlurDatabaseHelper dbHelper) {
NotificationManagerCompat nm = NotificationManagerCompat.from(context);
int count = 0;
@ -41,12 +41,12 @@ public class NotificationUtils {
nm.cancel(story.hashCode());
continue;
}
if (FeedUtils.dbHelper.isStoryDismissed(story.storyHash)) {
if (dbHelper.isStoryDismissed(story.storyHash)) {
nm.cancel(story.hashCode());
continue;
}
if (StoryUtils.hasOldTimestamp(story.timestamp)) {
FeedUtils.dbHelper.putStoryDismissed(story.storyHash);
dbHelper.putStoryDismissed(story.storyHash);
nm.cancel(story.hashCode());
continue;
}
@ -55,7 +55,7 @@ public class NotificationUtils {
nm.notify(story.hashCode(), n);
} else {
nm.cancel(story.hashCode());
FeedUtils.dbHelper.putStoryDismissed(story.storyHash);
dbHelper.putStoryDismissed(story.storyHash);
}
count++;
}
@ -65,12 +65,12 @@ public class NotificationUtils {
nm.cancel(story.hashCode());
continue;
}
if (FeedUtils.dbHelper.isStoryDismissed(story.storyHash)) {
if (dbHelper.isStoryDismissed(story.storyHash)) {
nm.cancel(story.hashCode());
continue;
}
if (StoryUtils.hasOldTimestamp(story.timestamp)) {
FeedUtils.dbHelper.putStoryDismissed(story.storyHash);
dbHelper.putStoryDismissed(story.storyHash);
nm.cancel(story.hashCode());
continue;
}
@ -79,7 +79,7 @@ public class NotificationUtils {
nm.notify(story.hashCode(), n);
} else {
nm.cancel(story.hashCode());
FeedUtils.dbHelper.putStoryDismissed(story.storyHash);
dbHelper.putStoryDismissed(story.storyHash);
}
count++;
}

View file

@ -4,15 +4,21 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.newsblur.activity.Reading
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class NotifyDismissReceiver : BroadcastReceiver() {
@Inject
lateinit var dbHelper: BlurDatabaseHelper
override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
NBScope.executeAsyncTask(
doInBackground = {
FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash)
dbHelper.putStoryDismissed(storyHash)
}
)
}

View file

@ -4,17 +4,26 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.newsblur.activity.Reading
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class NotifyMarkreadReceiver : BroadcastReceiver() {
@Inject
lateinit var dbHelper: BlurDatabaseHelper
@Inject
lateinit var feedUtils: FeedUtils
override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
NotificationUtils.cancel(c, storyHash.hashCode())
NBScope.executeAsyncTask(
doInBackground = {
FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash)
FeedUtils.setStoryReadStateExternal(storyHash, c, true)
dbHelper.putStoryDismissed(storyHash)
feedUtils.setStoryReadStateExternal(storyHash, c, true)
}
)
}

View file

@ -4,17 +4,26 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.newsblur.activity.Reading
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class NotifySaveReceiver : BroadcastReceiver() {
@Inject
lateinit var dbHelper: BlurDatabaseHelper
@Inject
lateinit var feedUtils: FeedUtils
override fun onReceive(c: Context, i: Intent) {
val storyHash = i.getStringExtra(Reading.EXTRA_STORY_HASH)
NotificationUtils.cancel(c, storyHash.hashCode())
NBScope.executeAsyncTask(
doInBackground = {
FeedUtils.offerInitContext(c)
FeedUtils.dbHelper!!.putStoryDismissed(storyHash)
FeedUtils.setStorySaved(storyHash, true, c)
dbHelper.putStoryDismissed(storyHash)
feedUtils.setStorySaved(storyHash, true, c)
}
)
}

View file

@ -28,6 +28,7 @@ import android.util.Log;
import com.newsblur.R;
import com.newsblur.activity.Login;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.APIConstants;
import com.newsblur.service.SubscriptionSyncService;
@ -87,10 +88,10 @@ public class PrefsUtils {
}
public static void updateVersion(Context context) {
public static void updateVersion(Context context, String appVersion) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
// store the current version
prefs.edit().putString(AppConstants.LAST_APP_VERSION, getVersion(context)).commit();
prefs.edit().putString(AppConstants.LAST_APP_VERSION, appVersion).commit();
// also make sure we auto-trigger an update, since all data are now gone
prefs.edit().putLong(AppConstants.LAST_SYNC_TIME, 0L).commit();
}
@ -104,18 +105,18 @@ public class PrefsUtils {
}
}
public static String createFeedbackLink(Context context) {
public static String createFeedbackLink(Context context, BlurDatabaseHelper dbHelper) {
StringBuilder s = new StringBuilder(AppConstants.FEEDBACK_URL);
s.append("<give us some feedback!>%0A%0A%0A");
String info = getDebugInfo(context);
String info = getDebugInfo(context, dbHelper);
s.append(info.replace("\n", "%0A"));
return s.toString();
}
public static void sendLogEmail(Context context) {
public static void sendLogEmail(Context context, BlurDatabaseHelper dbHelper) {
File f = com.newsblur.util.Log.getLogfile();
if (f == null) return;
String debugInfo = "Tell us a bit about your problem:\n\n\n\n" + getDebugInfo(context);
String debugInfo = "Tell us a bit about your problem:\n\n\n\n" + getDebugInfo(context, dbHelper);
android.net.Uri localPath = FileProvider.getUriForFile(context, "com.newsblur.fileprovider", f);
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("*/*");
@ -128,7 +129,7 @@ public class PrefsUtils {
}
}
private static String getDebugInfo(Context context) {
private static String getDebugInfo(Context context, BlurDatabaseHelper dbHelper) {
StringBuilder s = new StringBuilder();
s.append("app version: ").append(getVersion(context));
s.append("\n");
@ -136,7 +137,7 @@ public class PrefsUtils {
s.append("\n");
s.append("device: ").append(Build.MANUFACTURER).append(" ").append(Build.MODEL).append(" (").append(Build.BOARD).append(")");
s.append("\n");
s.append("sqlite version: ").append(FeedUtils.dbHelper.getEngineVersion());
s.append("sqlite version: ").append(dbHelper.getEngineVersion());
s.append("\n");
s.append("username: ").append(getUserDetails(context).username);
s.append("\n");
@ -166,7 +167,7 @@ public class PrefsUtils {
return s.toString();
}
public static void logout(Context context) {
public static void logout(Context context, BlurDatabaseHelper dbHelper) {
NBSyncService.softInterrupt();
NBSyncService.clearState();
@ -179,7 +180,7 @@ public class PrefsUtils {
context.getSharedPreferences(PrefConstants.PREFERENCES, 0).edit().clear().commit();
// wipe the local DB
FeedUtils.dropAndRecreateTables();
dbHelper.dropAndRecreateTables();
// disable widget
WidgetUtils.disableWidgetUpdate(context);
@ -193,7 +194,7 @@ public class PrefsUtils {
context.startActivity(i);
}
public static void clearPrefsAndDbForLoginAs(Context context) {
public static void clearPrefsAndDbForLoginAs(Context context, BlurDatabaseHelper dbHelper) {
NBSyncService.softInterrupt();
NBSyncService.clearState();
@ -211,7 +212,7 @@ public class PrefsUtils {
editor.commit();
// wipe the local DB
FeedUtils.dropAndRecreateTables();
dbHelper.dropAndRecreateTables();
}
/**

View file

@ -31,7 +31,6 @@ import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -45,10 +44,12 @@ import androidx.core.content.ContextCompat;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.color.MaterialColors;
import com.newsblur.NbApplication;
import com.newsblur.R;
import com.newsblur.activity.*;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.service.NBSyncReceiver;
public class UIUtils {
@ -178,9 +179,9 @@ public class UIUtils {
* Set up our customised ActionBar view that features the specified icon and title, sized
* away from system standard to meet the NewsBlur visual style.
*/
public static void setupToolbar(AppCompatActivity activity, String imageUrl, String title, boolean showHomeEnabled) {
public static void setupToolbar(AppCompatActivity activity, String imageUrl, String title, ImageLoader iconLoader, boolean showHomeEnabled) {
ImageView iconView = setupCustomToolbar(activity, title, showHomeEnabled);
FeedUtils.iconLoader.displayImage(imageUrl, iconView);
iconLoader.displayImage(imageUrl, iconView);
}
public static void setupToolbar(AppCompatActivity activity, int imageId, String title, boolean showHomeEnabled) {
@ -590,4 +591,12 @@ public class UIUtils {
return CustomTabsIntent.COLOR_SCHEME_SYSTEM;
}
}
public static void syncUpdateStatus(Context context, int updateType) {
if (NbApplication.isAppForeground()) {
Intent intent = new Intent(NBSyncReceiver.NB_SYNC_ACTION);
intent.putExtra(NBSyncReceiver.NB_SYNC_UPDATE_TYPE, updateType);
context.sendBroadcast(intent);
}
}
}

View file

@ -15,7 +15,7 @@ public class ViewUtils {
private ViewUtils() {} // util class - no instances
public static ImageView createSharebarImage(final Context context, final String photoUrl, final String userId) {
public static ImageView createSharebarImage(final Context context, final String photoUrl, final String userId, ImageLoader iconLoader) {
RoundedImageView image = new RoundedImageView(context);
int imageLength = UIUtils.dp2px(context, 15);
image.setMaxHeight(imageLength);
@ -30,7 +30,7 @@ public class ViewUtils {
image.setMaxWidth(imageLength);
image.setLayoutParams(imageParameters);
FeedUtils.iconLoader.displayImage(photoUrl, image);
iconLoader.displayImage(photoUrl, image);
image.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {

View file

@ -9,6 +9,7 @@ import android.text.TextUtils;
import com.newsblur.R;
import com.newsblur.domain.ActivityDetails;
import com.newsblur.domain.UserDetails;
import com.newsblur.util.ImageLoader;
/**
* Created by mark on 17/06/15.
@ -17,8 +18,8 @@ public class ActivitiesAdapter extends ActivityDetailsAdapter {
private final String startedFollowing, repliedTo, favorited, subscribedTo, saved, signup, commentsOn, sharedStory, you;
public ActivitiesAdapter(final Context context, UserDetails user) {
super(context, user);
public ActivitiesAdapter(final Context context, UserDetails user, ImageLoader iconLoader) {
super(context, user, iconLoader);
Resources resources = context.getResources();
startedFollowing = resources.getString(R.string.profile_started_following);

View file

@ -15,20 +15,22 @@ import com.newsblur.domain.ActivityDetails;
import com.newsblur.domain.ActivityDetails.Category;
import com.newsblur.network.APIConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.UIUtils;
public abstract class ActivityDetailsAdapter extends ArrayAdapter<ActivityDetails> {
private LayoutInflater inflater;
private final ImageLoader iconLoader;
private final LayoutInflater inflater;
protected final String ago;
protected ForegroundColorSpan linkColor, contentColor, quoteColor;
protected UserDetails currentUserDetails;
protected final UserDetails currentUserDetails;
protected final boolean userIsYou;
public ActivityDetailsAdapter(final Context context, UserDetails user) {
public ActivityDetailsAdapter(final Context context, UserDetails user, ImageLoader iconLoader) {
super(context, R.layout.row_activity); // final argument seems unused since we override getView()
inflater = LayoutInflater.from(context);
this.iconLoader = iconLoader;
currentUserDetails = user;
Resources resources = context.getResources();
@ -57,13 +59,13 @@ public abstract class ActivityDetailsAdapter extends ArrayAdapter<ActivityDetail
activityTime.setText(activity.timeSince.toUpperCase() + " " + ago);
if (activity.category == Category.FEED_SUBSCRIPTION) {
FeedUtils.iconLoader.displayImage(APIConstants.S3_URL_FEED_ICONS + activity.feedId + ".png", imageView);
iconLoader.displayImage(APIConstants.S3_URL_FEED_ICONS + activity.feedId + ".png", imageView);
} else if (activity.category == Category.SHARED_STORY) {
FeedUtils.iconLoader.displayImage(currentUserDetails.photoUrl, imageView);
iconLoader.displayImage(currentUserDetails.photoUrl, imageView);
} else if (activity.category == Category.STAR) {
imageView.setImageResource(R.drawable.ic_clock);
} else if (activity.user != null) {
FeedUtils.iconLoader.displayImage(activity.user.photoUrl, imageView);
iconLoader.displayImage(activity.user.photoUrl, imageView);
} else {
imageView.setImageResource(R.drawable.logo);
}

View file

@ -17,7 +17,7 @@ import com.newsblur.util.UIUtils;
* children as special case that should all be squares of the same size, for forming
* icon grids. Many iterations ago inspired by the code referenced below.
*
* @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
* @see https://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
*/
public class FlowLayout extends ViewGroup {

View file

@ -9,6 +9,7 @@ import android.text.TextUtils;
import com.newsblur.R;
import com.newsblur.domain.ActivityDetails;
import com.newsblur.domain.UserDetails;
import com.newsblur.util.ImageLoader;
/**
* Created by mark on 17/06/15.
@ -17,8 +18,8 @@ public class InteractionsAdapter extends ActivityDetailsAdapter {
private final String nowFollowingYou, repliedToYour, comment, reply, favoritedComments, reshared, your, you;
public InteractionsAdapter(final Context context, UserDetails user) {
super(context, user);
public InteractionsAdapter(final Context context, UserDetails user, ImageLoader iconLoader) {
super(context, user, iconLoader);
Resources resources = context.getResources();
nowFollowingYou = resources.getString(R.string.profile_now_following);

View file

@ -6,11 +6,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.newsblur.util.FeedUtils
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class AllFoldersViewModel : ViewModel() {
@HiltViewModel
class AllFoldersViewModel
@Inject constructor(private val dbHelper: BlurDatabaseHelper): ViewModel() {
private val cancellationSignal = CancellationSignal()
@ -37,27 +41,27 @@ class AllFoldersViewModel : ViewModel() {
fun getData() {
viewModelScope.launch(Dispatchers.IO) {
launch {
FeedUtils.dbHelper!!.getSocialFeedsCursor(cancellationSignal).let {
dbHelper.getSocialFeedsCursor(cancellationSignal).let {
_socialFeeds.postValue(it)
}
}
launch {
FeedUtils.dbHelper!!.getFoldersCursor(cancellationSignal).let {
dbHelper.getFoldersCursor(cancellationSignal).let {
_folders.postValue(it)
}
}
launch {
FeedUtils.dbHelper!!.getFeedsCursor(cancellationSignal).let {
dbHelper.getFeedsCursor(cancellationSignal).let {
_feeds.postValue(it)
}
}
launch {
FeedUtils.dbHelper!!.getSavedStoryCountsCursor(cancellationSignal).let {
dbHelper.getSavedStoryCountsCursor(cancellationSignal).let {
_savedStoryCounts.postValue(it)
}
}
launch {
FeedUtils.dbHelper!!.getSavedSearchCursor(cancellationSignal).let {
dbHelper.getSavedSearchCursor(cancellationSignal).let {
_savedSearch.postValue(it)
}
}

View file

@ -6,11 +6,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.newsblur.util.FeedUtils
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class FeedFolderViewModel : ViewModel() {
@HiltViewModel
class FeedFolderViewModel
@Inject constructor(private val dbHelper: BlurDatabaseHelper) : ViewModel() {
private val cancellationSignal = CancellationSignal()
@ -22,12 +26,12 @@ class FeedFolderViewModel : ViewModel() {
fun getData() {
viewModelScope.launch(Dispatchers.IO) {
launch {
FeedUtils.dbHelper!!.getFoldersCursor(cancellationSignal).let {
dbHelper.getFoldersCursor(cancellationSignal).let {
_folders.postValue(it)
}
}
launch {
FeedUtils.dbHelper!!.getFeedsCursor(cancellationSignal).let {
dbHelper.getFeedsCursor(cancellationSignal).let {
_feeds.postValue(it)
}
}
@ -36,7 +40,7 @@ class FeedFolderViewModel : ViewModel() {
fun getFeeds() {
viewModelScope.launch(Dispatchers.IO) {
FeedUtils.dbHelper!!.getFeedsCursor(cancellationSignal).let {
dbHelper.getFeedsCursor(cancellationSignal).let {
_feeds.postValue(it)
}
}

View file

@ -6,12 +6,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.util.FeedSet
import com.newsblur.util.FeedUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class StoriesViewModel : ViewModel() {
@HiltViewModel
class StoriesViewModel
@Inject constructor(private val dbHelper: BlurDatabaseHelper): ViewModel() {
private val cancellationSignal = CancellationSignal()
private val _activeStoriesLiveData = MutableLiveData<Cursor>()
@ -19,7 +23,7 @@ class StoriesViewModel : ViewModel() {
fun getActiveStories(fs: FeedSet) {
viewModelScope.launch(Dispatchers.IO) {
FeedUtils.dbHelper!!.getActiveStoriesCursor(fs, cancellationSignal).let {
dbHelper.getActiveStoriesCursor(fs, cancellationSignal).let {
_activeStoriesLiveData.postValue(it)
}
}

View file

@ -6,11 +6,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.newsblur.util.FeedUtils
import com.newsblur.database.BlurDatabaseHelper
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class StoryUserTagsViewModel : ViewModel() {
@HiltViewModel
class StoryUserTagsViewModel
@Inject constructor(private val dbHelper: BlurDatabaseHelper): ViewModel() {
private val cancellationSignal = CancellationSignal()
private val _savedStoryCountsLiveData = MutableLiveData<Cursor>()
@ -18,7 +22,7 @@ class StoryUserTagsViewModel : ViewModel() {
fun getSavedStoryCounts() {
viewModelScope.launch(Dispatchers.IO) {
val cursor = FeedUtils.dbHelper!!.getSavedStoryCountsCursor(cancellationSignal)
val cursor = dbHelper.getSavedStoryCountsCursor(cancellationSignal)
_savedStoryCountsLiveData.postValue(cursor)
}
}

View file

@ -11,27 +11,42 @@ import android.view.View
import android.widget.RemoteViews
import android.widget.RemoteViewsService.RemoteViewsFactory
import com.newsblur.R
import com.newsblur.database.BlurDatabaseHelper
import com.newsblur.di.IconLoader
import com.newsblur.di.ThumbnailLoader
import com.newsblur.domain.Feed
import com.newsblur.domain.Story
import com.newsblur.network.APIManager
import com.newsblur.util.*
import com.newsblur.util.FeedUtils.offerInitContext
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import java.util.*
import kotlin.math.min
class WidgetRemoteViewsFactory internal constructor(context: Context, intent: Intent) : RemoteViewsFactory {
class WidgetRemoteViewsFactory(context: Context, intent: Intent) : RemoteViewsFactory {
private val context: Context
private val apiManager: APIManager
private val dbHelper: BlurDatabaseHelper
private val iconLoader: ImageLoader
private val thumbnailLoader: ImageLoader
private var fs: FeedSet? = null
private val appWidgetId: Int
private var dataCompleted = false
private val storyItems: MutableList<Story> = ArrayList()
private val cancellationSignal = CancellationSignal()
private var apiManager: APIManager? = null
init {
Log.d(TAG, "Constructor")
val hiltEntryPoint = EntryPointAccessors
.fromApplication(context.applicationContext, WidgetRemoteViewsFactoryEntryPoint::class.java)
this.context = context
this.apiManager = hiltEntryPoint.apiManager()
this.dbHelper = hiltEntryPoint.dbHelper()
this.iconLoader = hiltEntryPoint.iconLoader()
this.thumbnailLoader = hiltEntryPoint.thumbnailLoader()
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID)
}
@ -47,19 +62,6 @@ class WidgetRemoteViewsFactory internal constructor(context: Context, intent: In
*/
override fun onCreate() {
Log.d(TAG, "onCreate")
apiManager = APIManager(context)
// widget could be created before app init
// wait for the dbHelper to be ready for use
while (FeedUtils.dbHelper == null) {
try {
Thread.sleep(500)
} catch (e: InterruptedException) {
e.printStackTrace()
}
if (FeedUtils.dbHelper == null) {
offerInitContext(context)
}
}
WidgetUtils.enableWidgetUpdate(context)
}
@ -78,9 +80,9 @@ class WidgetRemoteViewsFactory internal constructor(context: Context, intent: In
rv.setTextViewText(R.id.story_item_date, time)
// image dimensions same as R.layout.view_widget_story_item
FeedUtils.iconLoader!!.displayWidgetImage(story.extern_faviconUrl, R.id.story_item_feedicon, UIUtils.dp2px(context, 19), rv)
iconLoader.displayWidgetImage(story.extern_faviconUrl, R.id.story_item_feedicon, UIUtils.dp2px(context, 19), rv)
if (PrefsUtils.getThumbnailStyle(context) != ThumbnailStyle.OFF && !TextUtils.isEmpty(story.thumbnailUrl)) {
FeedUtils.thumbnailLoader!!.displayWidgetImage(story.thumbnailUrl, R.id.story_item_thumbnail, UIUtils.dp2px(context, 64), rv)
thumbnailLoader.displayWidgetImage(story.thumbnailUrl, R.id.story_item_thumbnail, UIUtils.dp2px(context, 64), rv)
} else {
rv.setViewVisibility(R.id.story_item_thumbnail, View.GONE)
}
@ -141,13 +143,13 @@ class WidgetRemoteViewsFactory internal constructor(context: Context, intent: In
return
}
Log.d(TAG, "onDataSetChanged - fetch stories")
val response = apiManager!!.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
val response = apiManager.getStories(fs, 1, StoryOrder.NEWEST, ReadFilter.ALL)
if (response?.stories == null) {
Log.d(TAG, "Error fetching widget stories")
} else {
Log.d(TAG, "Fetched widget stories")
processStories(response.stories)
FeedUtils.dbHelper!!.insertStories(response, true)
dbHelper.insertStories(response, true)
}
}
}
@ -173,7 +175,7 @@ class WidgetRemoteViewsFactory internal constructor(context: Context, intent: In
val feedMap = HashMap<String, Feed>()
NBScope.executeAsyncTask(
doInBackground = {
FeedUtils.dbHelper!!.getFeedsCursor(cancellationSignal)
dbHelper.getFeedsCursor(cancellationSignal)
},
onPostExecute = {
while (it != null && it.moveToNext()) {
@ -228,4 +230,19 @@ class WidgetRemoteViewsFactory internal constructor(context: Context, intent: In
companion object {
private const val TAG = "WidgetRemoteViewsFactory"
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface WidgetRemoteViewsFactoryEntryPoint {
fun apiManager(): APIManager
fun dbHelper(): BlurDatabaseHelper
@IconLoader
fun iconLoader(): ImageLoader
@ThumbnailLoader
fun thumbnailLoader(): ImageLoader
}
}