mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'dejal' into catalyst
This commit is contained in:
commit
0a5e6fe848
56 changed files with 1766 additions and 659 deletions
|
@ -1028,7 +1028,9 @@ class Profile(models.Model):
|
|||
|
||||
self.setup_premium_history()
|
||||
|
||||
if not self.is_premium:
|
||||
if order_id == "nb.premium.archive.99":
|
||||
self.activate_archive()
|
||||
elif not self.is_premium:
|
||||
self.activate_premium()
|
||||
|
||||
logging.user(self.user, "~FG~BBNew Android premium subscription: $%s~FW" % amount)
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
android:label="@string/settings"/>
|
||||
|
||||
<activity android:name=".activity.ImportExportActivity" />
|
||||
<activity android:name=".activity.NotificationsActivity" />
|
||||
<activity
|
||||
android:name=".activity.WidgetConfig"
|
||||
android:launchMode="singleTask"
|
||||
|
@ -167,6 +168,7 @@
|
|||
<receiver android:name=".util.NotifyDismissReceiver" android:exported="false" />
|
||||
<receiver android:name=".util.NotifySaveReceiver" android:exported="false" />
|
||||
<receiver android:name=".util.NotifyMarkreadReceiver" android:exported="false" />
|
||||
<receiver android:name=".util.NotifyShareReceiver" android:exported="false" />
|
||||
<receiver android:name=".widget.WidgetProvider"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.6.21'
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.43.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +19,6 @@ repositories {
|
|||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
|
@ -44,8 +42,10 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation "com.google.dagger:hilt-android:2.40.5"
|
||||
kapt "com.google.dagger:hilt-compiler:2.40.5"
|
||||
implementation "com.google.dagger:hilt-android:2.43.2"
|
||||
kapt "com.google.dagger:hilt-compiler:2.43.2"
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -54,14 +54,17 @@ android {
|
|||
applicationId "com.newsblur"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 205
|
||||
versionName "12.0.1"
|
||||
versionCode 207
|
||||
versionName "12.1.1"
|
||||
}
|
||||
compileOptions.with {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
android.buildFeatures.viewBinding = true
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
|
@ -70,6 +73,9 @@ android {
|
|||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
test {
|
||||
java.srcDirs = ['test']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Wed Aug 11 15:59:29 EDT 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/gray55" android:state_checked="true" />
|
||||
<item android:color="@color/gray30" android:state_checked="false" />
|
||||
</selector>
|
|
@ -0,0 +1,4 @@
|
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/gray75" android:state_checked="true" />
|
||||
<item android:color="@color/gray90" android:state_checked="false" />
|
||||
</selector>
|
|
@ -0,0 +1,6 @@
|
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/gray96" android:state_checkable="true" android:state_checked="true" android:state_enabled="true" />
|
||||
<item android:color="@color/gray65" android:state_checkable="true" android:state_checked="false" android:state_enabled="true" />
|
||||
<item android:color="@color/gray65" android:state_enabled="true" />
|
||||
<item android:color="@color/gray65" />
|
||||
</selector>
|
|
@ -0,0 +1,6 @@
|
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/gray10" android:state_checkable="true" android:state_checked="true" android:state_enabled="true" />
|
||||
<item android:color="@color/gray30" android:state_checkable="true" android:state_checked="false" android:state_enabled="true" />
|
||||
<item android:color="@color/gray30" android:state_enabled="true" />
|
||||
<item android:color="@color/gray30" />
|
||||
</selector>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gray55" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gray30" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar_newsblur" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view_feeds"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_no_notifications"
|
||||
style="?defaultText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="48dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_feed_notifications"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
107
clients/android/NewsBlur/res/layout/view_notifications_item.xml
Normal file
107
clients/android/NewsBlur/res/layout/view_notifications_item.xml
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
app:shapeAppearanceOverlay="@style/smallRoundImageShapeAppearance" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/text_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="28dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||
android:id="@+id/group_filter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:orientation="horizontal"
|
||||
app:selectionRequired="true"
|
||||
app:singleSelection="true">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_unread"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/state_unread"
|
||||
app:icon="@drawable/ic_indicator_unread"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="19dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_focus"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/state_focus"
|
||||
app:icon="@drawable/ic_indicator_focus"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="16dp"
|
||||
app:iconTint="#FF6BBF7A" />
|
||||
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||
android:id="@+id/group_platform"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_email"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_weight="1"
|
||||
android:checkable="true"
|
||||
android:gravity="center"
|
||||
android:text="@string/notification_email" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_web"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/notification_web" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_ios"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/notification_ios" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_android"
|
||||
style="?toggleButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/notification_android" />
|
||||
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
@ -12,7 +12,8 @@
|
|||
|
||||
<item android:id="@+id/menu_widget"
|
||||
android:title="@string/widget"
|
||||
app:showAsAction="never" />
|
||||
app:showAsAction="never"
|
||||
android:visible="false"/>
|
||||
|
||||
<item android:id="@+id/menu_premium_account"
|
||||
android:title="@string/menu_premium_account"
|
||||
|
@ -22,6 +23,10 @@
|
|||
android:title="@string/import_export"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/menu_notifications"
|
||||
android:title="@string/menu_notifications"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/menu_text_size"
|
||||
android:title="@string/menu_text_size" >
|
||||
<menu>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<attr name="muteicon" format="string" />
|
||||
<attr name="circleProgressIndicator" format="string" />
|
||||
<attr name="delimiter" format="string" />
|
||||
<attr name="toggleButton" format="string" />
|
||||
|
||||
<attr name="flow" format="string" />
|
||||
<attr name="imageViewSize" format="integer" />
|
||||
|
|
|
@ -281,12 +281,14 @@
|
|||
<string name="menu_mute_sites">Mute Sites…</string>
|
||||
<string name="mute_sites">Mute Sites</string>
|
||||
<string name="menu_widget">Widget…</string>
|
||||
<string name="menu_notifications">Notifications…</string>
|
||||
<string name="widget">Widget</string>
|
||||
<string name="title_widget_setup">Tap to setup in NewsBlur</string>
|
||||
<string name="title_no_subscriptions">No active subscriptions detected</string>
|
||||
<string name="title_widget_loading">Loading…</string>
|
||||
|
||||
<string name="import_export_title">Import/Export OPML</string>
|
||||
<string name="notifications_title">Notifications</string>
|
||||
|
||||
<string name="premium_subscribers_folder">Reading by folder is only available to</string>
|
||||
<string name="premium_subscribers_search">Search is only available to</string>
|
||||
|
@ -577,6 +579,8 @@
|
|||
<item>DOWN_NEXT</item>
|
||||
</string-array>
|
||||
<string name="default_volume_key_navigation_value">OFF</string>
|
||||
<string name="settings_load_next_on_mark_read">Open next feed/folder after read</string>
|
||||
<string name="settings_load_next_on_mark_read_summary">Load the next feed/folder after marked as read</string>
|
||||
|
||||
<string name="settings_confirm_mark_all_read">Confirm mark all read on…</string>
|
||||
<string name="none">Neither</string>
|
||||
|
@ -711,4 +715,10 @@
|
|||
<string name="add_feed">Add feed</string>
|
||||
|
||||
<string name="story_author">• %s</string>
|
||||
|
||||
<string name="notification_email">Email</string>
|
||||
<string name="notification_web">Web</string>
|
||||
<string name="notification_ios">iOS</string>
|
||||
<string name="notification_android">Android</string>
|
||||
<string name="no_feed_notifications">No feed notifications</string>
|
||||
</resources>
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
<item name="colorControlNormal">@color/gray55</item>
|
||||
<item name="fontFamily">@font/whitney</item>
|
||||
</style>
|
||||
|
||||
<style name="actionbar.dark" parent="ThemeOverlay.MaterialComponents">
|
||||
<item name="android:background">@color/dark_bar_background</item>
|
||||
<item name="colorControlNormal">@color/gray55</item>
|
||||
<item name="fontFamily">@font/whitney</item>
|
||||
</style>
|
||||
|
||||
<style name="actionbar.black" parent="ThemeOverlay.MaterialComponents">
|
||||
<item name="android:background">@color/black</item>
|
||||
<item name="colorControlNormal">@color/gray55</item>
|
||||
|
@ -35,6 +37,7 @@
|
|||
<style name="delimiter">
|
||||
<item name="android:background">@color/gray85</item>
|
||||
</style>
|
||||
|
||||
<style name="delimiter.dark">
|
||||
<item name="android:background">@color/gray30</item>
|
||||
</style>
|
||||
|
@ -43,12 +46,15 @@
|
|||
<item name="android:paddingTop">2dp</item>
|
||||
<item name="android:paddingBottom">2dp</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFolderBackground" parent="selectorFolderParent">
|
||||
<item name="android:background">@drawable/selector_folder_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFolderBackground.dark" parent="selectorFolderParent">
|
||||
<item name="android:background">@drawable/dark_selector_folder_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFolderBackground.black" parent="selectorFolderParent">
|
||||
<item name="android:background">@drawable/black_selector_folder_background</item>
|
||||
</style>
|
||||
|
@ -56,19 +62,25 @@
|
|||
<style name="selectorFeedAlmostBlueBackground" parent="selectorFeedBackground">
|
||||
<item name="android:background">@drawable/selector_feed_almost_blue_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFeedAlmostRedBackground" parent="selectorFeedBackground">
|
||||
<item name="android:background">@drawable/selector_feed_almost_red_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFeedAlmostGreenBackground" parent="selectorFeedBackground">
|
||||
<item name="android:background">@drawable/selector_feed_almost_green_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFeedAlmostBlueBackground.dark" parent="selectorFeedBackground.dark" />
|
||||
|
||||
<style name="selectorFeedAlmostRedBackground.dark" parent="selectorFeedBackground.dark" />
|
||||
|
||||
<style name="selectorFeedAlmostGreenBackground.dark" parent="selectorFeedBackground.dark" />
|
||||
|
||||
<style name="selectorFeedBackground">
|
||||
<item name="android:background">@drawable/selector_feed_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorFeedBackground.dark">
|
||||
<item name="android:background">@drawable/dark_selector_feed_background</item>
|
||||
</style>
|
||||
|
@ -76,6 +88,7 @@
|
|||
<style name="feedRowNeutCountText">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="feedRowNeutCountText.dark">
|
||||
<item name="android:textColor">@color/black</item>
|
||||
</style>
|
||||
|
@ -83,9 +96,11 @@
|
|||
<style name="actionbarBackground">
|
||||
<item name="android:background">@color/bar_background</item>
|
||||
</style>
|
||||
|
||||
<style name="actionbarBackground.dark">
|
||||
<item name="android:background">@color/dark_bar_background</item>
|
||||
</style>
|
||||
|
||||
<style name="actionbarBackground.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
</style>
|
||||
|
@ -93,6 +108,7 @@
|
|||
<style name="listBackground">
|
||||
<item name="android:background">@drawable/list_background</item>
|
||||
</style>
|
||||
|
||||
<style name="listBackground.dark">
|
||||
<item name="android:background">@drawable/dark_list_background</item>
|
||||
</style>
|
||||
|
@ -100,9 +116,11 @@
|
|||
<style name="layoutBackground">
|
||||
<item name="android:background">@color/item_background</item>
|
||||
</style>
|
||||
|
||||
<style name="layoutBackground.dark">
|
||||
<item name="android:background">@color/dark_item_background</item>
|
||||
</style>
|
||||
|
||||
<style name="layoutBackground.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
</style>
|
||||
|
@ -110,9 +128,11 @@
|
|||
<style name="layoutRoundedBackground">
|
||||
<item name="android:background">@drawable/shape_rounded_corners_6dp_light</item>
|
||||
</style>
|
||||
|
||||
<style name="layoutRoundedBackground.dark">
|
||||
<item name="android:background">@drawable/shape_rounded_corners_6dp_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="layoutRoundedBackground.black">
|
||||
<item name="android:background">@drawable/shape_rounded_corners_6dp_black</item>
|
||||
</style>
|
||||
|
@ -120,9 +140,11 @@
|
|||
<style name="readingBackground">
|
||||
<item name="android:background">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="readingBackground.dark">
|
||||
<item name="android:background">@color/dark_item_background</item>
|
||||
</style>
|
||||
|
||||
<style name="readingBackground.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
</style>
|
||||
|
@ -131,6 +153,7 @@
|
|||
<item name="android:textColorLink">@color/linkblue</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
</style>
|
||||
|
||||
<style name="defaultText.dark">
|
||||
<item name="android:textColorLink">@color/dark_linkblue</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
|
@ -140,6 +163,7 @@
|
|||
<item name="android:textColorLink">@color/linkblue</item>
|
||||
<item name="android:textColor">@color/linkblue</item>
|
||||
</style>
|
||||
|
||||
<style name="linkText.dark">
|
||||
<item name="android:textColorLink">@color/dark_linkblue</item>
|
||||
<item name="android:textColor">@color/dark_linkblue</item>
|
||||
|
@ -148,6 +172,7 @@
|
|||
<style name="storySnippetText">
|
||||
<item name="android:textColor">@color/story_content_text</item>
|
||||
</style>
|
||||
|
||||
<style name="storySnippetText.dark">
|
||||
<item name="android:textColor">@color/dark_story_content_text</item>
|
||||
</style>
|
||||
|
@ -155,6 +180,7 @@
|
|||
<style name="storyFeedTitleText">
|
||||
<item name="android:textColor">@color/story_feed_title_text</item>
|
||||
</style>
|
||||
|
||||
<style name="storyFeedTitleText.dark">
|
||||
<item name="android:textColor">@color/dark_story_feed_title_text</item>
|
||||
</style>
|
||||
|
@ -162,9 +188,11 @@
|
|||
<style name="selectorStoryBackground">
|
||||
<item name="android:background">@drawable/selector_story_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorStoryBackground.dark">
|
||||
<item name="android:background">@drawable/dark_selector_story_background</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorStoryBackground.black">
|
||||
<item name="android:background">@drawable/black_selector_story_background</item>
|
||||
</style>
|
||||
|
@ -172,9 +200,11 @@
|
|||
<style name="rowItemHeaderBackground">
|
||||
<item name="android:background">@drawable/row_item_header_background</item>
|
||||
</style>
|
||||
|
||||
<style name="rowItemHeaderBackground.dark">
|
||||
<item name="android:background">@drawable/dark_row_item_header_background</item>
|
||||
</style>
|
||||
|
||||
<style name="rowItemHeaderBackground.black">
|
||||
<item name="android:background">@drawable/black_row_item_header_background</item>
|
||||
</style>
|
||||
|
@ -182,6 +212,7 @@
|
|||
<style name="readingItemMetadata">
|
||||
<item name="android:textColor">@color/half_darkgray</item>
|
||||
</style>
|
||||
|
||||
<style name="readingItemMetadata.dark">
|
||||
<item name="android:textColor">@color/half_white</item>
|
||||
</style>
|
||||
|
@ -191,6 +222,7 @@
|
|||
<item name="chipBackgroundColor">@color/tag_gray</item>
|
||||
<item name="android:fontFamily">@font/whitney</item>
|
||||
</style>
|
||||
|
||||
<style name="chip.dark">
|
||||
<item name="android:textColor">@color/tag_gray</item>
|
||||
<item name="chipBackgroundColor">@color/tag_bg_dark</item>
|
||||
|
@ -203,6 +235,7 @@
|
|||
<item name="android:background">@color/gray90</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
</style>
|
||||
|
||||
<style name="actionButtons.dark">
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/button_text_dark</item>
|
||||
|
@ -217,6 +250,7 @@
|
|||
<item name="iconTint">@color/gray75</item>
|
||||
<item name="iconSize">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="storyButtons.dark" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
|
@ -229,6 +263,7 @@
|
|||
<style name="storyButtonsDimmed" parent="storyButtons">
|
||||
<item name="android:textColor">@color/gray85</item>
|
||||
</style>
|
||||
|
||||
<style name="storyButtonsDimmed.dark" parent="storyButtons.dark">
|
||||
<item name="android:textColor">@color/gray30</item>
|
||||
</style>
|
||||
|
@ -236,9 +271,11 @@
|
|||
<style name="shareBarBackground">
|
||||
<item name="android:background">@color/share_bar_background</item>
|
||||
</style>
|
||||
|
||||
<style name="shareBarBackground.dark">
|
||||
<item name="android:background">@color/dark_share_bar_background</item>
|
||||
</style>
|
||||
|
||||
<style name="shareBarBackground.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
</style>
|
||||
|
@ -250,6 +287,7 @@
|
|||
<item name="android:shadowDy">1</item>
|
||||
<item name="android:shadowRadius">1</item>
|
||||
</style>
|
||||
|
||||
<style name="shareBarText.dark">
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
<item name="android:shadowDx">0</item>
|
||||
|
@ -261,10 +299,12 @@
|
|||
<item name="android:background">@drawable/gradient_background_default</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
</style>
|
||||
|
||||
<style name="commentsHeader.dark">
|
||||
<item name="android:background">@drawable/dark_gradient_background_default</item>
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
</style>
|
||||
|
||||
<style name="commentsHeader.black">
|
||||
<item name="android:background">@drawable/black_gradient_background_default</item>
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
|
@ -274,10 +314,12 @@
|
|||
<item name="android:background">@drawable/gradient_background_default</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
</style>
|
||||
|
||||
<style name="activityDetailsPager.dark">
|
||||
<item name="android:background">@drawable/dark_gradient_background_default</item>
|
||||
<item name="android:textColor">@color/dark_text</item>
|
||||
</style>
|
||||
|
||||
<style name="activityDetailsPager.black">
|
||||
<item name="android:background">@drawable/black_gradient_background_default</item>
|
||||
<item name="android:textColor">@color/dark_text</item>
|
||||
|
@ -286,6 +328,7 @@
|
|||
<style name="rowBorder">
|
||||
<item name="android:background">@color/row_border</item>
|
||||
</style>
|
||||
|
||||
<style name="rowBorder.dark">
|
||||
<item name="android:background">@color/dark_row_border</item>
|
||||
</style>
|
||||
|
@ -294,10 +337,12 @@
|
|||
<item name="android:background">@color/item_background</item>
|
||||
<item name="android:textColor">@color/text</item>
|
||||
</style>
|
||||
|
||||
<style name="profileCount.dark">
|
||||
<item name="android:background">@color/dark_item_background</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="profileCount.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
|
@ -307,10 +352,12 @@
|
|||
<item name="android:background">@color/item_background</item>
|
||||
<item name="android:divider">@drawable/divider_light</item>
|
||||
</style>
|
||||
|
||||
<style name="profileActivityList.dark">
|
||||
<item name="android:background">@color/dark_item_background</item>
|
||||
<item name="android:divider">@drawable/divider_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="profileActivityList.black">
|
||||
<item name="android:background">@color/black</item>
|
||||
<item name="android:divider">@drawable/divider_dark</item>
|
||||
|
@ -319,9 +366,11 @@
|
|||
<style name="storyCommentDivider">
|
||||
<item name="android:background">@color/story_comment_divider</item>
|
||||
</style>
|
||||
|
||||
<style name="storyCommentDivider.dark">
|
||||
<item name="android:background">@color/dark_story_comment_divider</item>
|
||||
</style>
|
||||
|
||||
<style name="storyCommentDivider.black">
|
||||
<item name="android:background">@color/gray07</item>
|
||||
</style>
|
||||
|
@ -329,6 +378,7 @@
|
|||
<style name="explainerText">
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
</style>
|
||||
|
||||
<style name="explainerText.dark">
|
||||
<item name="android:textColor">@color/dark_text</item>
|
||||
</style>
|
||||
|
@ -338,15 +388,17 @@
|
|||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="toggleText.dark">
|
||||
<item name="android:textColor">@color/gray55</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="selectorOverlayBackgroundLeft">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_left</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundLeft.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_left</item>
|
||||
</style>
|
||||
|
@ -355,6 +407,7 @@
|
|||
<item name="android:background">@drawable/selector_overlay_bg_right</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundRight.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_right</item>
|
||||
<item name="android:textColor">@color/button_text_dark</item>
|
||||
|
@ -364,6 +417,7 @@
|
|||
<item name="android:background">@drawable/selector_overlay_bg_right_done</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundRightDone.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_right_done</item>
|
||||
<item name="android:textColor">@color/button_text_dark</item>
|
||||
|
@ -372,6 +426,7 @@
|
|||
<style name="selectorOverlayBackgroundSend">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_send</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundSend.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_send</item>
|
||||
</style>
|
||||
|
@ -380,6 +435,7 @@
|
|||
<item name="android:background">@drawable/selector_overlay_bg_story</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundStory.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_story</item>
|
||||
<item name="android:textColor">@color/button_text_dark</item>
|
||||
|
@ -389,6 +445,7 @@
|
|||
<item name="android:background">@drawable/selector_overlay_bg_text</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
</style>
|
||||
|
||||
<style name="selectorOverlayBackgroundText.dark">
|
||||
<item name="android:background">@drawable/selector_overlay_bg_dark_text</item>
|
||||
<item name="android:textColor">@color/button_text_dark</item>
|
||||
|
@ -397,6 +454,7 @@
|
|||
<style name="muteicon">
|
||||
<item name="android:src">@drawable/mute_white</item>
|
||||
</style>
|
||||
|
||||
<style name="muteicon.dark">
|
||||
<item name="android:src">@drawable/mute_black</item>
|
||||
</style>
|
||||
|
@ -429,7 +487,7 @@
|
|||
<style name="dialogPreference" parent="Preference.DialogPreference.Material">
|
||||
<item name="iconSpaceReserved">false</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="circleProgressIndicator" parent="Widget.MaterialComponents.CircularProgressIndicator">
|
||||
<item name="indicatorColor">@color/newsblur_blue</item>
|
||||
<item name="indicatorSize">24dp</item>
|
||||
|
@ -448,4 +506,22 @@
|
|||
<item name="cornerSize">10%</item>
|
||||
</style>
|
||||
|
||||
<style name="toggleButton" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="backgroundTint">@color/mtrl_btn_bg_color_selector_light</item>
|
||||
<item name="android:textColor">@color/mtrl_btn_text_color_selector_light</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
</style>
|
||||
|
||||
<style name="toggleButton.dark" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="backgroundTint">@color/mtrl_btn_bg_color_selector_dark</item>
|
||||
<item name="android:textColor">@color/mtrl_btn_text_color_selector_dark</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<item name="preferenceTheme">@style/preferenceTheme</item>
|
||||
<item name="fontFamily">@font/whitney</item>
|
||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||
<item name="toggleButton">@style/toggleButton</item>
|
||||
</style>
|
||||
|
||||
<style name="NewsBlurDarkTheme" parent="Theme.MaterialComponents.NoActionBar">
|
||||
|
@ -111,6 +112,7 @@
|
|||
<item name="android:navigationBarColor">@android:color/black</item>
|
||||
<item name="fontFamily">@font/whitney</item>
|
||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||
<item name="toggleButton">@style/toggleButton.dark</item>
|
||||
</style>
|
||||
|
||||
<style name="NewsBlurBlackTheme" parent="Theme.MaterialComponents.NoActionBar" >
|
||||
|
@ -168,6 +170,7 @@
|
|||
<item name="android:navigationBarColor">@android:color/black</item>
|
||||
<item name="fontFamily">@font/whitney</item>
|
||||
<item name="circleProgressIndicator">@style/circleProgressIndicator</item>
|
||||
<item name="toggleButton">@style/toggleButton.dark</item>
|
||||
</style>
|
||||
|
||||
<style name="NewsBlurTheme.Translucent" parent="NewsBlurTheme">
|
||||
|
|
|
@ -148,6 +148,11 @@
|
|||
android:entries="@array/volume_key_navigation_entries"
|
||||
android:entryValues="@array/volume_key_navigation_values"
|
||||
android:defaultValue="@string/default_volume_key_navigation_value" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="load_next_on_mark_read"
|
||||
android:title="@string/settings_load_next_on_mark_read"
|
||||
android:summary="@string/settings_load_next_on_mark_read_summary" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
|
@ -3,6 +3,9 @@ package com.newsblur.activity;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
@ -12,16 +15,30 @@ import com.google.android.play.core.review.ReviewManager;
|
|||
import com.google.android.play.core.review.ReviewManagerFactory;
|
||||
import com.google.android.play.core.tasks.Task;
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.fragment.DeleteFeedFragment;
|
||||
import com.newsblur.fragment.FeedIntelTrainerFragment;
|
||||
import com.newsblur.fragment.RenameDialogFragment;
|
||||
import com.newsblur.util.FeedExt;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class FeedItemsList extends ItemsList {
|
||||
|
||||
@Inject
|
||||
@IconLoader
|
||||
ImageLoader iconLoader;
|
||||
|
||||
public static final String EXTRA_FEED = "feed";
|
||||
public static final String EXTRA_FOLDER_NAME = "folderName";
|
||||
private Feed feed;
|
||||
|
@ -30,22 +47,21 @@ public class FeedItemsList extends ItemsList {
|
|||
private ReviewInfo reviewInfo;
|
||||
|
||||
public static void startActivity(Context context, FeedSet feedSet,
|
||||
Feed feed, String folderName) {
|
||||
Feed feed, String folderName,
|
||||
@Nullable SessionDataSource sessionDataSource) {
|
||||
Intent intent = new Intent(context, FeedItemsList.class);
|
||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, feedSet);
|
||||
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
|
||||
intent.putExtra(FeedItemsList.EXTRA_FOLDER_NAME, folderName);
|
||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, feedSet);
|
||||
intent.putExtra(ItemsList.EXTRA_SESSION_DATA, sessionDataSource);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
feed = (Feed) getIntent().getSerializableExtra(EXTRA_FEED);
|
||||
folderName = getIntent().getStringExtra(EXTRA_FOLDER_NAME);
|
||||
|
||||
super.onCreate(bundle);
|
||||
|
||||
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
|
||||
setupFeedItems(getIntent());
|
||||
viewModel.getNextSession().observe(this, this::setupFeedItems);
|
||||
checkInAppReview();
|
||||
}
|
||||
|
||||
|
@ -116,11 +132,11 @@ public class FeedItemsList extends ItemsList {
|
|||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (feed.isNotifyUnread()) {
|
||||
if (FeedExt.isAndroidNotifyUnread(feed)) {
|
||||
menu.findItem(R.id.menu_notifications_disable).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_unread).setChecked(true);
|
||||
menu.findItem(R.id.menu_notifications_focus).setChecked(false);
|
||||
} else if (feed.isNotifyFocus()) {
|
||||
} else if (FeedExt.isAndroidNotifyFocus(feed)) {
|
||||
menu.findItem(R.id.menu_notifications_disable).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_unread).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_focus).setChecked(true);
|
||||
|
@ -137,6 +153,28 @@ public class FeedItemsList extends ItemsList {
|
|||
return "feed:" + feed.feedId;
|
||||
}
|
||||
|
||||
private void setupFeedItems(Session session) {
|
||||
Feed feed = session.getFeed();
|
||||
String folderName = session.getFolderName();
|
||||
if (feed != null && folderName != null) {
|
||||
setupFeedItems(feed, folderName);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupFeedItems(Intent intent) {
|
||||
Feed feed = (Feed) intent.getSerializableExtra(EXTRA_FEED);
|
||||
String folderName = intent.getStringExtra(EXTRA_FOLDER_NAME);
|
||||
setupFeedItems(feed, folderName);
|
||||
}
|
||||
|
||||
private void setupFeedItems(@NonNull Feed feed, @NonNull String folderName) {
|
||||
this.feed = feed;
|
||||
this.folderName = folderName;
|
||||
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
|
||||
}
|
||||
|
||||
private void checkInAppReview() {
|
||||
if (!PrefsUtils.hasInAppReviewed(this)) {
|
||||
reviewManager = ReviewManagerFactory.create(this);
|
||||
|
|
|
@ -20,7 +20,6 @@ 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
|
||||
|
@ -86,13 +85,9 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
|
|||
override fun afterTextChanged(s: Editable) {
|
||||
searchQueryRunnable?.let { handler.removeCallbacks(it) }
|
||||
searchQueryRunnable = Runnable {
|
||||
if (tryAddByURL(s.toString())) {
|
||||
return@Runnable
|
||||
}
|
||||
|
||||
syncClearIconVisibility(s)
|
||||
if (s.isNotEmpty()) searchQuery(s)
|
||||
else syncSearchResults(arrayOf())
|
||||
else syncSearchResults(emptyList())
|
||||
}
|
||||
handler.postDelayed(searchQueryRunnable!!, 350)
|
||||
}
|
||||
|
@ -111,7 +106,12 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
|
|||
onPostExecute = {
|
||||
binding.loadingCircle.visibility = View.GONE
|
||||
binding.clearText.visibility = View.VISIBLE
|
||||
syncSearchResults(it ?: arrayOf())
|
||||
syncSearchResults(buildList {
|
||||
if (matchesUrl(query.toString())) {
|
||||
add(FeedResult.createFeedResultForUrl(query.toString().lowercase()))
|
||||
}
|
||||
addAll(it ?: arrayOf())
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -120,15 +120,20 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
|
|||
binding.clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun syncSearchResults(results: Array<FeedResult>) {
|
||||
private fun syncSearchResults(results: List<FeedResult>) {
|
||||
adapter.replaceAll(results)
|
||||
}
|
||||
|
||||
private fun showAddFeedDialog(feedUrl: String, feedLabel: String) {
|
||||
val addFeedFragment: DialogFragment = AddFeedFragment.newInstance(feedUrl, feedLabel)
|
||||
addFeedFragment.show(supportFragmentManager, "dialog")
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the text entered in the query field was actually a URL so we can skip the
|
||||
* search step and just let users who know feed URLs directly subscribe.
|
||||
* See if the text entered in the query field was actually a URL
|
||||
* to let users who know feed URLs directly subscribe.
|
||||
*/
|
||||
private fun tryAddByURL(s: String): Boolean {
|
||||
private fun matchesUrl(s: String): Boolean {
|
||||
var u: URL? = null
|
||||
try {
|
||||
u = URL(s)
|
||||
|
@ -144,12 +149,6 @@ class FeedSearchActivity : NbActivity(), OnFeedSearchResultClickListener, AddFee
|
|||
if (u.host == null || u.host.trim().isEmpty()) {
|
||||
return false
|
||||
}
|
||||
showAddFeedDialog(s, s)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showAddFeedDialog(feedUrl: String, feedLabel: String) {
|
||||
val addFeedFragment: DialogFragment = AddFeedFragment.newInstance(feedUrl, feedLabel)
|
||||
addFeedFragment.show(supportFragmentManager, "dialog")
|
||||
}
|
||||
}
|
|
@ -8,8 +8,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.newsblur.R
|
||||
import com.newsblur.databinding.ViewFeedSearchRowBinding
|
||||
import com.newsblur.domain.FeedResult
|
||||
import com.newsblur.util.FeedUtils
|
||||
import com.newsblur.util.ImageLoader
|
||||
import com.newsblur.util.setViewGone
|
||||
import com.newsblur.util.setViewVisible
|
||||
|
||||
class FeedSearchAdapter(
|
||||
private val onClickListener: OnFeedSearchResultClickListener,
|
||||
|
@ -30,12 +31,11 @@ class FeedSearchAdapter(
|
|||
|
||||
override fun getItemCount(): Int = resultsList.size
|
||||
|
||||
fun replaceAll(results: Array<FeedResult>) {
|
||||
val newResultsList: List<FeedResult> = results.toList()
|
||||
val diffCallback = ResultDiffCallback(resultsList, newResultsList)
|
||||
fun replaceAll(results: List<FeedResult>) {
|
||||
val diffCallback = ResultDiffCallback(resultsList, results)
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
resultsList.clear()
|
||||
resultsList.addAll(newResultsList)
|
||||
resultsList.addAll(results)
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
|
@ -46,19 +46,25 @@ class FeedSearchAdapter(
|
|||
fun bind(result: FeedResult) {
|
||||
val resultFaviconUrl = result.faviconUrl
|
||||
if (resultFaviconUrl.isNotEmpty()) {
|
||||
iconLoader.displayImage(resultFaviconUrl, binding.imgFeedIcon)
|
||||
iconLoader.displayImage(resultFaviconUrl, binding.imgFeedIcon)
|
||||
}
|
||||
|
||||
binding.textTitle.text = result.label
|
||||
binding.textTagline.text = result.tagline
|
||||
val subscribersCountText = binding.root.context.getString(R.string.feed_subscribers, result.numberOfSubscriber)
|
||||
binding.textSubscriptionCount.text = subscribersCountText
|
||||
|
||||
if (result.numberOfSubscriber > 0) {
|
||||
val subscribersCountText = binding.root.context.getString(R.string.feed_subscribers, result.numberOfSubscriber)
|
||||
binding.textSubscriptionCount.text = subscribersCountText
|
||||
binding.textSubscriptionCount.setViewVisible()
|
||||
} else {
|
||||
binding.textSubscriptionCount.setViewGone()
|
||||
}
|
||||
|
||||
if (result.url.isNotEmpty()) {
|
||||
binding.rowResultAddress.text = result.url
|
||||
binding.rowResultAddress.visibility = View.VISIBLE
|
||||
binding.rowResultAddress.setViewVisible()
|
||||
} else {
|
||||
binding.rowResultAddress.visibility = View.GONE
|
||||
binding.rowResultAddress.setViewGone()
|
||||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
|
|
|
@ -12,15 +12,19 @@ public class FolderItemsList extends ItemsList {
|
|||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
folderName = getIntent().getStringExtra(EXTRA_FOLDER_NAME);
|
||||
|
||||
super.onCreate(bundle);
|
||||
|
||||
UIUtils.setupToolbar(this, R.drawable.ic_folder_closed, folderName, false);
|
||||
setupFolder(getIntent().getStringExtra(EXTRA_FOLDER_NAME));
|
||||
viewModel.getNextSession().observe(this, session ->
|
||||
setupFolder(session.getFolderName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSaveSearchFeedId() {
|
||||
return "river:" + folderName;
|
||||
}
|
||||
|
||||
private void setupFolder(String folderName) {
|
||||
this.folderName = folderName;
|
||||
UIUtils.setupToolbar(this, R.drawable.ic_folder_closed, folderName, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,15 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
|
|||
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
|
@ -18,32 +21,27 @@ import android.view.View.OnKeyListener;
|
|||
import com.newsblur.R;
|
||||
import com.newsblur.database.BlurDatabaseHelper;
|
||||
import com.newsblur.databinding.ActivityItemslistBinding;
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.delegate.ItemListContextMenuDelegate;
|
||||
import com.newsblur.delegate.ItemListContextMenuDelegateImpl;
|
||||
import com.newsblur.fragment.ItemSetFragment;
|
||||
import com.newsblur.fragment.SaveSearchFragment;
|
||||
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.ReadingActionListener;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.ReadFilter;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.StateFilter;
|
||||
import com.newsblur.util.StoryContentPreviewStyle;
|
||||
import com.newsblur.util.StoryListStyle;
|
||||
import com.newsblur.util.StoryOrder;
|
||||
import com.newsblur.util.ListTextSize;
|
||||
import com.newsblur.util.ThumbnailStyle;
|
||||
import com.newsblur.util.UIUtils;
|
||||
import com.newsblur.viewModel.ItemListViewModel;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public abstract class ItemsList extends NbActivity {
|
||||
public abstract class ItemsList extends NbActivity implements ReadingActionListener {
|
||||
|
||||
@Inject
|
||||
BlurDatabaseHelper dbHelper;
|
||||
|
@ -51,21 +49,21 @@ public abstract class ItemsList extends NbActivity {
|
|||
@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";
|
||||
public static final String EXTRA_VISIBLE_SEARCH = "visibleSearch";
|
||||
public static final String EXTRA_SESSION_DATA = "session_data";
|
||||
private static final String BUNDLE_ACTIVE_SEARCH_QUERY = "activeSearchQuery";
|
||||
private ActivityItemslistBinding binding;
|
||||
|
||||
protected ItemSetFragment itemSetFragment;
|
||||
protected StateFilter intelState;
|
||||
|
||||
protected ItemListViewModel viewModel;
|
||||
protected FeedSet fs;
|
||||
|
||||
private ItemSetFragment itemSetFragment;
|
||||
private ActivityItemslistBinding binding;
|
||||
private ItemListContextMenuDelegate contextMenuDelegate;
|
||||
@Nullable
|
||||
private SessionDataSource sessionDataSource;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
|
@ -73,8 +71,10 @@ public abstract class ItemsList extends NbActivity {
|
|||
|
||||
overridePendingTransition(R.anim.slide_in_from_right, R.anim.slide_out_to_left);
|
||||
|
||||
contextMenuDelegate = new ItemListContextMenuDelegateImpl(this, feedUtils);
|
||||
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
|
||||
fs = (FeedSet) getIntent().getSerializableExtra(EXTRA_FEED_SET);
|
||||
intelState = PrefsUtils.getStateFilter(this);
|
||||
sessionDataSource = (SessionDataSource) getIntent().getSerializableExtra(EXTRA_SESSION_DATA);
|
||||
|
||||
// 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
|
||||
|
@ -84,6 +84,7 @@ public abstract class ItemsList extends NbActivity {
|
|||
String hash = (String) getIntent().getSerializableExtra(EXTRA_STORY_HASH);
|
||||
UIUtils.startReadingActivity(fs, hash, this);
|
||||
} else if (PrefsUtils.isAutoOpenFirstUnread(this)) {
|
||||
StateFilter intelState = PrefsUtils.getStateFilter(this);
|
||||
if (dbHelper.getUnreadCount(fs, intelState) > 0) {
|
||||
UIUtils.startReadingActivity(fs, Reading.FIND_FIRST_UNREAD, this);
|
||||
}
|
||||
|
@ -137,11 +138,9 @@ public abstract class ItemsList extends NbActivity {
|
|||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (binding.itemlistSearchQuery != null) {
|
||||
String q = binding.itemlistSearchQuery.getText().toString().trim();
|
||||
if (q.length() > 0) {
|
||||
outState.putString(BUNDLE_ACTIVE_SEARCH_QUERY, q);
|
||||
}
|
||||
String q = binding.itemlistSearchQuery.getText().toString().trim();
|
||||
if (q.length() > 0) {
|
||||
outState.putString(BUNDLE_ACTIVE_SEARCH_QUERY, q);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,277 +166,19 @@ public abstract class ItemsList extends NbActivity {
|
|||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.itemslist, menu);
|
||||
|
||||
if (fs.isGlobalShared() ||
|
||||
fs.isAllSocial() ||
|
||||
fs.isFilterSaved() ||
|
||||
fs.isAllSaved() ||
|
||||
fs.isSingleSavedTag() ||
|
||||
fs.isInfrequent() ||
|
||||
fs.isAllRead() ) {
|
||||
menu.findItem(R.id.menu_mark_all_as_read).setVisible(false);
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared() ||
|
||||
fs.isAllSocial() ||
|
||||
fs.isAllRead() ) {
|
||||
menu.findItem(R.id.menu_story_order).setVisible(false);
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared() ||
|
||||
fs.isFilterSaved() ||
|
||||
fs.isAllSaved() ||
|
||||
fs.isSingleSavedTag() ||
|
||||
fs.isInfrequent() ||
|
||||
fs.isAllRead() ) {
|
||||
menu.findItem(R.id.menu_read_filter).setVisible(false);
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll).setVisible(false);
|
||||
menu.findItem(R.id.menu_story_content_preview_style).setVisible(false);
|
||||
menu.findItem(R.id.menu_story_thumbnail_style).setVisible(false);
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared() ||
|
||||
fs.isAllSocial() ||
|
||||
fs.isInfrequent() ||
|
||||
fs.isAllRead() ) {
|
||||
menu.findItem(R.id.menu_search_stories).setVisible(false);
|
||||
}
|
||||
|
||||
if ((!fs.isSingleNormal()) || fs.isFilterSaved()) {
|
||||
menu.findItem(R.id.menu_notifications).setVisible(false);
|
||||
menu.findItem(R.id.menu_delete_feed).setVisible(false);
|
||||
menu.findItem(R.id.menu_instafetch_feed).setVisible(false);
|
||||
menu.findItem(R.id.menu_intel).setVisible(false);
|
||||
menu.findItem(R.id.menu_rename_feed).setVisible(false);
|
||||
menu.findItem(R.id.menu_statistics).setVisible(false);
|
||||
}
|
||||
|
||||
if (!fs.isInfrequent()) {
|
||||
menu.findItem(R.id.menu_infrequent_cutoff).setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
return contextMenuDelegate.onCreateMenuOptions(menu, getMenuInflater(), fs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
boolean showSavedSearch = !TextUtils.isEmpty(binding.itemlistSearchQuery.getText());
|
||||
return contextMenuDelegate.onPrepareMenuOptions(menu, fs, showSavedSearch);
|
||||
}
|
||||
|
||||
StoryOrder storyOrder = PrefsUtils.getStoryOrder(this, fs);
|
||||
if (storyOrder == StoryOrder.NEWEST) {
|
||||
menu.findItem(R.id.menu_story_order_newest).setChecked(true);
|
||||
} else if (storyOrder == StoryOrder.OLDEST) {
|
||||
menu.findItem(R.id.menu_story_order_oldest).setChecked(true);
|
||||
}
|
||||
|
||||
ReadFilter readFilter = PrefsUtils.getReadFilter(this, fs);
|
||||
if (readFilter == ReadFilter.ALL) {
|
||||
menu.findItem(R.id.menu_read_filter_all_stories).setChecked(true);
|
||||
} else if (readFilter == ReadFilter.UNREAD) {
|
||||
menu.findItem(R.id.menu_read_filter_unread_only).setChecked(true);
|
||||
}
|
||||
|
||||
StoryListStyle listStyle = PrefsUtils.getStoryListStyle(this, fs);
|
||||
if (listStyle == StoryListStyle.GRID_F) {
|
||||
menu.findItem(R.id.menu_list_style_grid_f).setChecked(true);
|
||||
} else if (listStyle == StoryListStyle.GRID_M) {
|
||||
menu.findItem(R.id.menu_list_style_grid_m).setChecked(true);
|
||||
} else if (listStyle == StoryListStyle.GRID_C) {
|
||||
menu.findItem(R.id.menu_list_style_grid_c).setChecked(true);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_list_style_list).setChecked(true);
|
||||
}
|
||||
|
||||
ThemeValue themeValue = PrefsUtils.getSelectedTheme(this);
|
||||
if (themeValue == ThemeValue.LIGHT) {
|
||||
menu.findItem(R.id.menu_theme_light).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.DARK) {
|
||||
menu.findItem(R.id.menu_theme_dark).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.BLACK) {
|
||||
menu.findItem(R.id.menu_theme_black).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.AUTO) {
|
||||
menu.findItem(R.id.menu_theme_auto).setChecked(true);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(binding.itemlistSearchQuery.getText())) {
|
||||
menu.findItem(R.id.menu_save_search).setVisible(true);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_save_search).setVisible(false);
|
||||
}
|
||||
|
||||
StoryContentPreviewStyle previewStyle = PrefsUtils.getStoryContentPreviewStyle(this);
|
||||
if (previewStyle == StoryContentPreviewStyle.NONE) {
|
||||
menu.findItem(R.id.menu_story_content_preview_none).setChecked(true);
|
||||
} else if (previewStyle == StoryContentPreviewStyle.SMALL) {
|
||||
menu.findItem(R.id.menu_story_content_preview_small).setChecked(true);
|
||||
} else if (previewStyle == StoryContentPreviewStyle.MEDIUM) {
|
||||
menu.findItem(R.id.menu_story_content_preview_medium).setChecked(true);
|
||||
} else if (previewStyle == StoryContentPreviewStyle.LARGE) {
|
||||
menu.findItem(R.id.menu_story_content_preview_large).setChecked(true);
|
||||
}
|
||||
|
||||
ThumbnailStyle thumbnailStyle = PrefsUtils.getThumbnailStyle(this);
|
||||
if (thumbnailStyle == ThumbnailStyle.LEFT_SMALL) {
|
||||
menu.findItem(R.id.menu_story_thumbnail_left_small).setChecked(true);
|
||||
} else if (thumbnailStyle == ThumbnailStyle.LEFT_LARGE) {
|
||||
menu.findItem(R.id.menu_story_thumbnail_left_large).setChecked(true);
|
||||
} else if (thumbnailStyle == ThumbnailStyle.RIGHT_SMALL) {
|
||||
menu.findItem(R.id.menu_story_thumbnail_right_small).setChecked(true);
|
||||
} else if (thumbnailStyle == ThumbnailStyle.RIGHT_LARGE) {
|
||||
menu.findItem(R.id.menu_story_thumbnail_right_large).setChecked(true);
|
||||
} else if (thumbnailStyle.isOff()) {
|
||||
menu.findItem(R.id.menu_story_thumbnail_no_preview).setChecked(true);
|
||||
}
|
||||
|
||||
SpacingStyle spacingStyle = PrefsUtils.getSpacingStyle(this);
|
||||
if (spacingStyle == SpacingStyle.COMFORTABLE) {
|
||||
menu.findItem(R.id.menu_spacing_comfortable).setChecked(true);
|
||||
} else if (spacingStyle == SpacingStyle.COMPACT) {
|
||||
menu.findItem(R.id.menu_spacing_compact).setChecked(true);
|
||||
}
|
||||
|
||||
ListTextSize listTextSize = ListTextSize.fromSize(PrefsUtils.getListTextSize(this));
|
||||
if (listTextSize == ListTextSize.XS) {
|
||||
menu.findItem(R.id.menu_text_size_xs).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.S) {
|
||||
menu.findItem(R.id.menu_text_size_s).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.M) {
|
||||
menu.findItem(R.id.menu_text_size_m).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.L) {
|
||||
menu.findItem(R.id.menu_text_size_l).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.XL) {
|
||||
menu.findItem(R.id.menu_text_size_xl).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.XXL) {
|
||||
menu.findItem(R.id.menu_text_size_xxl).setChecked(true);
|
||||
}
|
||||
|
||||
boolean isMarkReadOnScroll = PrefsUtils.isMarkReadOnFeedScroll(this);
|
||||
if (isMarkReadOnScroll) {
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll_enabled).setChecked(true);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll_disabled).setChecked(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
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);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_story_order_newest) {
|
||||
updateStoryOrder(StoryOrder.NEWEST);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_story_order_oldest) {
|
||||
updateStoryOrder(StoryOrder.OLDEST);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_read_filter_all_stories) {
|
||||
updateReadFilter(ReadFilter.ALL);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_read_filter_unread_only) {
|
||||
updateReadFilter(ReadFilter.UNREAD);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xs) {
|
||||
updateTextSizeStyle(ListTextSize.XS);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_s) {
|
||||
updateTextSizeStyle(ListTextSize.S);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_m) {
|
||||
updateTextSizeStyle(ListTextSize.M);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_l) {
|
||||
updateTextSizeStyle(ListTextSize.L);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xl) {
|
||||
updateTextSizeStyle(ListTextSize.XL);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xxl) {
|
||||
updateTextSizeStyle(ListTextSize.XXL);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_search_stories) {
|
||||
if (binding.itemlistSearchQuery.getVisibility() != View.VISIBLE) {
|
||||
binding.itemlistSearchQuery.setVisibility(View.VISIBLE);
|
||||
binding.itemlistSearchQuery.requestFocus();
|
||||
} else {
|
||||
binding.itemlistSearchQuery.setVisibility(View.GONE);
|
||||
checkSearchQuery();
|
||||
}
|
||||
} else if(item.getItemId() == R.id.menu_theme_auto) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.AUTO);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_light) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.LIGHT);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_dark) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.DARK);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_black) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.BLACK);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_spacing_comfortable) {
|
||||
updateSpacingStyle(SpacingStyle.COMFORTABLE);
|
||||
} else if (item.getItemId() == R.id.menu_spacing_compact) {
|
||||
updateSpacingStyle(SpacingStyle.COMPACT);
|
||||
} else if (item.getItemId() == R.id.menu_list_style_list) {
|
||||
PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.LIST);
|
||||
itemSetFragment.updateListStyle();
|
||||
} else if (item.getItemId() == R.id.menu_list_style_grid_f) {
|
||||
PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.GRID_F);
|
||||
itemSetFragment.updateListStyle();
|
||||
} else if (item.getItemId() == R.id.menu_list_style_grid_m) {
|
||||
PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.GRID_M);
|
||||
itemSetFragment.updateListStyle();
|
||||
} else if (item.getItemId() == R.id.menu_list_style_grid_c) {
|
||||
PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.GRID_C);
|
||||
itemSetFragment.updateListStyle();
|
||||
} else if (item.getItemId() == R.id.menu_save_search) {
|
||||
String feedId = getSaveSearchFeedId();
|
||||
if (feedId != null) {
|
||||
String query = binding.itemlistSearchQuery.getText().toString();
|
||||
SaveSearchFragment frag = SaveSearchFragment.newInstance(feedId, query);
|
||||
frag.show(getSupportFragmentManager(), SaveSearchFragment.class.getName());
|
||||
}
|
||||
} else if (item.getItemId() == R.id.menu_story_content_preview_none) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(this, StoryContentPreviewStyle.NONE);
|
||||
itemSetFragment.notifyContentPrefsChanged();
|
||||
} else if (item.getItemId() == R.id.menu_story_content_preview_small) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(this, StoryContentPreviewStyle.SMALL);
|
||||
itemSetFragment.notifyContentPrefsChanged();
|
||||
} else if (item.getItemId() == R.id.menu_story_content_preview_medium) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(this, StoryContentPreviewStyle.MEDIUM);
|
||||
itemSetFragment.notifyContentPrefsChanged();
|
||||
} else if (item.getItemId() == R.id.menu_story_content_preview_large) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(this, StoryContentPreviewStyle.LARGE);
|
||||
itemSetFragment.notifyContentPrefsChanged();
|
||||
} else if (item.getItemId() == R.id.menu_mark_read_on_scroll_disabled) {
|
||||
PrefsUtils.setMarkReadOnScroll(this, false);
|
||||
} else if (item.getItemId() == R.id.menu_mark_read_on_scroll_enabled) {
|
||||
PrefsUtils.setMarkReadOnScroll(this, true);
|
||||
} else if (item.getItemId() == R.id.menu_story_thumbnail_left_small) {
|
||||
PrefsUtils.setThumbnailStyle(this, ThumbnailStyle.LEFT_SMALL);
|
||||
itemSetFragment.updateThumbnailStyle();
|
||||
} else if (item.getItemId() == R.id.menu_story_thumbnail_left_large) {
|
||||
PrefsUtils.setThumbnailStyle(this, ThumbnailStyle.LEFT_LARGE);
|
||||
itemSetFragment.updateThumbnailStyle();
|
||||
} else if (item.getItemId() == R.id.menu_story_thumbnail_right_small) {
|
||||
PrefsUtils.setThumbnailStyle(this, ThumbnailStyle.RIGHT_SMALL);
|
||||
itemSetFragment.updateThumbnailStyle();
|
||||
} else if (item.getItemId() == R.id.menu_story_thumbnail_right_large) {
|
||||
PrefsUtils.setThumbnailStyle(this, ThumbnailStyle.RIGHT_LARGE);
|
||||
itemSetFragment.updateThumbnailStyle();
|
||||
} else if (item.getItemId() == R.id.menu_story_thumbnail_no_preview) {
|
||||
PrefsUtils.setThumbnailStyle(this, ThumbnailStyle.OFF);
|
||||
itemSetFragment.updateThumbnailStyle();
|
||||
}
|
||||
|
||||
return false;
|
||||
return contextMenuDelegate.onOptionsItemSelected(item, itemSetFragment, fs, binding.itemlistSearchQuery, getSaveSearchFeedId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -455,6 +196,27 @@ public abstract class ItemsList extends NbActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadingActionCompleted() {
|
||||
if (sessionDataSource != null) {
|
||||
Session session = sessionDataSource.getNextSession();
|
||||
if (session != null) {
|
||||
// set the next session on the parent activity
|
||||
fs = session.getFeedSet();
|
||||
feedUtils.prepareReadingSession(fs, false);
|
||||
triggerSync();
|
||||
|
||||
// set the next session on the child activity
|
||||
viewModel.updateSession(session);
|
||||
|
||||
// update item set fragment
|
||||
itemSetFragment.resetEmptyState();
|
||||
itemSetFragment.hasUpdated();
|
||||
itemSetFragment.scrollToTop();
|
||||
} else finish();
|
||||
} else finish();
|
||||
}
|
||||
|
||||
private void updateStatusIndicators() {
|
||||
if (binding.itemlistSyncStatus != null) {
|
||||
String syncStatus = NBSyncService.getSyncStatusMessage(this, true);
|
||||
|
@ -511,26 +273,6 @@ public abstract class ItemsList extends NbActivity {
|
|||
transaction.commit();
|
||||
}
|
||||
|
||||
private void updateTextSizeStyle(ListTextSize listTextSize) {
|
||||
PrefsUtils.setListTextSize(this, listTextSize.getSize());
|
||||
itemSetFragment.updateTextSize();
|
||||
}
|
||||
|
||||
private void updateSpacingStyle(SpacingStyle spacingStyle) {
|
||||
PrefsUtils.setSpacingStyle(this, spacingStyle);
|
||||
itemSetFragment.updateSpacingStyle();
|
||||
}
|
||||
|
||||
private void updateStoryOrder(StoryOrder storyOrder) {
|
||||
PrefsUtils.updateStoryOrder(this, fs, storyOrder);
|
||||
restartReadingSession();
|
||||
}
|
||||
|
||||
private void updateReadFilter(ReadFilter readFilter) {
|
||||
PrefsUtils.updateReadFilter(this, fs, readFilter);
|
||||
restartReadingSession();
|
||||
}
|
||||
|
||||
protected void restartReadingSession() {
|
||||
NBSyncService.resetFetchState(fs);
|
||||
feedUtils.prepareReadingSession(fs, true);
|
||||
|
@ -553,4 +295,5 @@ public abstract class ItemsList extends NbActivity {
|
|||
}
|
||||
|
||||
abstract String getSaveSearchFeedId();
|
||||
|
||||
}
|
||||
|
|
|
@ -7,19 +7,15 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
|
@ -28,24 +24,20 @@ import android.widget.AbsListView;
|
|||
import com.newsblur.R;
|
||||
import com.newsblur.database.BlurDatabaseHelper;
|
||||
import com.newsblur.databinding.ActivityMainBinding;
|
||||
import com.newsblur.delegate.MainContextMenuDelegate;
|
||||
import com.newsblur.delegate.MainContextMenuDelegateImpl;
|
||||
import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
|
||||
import com.newsblur.fragment.FolderListFragment;
|
||||
import com.newsblur.fragment.LoginAsDialogFragment;
|
||||
import com.newsblur.fragment.LogoutDialogFragment;
|
||||
import com.newsblur.service.BootReceiver;
|
||||
import com.newsblur.service.NBSyncService;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.PrefConstants.ThemeValue;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.ShortcutUtils;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.StateFilter;
|
||||
import com.newsblur.util.ListTextSize;
|
||||
import com.newsblur.util.UIUtils;
|
||||
import com.newsblur.view.StateToggleButton.StateChangedListener;
|
||||
import com.newsblur.widget.WidgetUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
|
@ -63,10 +55,9 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
public static final String EXTRA_FORCE_SHOW_FEED_ID = "force_show_feed_id";
|
||||
|
||||
private FolderListFragment folderFeedList;
|
||||
private FragmentManager fragmentManager;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private boolean wasSwipeEnabled = false;
|
||||
private ActivityMainBinding binding;
|
||||
private MainContextMenuDelegate contextMenuDelegate;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -75,6 +66,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
super.onCreate(savedInstanceState);
|
||||
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
contextMenuDelegate = new MainContextMenuDelegateImpl(this, dbHelper);
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
// set the status bar to an generic loading message when the activity is first created so
|
||||
|
@ -82,12 +74,11 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
binding.mainSyncStatus.setText(R.string.loading);
|
||||
binding.mainSyncStatus.setVisibility(View.VISIBLE);
|
||||
|
||||
swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_container);
|
||||
swipeLayout.setColorSchemeResources(R.color.refresh_1, R.color.refresh_2, R.color.refresh_3, R.color.refresh_4);
|
||||
swipeLayout.setProgressBackgroundColorSchemeResource(UIUtils.getThemedResource(this, R.attr.actionbarBackground, android.R.attr.background));
|
||||
swipeLayout.setOnRefreshListener(this);
|
||||
binding.swipeContainer.setColorSchemeResources(R.color.refresh_1, R.color.refresh_2, R.color.refresh_3, R.color.refresh_4);
|
||||
binding.swipeContainer.setProgressBackgroundColorSchemeResource(UIUtils.getThemedResource(this, R.attr.actionbarBackground, android.R.attr.background));
|
||||
binding.swipeContainer.setOnRefreshListener(this);
|
||||
|
||||
fragmentManager = getSupportFragmentManager();
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
folderFeedList = (FolderListFragment) fragmentManager.findFragmentByTag("folderFeedListFragment");
|
||||
((FeedIntelligenceSelectorFragment) fragmentManager.findFragmentByTag("feedIntelligenceSelector")).setState(folderFeedList.currentState);
|
||||
|
||||
|
@ -196,7 +187,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
|
||||
folderFeedList.changeState(state);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleUpdate(int updateType) {
|
||||
if ((updateType & UPDATE_REBUILD) != 0) {
|
||||
|
@ -250,23 +241,17 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
}
|
||||
|
||||
private void updateStatusIndicators() {
|
||||
if (NBSyncService.isFeedFolderSyncRunning()) {
|
||||
swipeLayout.setRefreshing(true);
|
||||
} else {
|
||||
swipeLayout.setRefreshing(false);
|
||||
}
|
||||
binding.swipeContainer.setRefreshing(NBSyncService.isFeedFolderSyncRunning());
|
||||
|
||||
if (binding.mainSyncStatus != null) {
|
||||
String syncStatus = NBSyncService.getSyncStatusMessage(this, false);
|
||||
if (syncStatus != null) {
|
||||
if (AppConstants.VERBOSE_LOG) {
|
||||
syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this);
|
||||
}
|
||||
binding.mainSyncStatus.setText(syncStatus);
|
||||
binding.mainSyncStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.mainSyncStatus.setVisibility(View.GONE);
|
||||
String syncStatus = NBSyncService.getSyncStatusMessage(this, false);
|
||||
if (syncStatus != null) {
|
||||
if (AppConstants.VERBOSE_LOG) {
|
||||
syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this);
|
||||
}
|
||||
binding.mainSyncStatus.setText(syncStatus);
|
||||
binding.mainSyncStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.mainSyncStatus.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,135 +263,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
}
|
||||
|
||||
private void onClickMenuButton() {
|
||||
PopupMenu pm = new PopupMenu(this, binding.mainMenuButton);
|
||||
Menu menu = pm.getMenu();
|
||||
pm.getMenuInflater().inflate(R.menu.main, menu);
|
||||
|
||||
MenuItem loginAsItem = menu.findItem(R.id.menu_loginas);
|
||||
if (NBSyncService.isStaff == Boolean.TRUE) {
|
||||
loginAsItem.setVisible(true);
|
||||
} else {
|
||||
loginAsItem.setVisible(false);
|
||||
}
|
||||
|
||||
ThemeValue themeValue = PrefsUtils.getSelectedTheme(this);
|
||||
if (themeValue == ThemeValue.LIGHT) {
|
||||
menu.findItem(R.id.menu_theme_light).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.DARK) {
|
||||
menu.findItem(R.id.menu_theme_dark).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.BLACK) {
|
||||
menu.findItem(R.id.menu_theme_black).setChecked(true);
|
||||
} else if (themeValue == ThemeValue.AUTO) {
|
||||
menu.findItem(R.id.menu_theme_auto).setChecked(true);
|
||||
}
|
||||
|
||||
SpacingStyle spacingStyle = PrefsUtils.getSpacingStyle(this);
|
||||
if (spacingStyle == SpacingStyle.COMFORTABLE) {
|
||||
menu.findItem(R.id.menu_spacing_comfortable).setChecked(true);
|
||||
} else if (spacingStyle == SpacingStyle.COMPACT) {
|
||||
menu.findItem(R.id.menu_spacing_compact).setChecked(true);
|
||||
}
|
||||
|
||||
ListTextSize listTextSize = ListTextSize.fromSize(PrefsUtils.getListTextSize(this));
|
||||
if (listTextSize == ListTextSize.XS) {
|
||||
menu.findItem(R.id.menu_text_size_xs).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.S) {
|
||||
menu.findItem(R.id.menu_text_size_s).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.M) {
|
||||
menu.findItem(R.id.menu_text_size_m).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.L) {
|
||||
menu.findItem(R.id.menu_text_size_l).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.XL) {
|
||||
menu.findItem(R.id.menu_text_size_xl).setChecked(true);
|
||||
} else if (listTextSize == ListTextSize.XXL) {
|
||||
menu.findItem(R.id.menu_text_size_xxl).setChecked(true);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.menu_widget).setVisible(WidgetUtils.hasActiveAppWidgets(this));
|
||||
|
||||
pm.setOnMenuItemClickListener(this);
|
||||
pm.show();
|
||||
contextMenuDelegate.onMenuClick(binding.mainMenuButton, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_logout) {
|
||||
DialogFragment newFragment = new LogoutDialogFragment();
|
||||
newFragment.show(getSupportFragmentManager(), "dialog");
|
||||
} else if (item.getItemId() == R.id.menu_settings) {
|
||||
Intent settingsIntent = new Intent(this, Settings.class);
|
||||
startActivity(settingsIntent);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_widget) {
|
||||
Intent widgetIntent = new Intent(this, WidgetConfig.class);
|
||||
startActivity(widgetIntent);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_feedback_email) {
|
||||
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, dbHelper)));
|
||||
startActivity(i);
|
||||
} catch (Exception e) {
|
||||
Log.wtf(this.getClass().getName(), "device cannot even open URLs to report feedback");
|
||||
}
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xs) {
|
||||
folderFeedList.setListTextSize(ListTextSize.XS);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_s) {
|
||||
folderFeedList.setListTextSize(ListTextSize.S);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_m) {
|
||||
folderFeedList.setListTextSize(ListTextSize.M);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_l) {
|
||||
folderFeedList.setListTextSize(ListTextSize.L);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xl) {
|
||||
folderFeedList.setListTextSize(ListTextSize.XL);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_text_size_xxl) {
|
||||
folderFeedList.setListTextSize(ListTextSize.XXL);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_spacing_comfortable) {
|
||||
folderFeedList.setSpacingStyle(SpacingStyle.COMFORTABLE);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_spacing_compact) {
|
||||
folderFeedList.setSpacingStyle(SpacingStyle.COMPACT);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_loginas) {
|
||||
DialogFragment newFragment = new LoginAsDialogFragment();
|
||||
newFragment.show(getSupportFragmentManager(), "dialog");
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_theme_auto) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.AUTO);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_light) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.LIGHT);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_dark) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.DARK);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_theme_black) {
|
||||
PrefsUtils.setSelectedTheme(this, ThemeValue.BLACK);
|
||||
UIUtils.restartActivity(this);
|
||||
} else if (item.getItemId() == R.id.menu_premium_account) {
|
||||
Intent intent = new Intent(this, Premium.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_mute_sites) {
|
||||
Intent intent = new Intent(this, MuteConfig.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_import_export) {
|
||||
Intent intent = new Intent(this, ImportExportActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return contextMenuDelegate.onMenuItemClick(item, folderFeedList);
|
||||
}
|
||||
|
||||
private void onClickAddButton() {
|
||||
|
@ -442,10 +304,10 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||
if (swipeLayout != null) {
|
||||
if (binding != null) {
|
||||
boolean enable = (firstVisibleItem == 0);
|
||||
if (wasSwipeEnabled != enable) {
|
||||
swipeLayout.setEnabled(enable);
|
||||
binding.swipeContainer.setEnabled(enable);
|
||||
wasSwipeEnabled = enable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package com.newsblur.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.newsblur.R
|
||||
import com.newsblur.databinding.ActivityNotificationsBinding
|
||||
import com.newsblur.di.IconLoader
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.util.ImageLoader
|
||||
import com.newsblur.util.UIUtils
|
||||
import com.newsblur.util.setViewGone
|
||||
import com.newsblur.util.setViewVisible
|
||||
import com.newsblur.viewModel.NotificationsViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationsActivity : NbActivity(), NotificationsAdapter.Listener {
|
||||
|
||||
@IconLoader
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
private lateinit var binding: ActivityNotificationsBinding
|
||||
private lateinit var viewModel: NotificationsViewModel
|
||||
private lateinit var adapter: NotificationsAdapter
|
||||
|
||||
override fun onCreate(bundle: Bundle?) {
|
||||
super.onCreate(bundle)
|
||||
viewModel = ViewModelProvider(this)[NotificationsViewModel::class.java]
|
||||
binding = ActivityNotificationsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setupUI()
|
||||
setupListeners()
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
UIUtils.setupToolbar(this, R.drawable.logo, getString(R.string.notifications_title), true)
|
||||
adapter = NotificationsAdapter(imageLoader, this).also {
|
||||
binding.recyclerViewFeeds.adapter = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
viewModel.feeds.collectLatest {
|
||||
val feeds = it.values
|
||||
if (feeds.isNotEmpty()) {
|
||||
binding.recyclerViewFeeds.setViewVisible()
|
||||
binding.txtNoNotifications.setViewGone()
|
||||
} else {
|
||||
binding.recyclerViewFeeds.setViewGone()
|
||||
binding.txtNoNotifications.setViewVisible()
|
||||
}
|
||||
adapter.refreshFeeds(feeds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFeedUpdated(feed: Feed) {
|
||||
viewModel.updateFeed(this, feed)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package com.newsblur.activity
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.newsblur.databinding.ViewNotificationsItemBinding
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.util.FeedExt
|
||||
import com.newsblur.util.FeedExt.disableNotificationType
|
||||
import com.newsblur.util.FeedExt.enableNotificationType
|
||||
import com.newsblur.util.FeedExt.isNotifyAndroid
|
||||
import com.newsblur.util.FeedExt.isNotifyEmail
|
||||
import com.newsblur.util.FeedExt.isNotifyFocus
|
||||
import com.newsblur.util.FeedExt.isNotifyIOS
|
||||
import com.newsblur.util.FeedExt.isNotifyUnread
|
||||
import com.newsblur.util.FeedExt.isNotifyWeb
|
||||
import com.newsblur.util.FeedExt.setNotifyFocus
|
||||
import com.newsblur.util.FeedExt.setNotifyUnread
|
||||
import com.newsblur.util.ImageLoader
|
||||
|
||||
class NotificationsAdapter(
|
||||
private val imageLoader: ImageLoader,
|
||||
private val listener: Listener,
|
||||
) : RecyclerView.Adapter<NotificationsAdapter.ViewHolder>() {
|
||||
|
||||
private val feeds: MutableList<Feed> = mutableListOf()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = ViewNotificationsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding, imageLoader)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(feeds[position], listener)
|
||||
|
||||
override fun getItemCount(): Int = feeds.size
|
||||
|
||||
fun refreshFeeds(feeds: Collection<Feed>) {
|
||||
this.feeds.clear()
|
||||
this.feeds.addAll(feeds)
|
||||
this.notifyItemRangeInserted(0, feeds.size)
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: ViewNotificationsItemBinding, val imageLoader: ImageLoader) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(feed: Feed, listener: Listener) {
|
||||
binding.textTitle.text = feed.title
|
||||
imageLoader.displayImage(feed.faviconUrl, binding.imgIcon, binding.imgIcon.height, true)
|
||||
|
||||
with(binding.groupFilter) {
|
||||
if (feed.isNotifyUnread()) check(binding.btnUnread.id)
|
||||
else if (feed.isNotifyFocus()) check(binding.btnFocus.id)
|
||||
}
|
||||
|
||||
with(binding.groupPlatform) {
|
||||
if (feed.isNotifyEmail()) check(binding.btnEmail.id)
|
||||
if (feed.isNotifyWeb()) check(binding.btnWeb.id)
|
||||
if (feed.isNotifyIOS()) check(binding.btnIos.id)
|
||||
if (feed.isNotifyAndroid()) check(binding.btnAndroid.id)
|
||||
}
|
||||
|
||||
binding.groupFilter.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
updateFilter(feed, checkedId, isChecked)
|
||||
listener.onFeedUpdated(feed)
|
||||
}
|
||||
binding.groupPlatform.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
updatePlatform(feed, checkedId, isChecked)
|
||||
listener.onFeedUpdated(feed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFilter(feed: Feed, checkedBtnId: Int, isChecked: Boolean) {
|
||||
when (checkedBtnId) {
|
||||
binding.btnUnread.id -> if (isChecked) feed.setNotifyUnread()
|
||||
binding.btnFocus.id -> if (isChecked) feed.setNotifyFocus()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePlatform(feed: Feed, checkedBtnId: Int, isChecked: Boolean) {
|
||||
when (checkedBtnId) {
|
||||
binding.btnEmail.id -> FeedExt.NOTIFY_EMAIL
|
||||
binding.btnWeb.id -> FeedExt.NOTIFY_WEB
|
||||
binding.btnIos.id -> FeedExt.NOTIFY_IOS
|
||||
binding.btnIos.id -> FeedExt.NOTIFY_ANDROID
|
||||
else -> null
|
||||
}?.let {
|
||||
if (isChecked) feed.enableNotificationType(it)
|
||||
else feed.disableNotificationType(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onFeedUpdated(feed: Feed)
|
||||
}
|
||||
}
|
|
@ -800,6 +800,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, ScrollChangeListene
|
|||
companion object {
|
||||
const val EXTRA_FEEDSET = "feed_set"
|
||||
const val EXTRA_STORY_HASH = "story_hash"
|
||||
const val EXTRA_STORY = "story"
|
||||
private const val BUNDLE_STARTING_UNREAD = "starting_unread"
|
||||
|
||||
/** special value for starting story hash that jumps to the first unread. */
|
||||
|
|
|
@ -2,11 +2,22 @@ package com.newsblur.activity;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class SocialFeedItemsList extends ItemsList {
|
||||
|
||||
@Inject
|
||||
@IconLoader
|
||||
ImageLoader iconLoader;
|
||||
|
||||
public static final String EXTRA_SOCIAL_FEED = "social_feed";
|
||||
|
||||
private SocialFeed socialFeed;
|
||||
|
|
|
@ -8,8 +8,7 @@ import android.database.Cursor;
|
|||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.CancellationSignal;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -26,7 +25,6 @@ import com.newsblur.network.domain.CommentResponse;
|
|||
import com.newsblur.network.domain.StoriesResponse;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
import com.newsblur.util.ReadFilter;
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package com.newsblur.database;
|
||||
|
||||
import static com.newsblur.util.AppConstants.ALL_SHARED_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.ALL_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.GLOBAL_SHARED_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.INFREQUENT_SITE_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.READ_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.SAVED_SEARCHES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.SAVED_STORIES_GROUP_KEY;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -35,8 +43,10 @@ import com.newsblur.domain.Folder;
|
|||
import com.newsblur.domain.SavedSearch;
|
||||
import com.newsblur.domain.StarredCount;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedListOrder;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
|
@ -52,21 +62,6 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_SEARCHES, SAVED_STORIES }
|
||||
private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG, SAVED_SEARCH }
|
||||
|
||||
// The following keys are used to mark the position of the special meta-folders within
|
||||
// the folders array. Since the ExpandableListView doesn't handle collapsing of views
|
||||
// set to View.GONE, we have to totally remove any hidden groups from the group count
|
||||
// and adjust all folder indicies accordingly. Fake folders are created with these
|
||||
// very unlikely names and layout methods check against them before assuming a row is
|
||||
// a normal folder. All the string comparison is a small price to pay to avoid the
|
||||
// alternative of index-counting in a situation where some rows might be disabled.
|
||||
private static final String GLOBAL_SHARED_STORIES_GROUP_KEY = "GLOBAL_SHARED_STORIES_GROUP_KEY";
|
||||
private static final String ALL_SHARED_STORIES_GROUP_KEY = "ALL_SHARED_STORIES_GROUP_KEY";
|
||||
private static final String ALL_STORIES_GROUP_KEY = "ALL_STORIES_GROUP_KEY";
|
||||
private static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY";
|
||||
private static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY";
|
||||
private static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY";
|
||||
private static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY";
|
||||
|
||||
private final static float defaultTextSize_childName = 14;
|
||||
private final static float defaultTextSize_groupName = 13;
|
||||
private final static float defaultTextSize_count = 13;
|
||||
|
@ -982,4 +977,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
this.spacingStyle = spacingStyle;
|
||||
}
|
||||
|
||||
public SessionDataSource buildSessionDataSource(Session activeSession) {
|
||||
return new SessionDataSource(activeSession, activeFolderNames, activeFolderChildren);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -423,11 +423,11 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
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);
|
||||
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);
|
||||
return true;
|
||||
|
||||
case R.id.menu_send_story:
|
||||
|
@ -456,7 +456,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, null);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
package com.newsblur.delegate
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.core.view.isVisible
|
||||
import com.newsblur.R
|
||||
import com.newsblur.activity.ItemsList
|
||||
import com.newsblur.fragment.ItemSetFragment
|
||||
import com.newsblur.fragment.SaveSearchFragment
|
||||
import com.newsblur.service.NBSyncService
|
||||
import com.newsblur.util.*
|
||||
import com.newsblur.util.FeedUtils.Companion.triggerSync
|
||||
import com.newsblur.util.ListTextSize.Companion.fromSize
|
||||
import com.newsblur.util.PrefConstants.ThemeValue
|
||||
|
||||
interface ItemListContextMenuDelegate {
|
||||
fun onCreateMenuOptions(menu: Menu, menuInflater: MenuInflater, fs: FeedSet): Boolean
|
||||
|
||||
fun onPrepareMenuOptions(menu: Menu, fs: FeedSet, showSavedSearch: Boolean): Boolean
|
||||
|
||||
fun onOptionsItemSelected(item: MenuItem, fragment: ItemSetFragment, fs: FeedSet, searchInputView: EditText, saveSearchFeedId: String?): Boolean
|
||||
}
|
||||
|
||||
open class ItemListContextMenuDelegateImpl(
|
||||
private val activity: ItemsList,
|
||||
private val feedUtils: FeedUtils,
|
||||
) : ItemListContextMenuDelegate, ReadingActionListener by activity {
|
||||
|
||||
override fun onCreateMenuOptions(menu: Menu, menuInflater: MenuInflater, fs: FeedSet): Boolean {
|
||||
menuInflater.inflate(R.menu.itemslist, menu)
|
||||
|
||||
if (fs.isGlobalShared ||
|
||||
fs.isAllSocial ||
|
||||
fs.isFilterSaved ||
|
||||
fs.isAllSaved ||
|
||||
fs.isSingleSavedTag ||
|
||||
fs.isInfrequent ||
|
||||
fs.isAllRead) {
|
||||
menu.findItem(R.id.menu_mark_all_as_read).isVisible = false
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared ||
|
||||
fs.isAllSocial ||
|
||||
fs.isAllRead) {
|
||||
menu.findItem(R.id.menu_story_order).isVisible = false
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared ||
|
||||
fs.isFilterSaved ||
|
||||
fs.isAllSaved ||
|
||||
fs.isSingleSavedTag ||
|
||||
fs.isInfrequent ||
|
||||
fs.isAllRead) {
|
||||
menu.findItem(R.id.menu_read_filter).isVisible = false
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll).isVisible = false
|
||||
menu.findItem(R.id.menu_story_content_preview_style).isVisible = false
|
||||
menu.findItem(R.id.menu_story_thumbnail_style).isVisible = false
|
||||
}
|
||||
|
||||
if (fs.isGlobalShared ||
|
||||
fs.isAllSocial ||
|
||||
fs.isInfrequent ||
|
||||
fs.isAllRead) {
|
||||
menu.findItem(R.id.menu_search_stories).isVisible = false
|
||||
}
|
||||
|
||||
if (!fs.isSingleNormal || fs.isFilterSaved) {
|
||||
menu.findItem(R.id.menu_notifications).isVisible = false
|
||||
menu.findItem(R.id.menu_delete_feed).isVisible = false
|
||||
menu.findItem(R.id.menu_instafetch_feed).isVisible = false
|
||||
menu.findItem(R.id.menu_intel).isVisible = false
|
||||
menu.findItem(R.id.menu_rename_feed).isVisible = false
|
||||
menu.findItem(R.id.menu_statistics).isVisible = false
|
||||
}
|
||||
|
||||
if (!fs.isInfrequent) {
|
||||
menu.findItem(R.id.menu_infrequent_cutoff).isVisible = false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareMenuOptions(menu: Menu, fs: FeedSet, showSavedSearch: Boolean): Boolean {
|
||||
val storyOrder = PrefsUtils.getStoryOrder(activity, fs)
|
||||
if (storyOrder == StoryOrder.NEWEST) {
|
||||
menu.findItem(R.id.menu_story_order_newest).isChecked = true
|
||||
} else if (storyOrder == StoryOrder.OLDEST) {
|
||||
menu.findItem(R.id.menu_story_order_oldest).isChecked = true
|
||||
}
|
||||
|
||||
val readFilter = PrefsUtils.getReadFilter(activity, fs)
|
||||
if (readFilter == ReadFilter.ALL) {
|
||||
menu.findItem(R.id.menu_read_filter_all_stories).isChecked = true
|
||||
} else if (readFilter == ReadFilter.UNREAD) {
|
||||
menu.findItem(R.id.menu_read_filter_unread_only).isChecked = true
|
||||
}
|
||||
|
||||
when (PrefsUtils.getStoryListStyle(activity, fs)) {
|
||||
StoryListStyle.GRID_F -> menu.findItem(R.id.menu_list_style_grid_f).isChecked = true
|
||||
StoryListStyle.GRID_M -> menu.findItem(R.id.menu_list_style_grid_m).isChecked = true
|
||||
StoryListStyle.GRID_C -> menu.findItem(R.id.menu_list_style_grid_c).isChecked = true
|
||||
else -> menu.findItem(R.id.menu_list_style_list).isChecked = true
|
||||
}
|
||||
|
||||
when (PrefsUtils.getSelectedTheme(activity)) {
|
||||
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
||||
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
||||
ThemeValue.BLACK -> menu.findItem(R.id.menu_theme_black).isChecked = true
|
||||
ThemeValue.AUTO -> menu.findItem(R.id.menu_theme_auto).isChecked = true
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
if (showSavedSearch) {
|
||||
menu.findItem(R.id.menu_save_search).isVisible = true
|
||||
}
|
||||
|
||||
when (PrefsUtils.getStoryContentPreviewStyle(activity)) {
|
||||
StoryContentPreviewStyle.NONE -> menu.findItem(R.id.menu_story_content_preview_none).isChecked = true
|
||||
StoryContentPreviewStyle.SMALL -> menu.findItem(R.id.menu_story_content_preview_small).isChecked = true
|
||||
StoryContentPreviewStyle.MEDIUM -> menu.findItem(R.id.menu_story_content_preview_medium).isChecked = true
|
||||
StoryContentPreviewStyle.LARGE -> menu.findItem(R.id.menu_story_content_preview_large).isChecked = true
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
when (PrefsUtils.getThumbnailStyle(activity)) {
|
||||
ThumbnailStyle.LEFT_SMALL -> menu.findItem(R.id.menu_story_thumbnail_left_small).isChecked = true
|
||||
ThumbnailStyle.LEFT_LARGE -> menu.findItem(R.id.menu_story_thumbnail_left_large).isChecked = true
|
||||
ThumbnailStyle.RIGHT_SMALL -> menu.findItem(R.id.menu_story_thumbnail_right_small).isChecked = true
|
||||
ThumbnailStyle.RIGHT_LARGE -> menu.findItem(R.id.menu_story_thumbnail_right_large).isChecked = true
|
||||
ThumbnailStyle.OFF -> menu.findItem(R.id.menu_story_thumbnail_no_preview).isChecked = true
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
val spacingStyle = PrefsUtils.getSpacingStyle(activity)
|
||||
if (spacingStyle === SpacingStyle.COMFORTABLE) {
|
||||
menu.findItem(R.id.menu_spacing_comfortable).isChecked = true
|
||||
} else if (spacingStyle == SpacingStyle.COMPACT) {
|
||||
menu.findItem(R.id.menu_spacing_compact).isChecked = true
|
||||
}
|
||||
|
||||
when (fromSize(PrefsUtils.getListTextSize(activity))) {
|
||||
ListTextSize.XS -> menu.findItem(R.id.menu_text_size_xs).isChecked = true
|
||||
ListTextSize.S -> menu.findItem(R.id.menu_text_size_s).isChecked = true
|
||||
ListTextSize.M -> menu.findItem(R.id.menu_text_size_m).isChecked = true
|
||||
ListTextSize.L -> menu.findItem(R.id.menu_text_size_l).isChecked = true
|
||||
ListTextSize.XL -> menu.findItem(R.id.menu_text_size_xl).isChecked = true
|
||||
ListTextSize.XXL -> menu.findItem(R.id.menu_text_size_xxl).isChecked = true
|
||||
}
|
||||
|
||||
val isMarkReadOnScroll = PrefsUtils.isMarkReadOnFeedScroll(activity)
|
||||
if (isMarkReadOnScroll) {
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll_enabled).isChecked = true
|
||||
} else {
|
||||
menu.findItem(R.id.menu_mark_read_on_scroll_disabled).isChecked = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(
|
||||
item: MenuItem,
|
||||
fragment: ItemSetFragment,
|
||||
fs: FeedSet,
|
||||
searchInputView: EditText,
|
||||
saveSearchFeedId: String?,
|
||||
): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
activity.finish()
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_mark_all_as_read) {
|
||||
feedUtils.markRead(activity, fs, null, null, R.array.mark_all_read_options, this)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_story_order_newest) {
|
||||
updateStoryOrder(fragment, fs, StoryOrder.NEWEST)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_story_order_oldest) {
|
||||
updateStoryOrder(fragment, fs, StoryOrder.OLDEST)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_read_filter_all_stories) {
|
||||
updateReadFilter(fragment, fs, ReadFilter.ALL)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_read_filter_unread_only) {
|
||||
updateReadFilter(fragment, fs, ReadFilter.UNREAD)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_xs) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.XS)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_s) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.S)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_m) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.M)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_l) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.L)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_xl) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.XL)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_text_size_xxl) {
|
||||
updateTextSizeStyle(fragment, ListTextSize.XXL)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_search_stories) {
|
||||
if (!searchInputView.isVisible) {
|
||||
searchInputView.visibility = View.VISIBLE
|
||||
searchInputView.requestFocus()
|
||||
} else {
|
||||
searchInputView.text.clear()
|
||||
searchInputView.visibility = View.GONE
|
||||
}
|
||||
} else if (item.itemId == R.id.menu_theme_auto) {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.AUTO)
|
||||
UIUtils.restartActivity(activity)
|
||||
} else if (item.itemId == R.id.menu_theme_light) {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.LIGHT)
|
||||
UIUtils.restartActivity(activity)
|
||||
} else if (item.itemId == R.id.menu_theme_dark) {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.DARK)
|
||||
UIUtils.restartActivity(activity)
|
||||
} else if (item.itemId == R.id.menu_theme_black) {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.BLACK)
|
||||
UIUtils.restartActivity(activity)
|
||||
} else if (item.itemId == R.id.menu_spacing_comfortable) {
|
||||
updateSpacingStyle(fragment, SpacingStyle.COMFORTABLE)
|
||||
} else if (item.itemId == R.id.menu_spacing_compact) {
|
||||
updateSpacingStyle(fragment, SpacingStyle.COMPACT)
|
||||
} else if (item.itemId == R.id.menu_list_style_list) {
|
||||
PrefsUtils.updateStoryListStyle(activity, fs, StoryListStyle.LIST)
|
||||
fragment.updateListStyle()
|
||||
} else if (item.itemId == R.id.menu_list_style_grid_f) {
|
||||
PrefsUtils.updateStoryListStyle(activity, fs, StoryListStyle.GRID_F)
|
||||
fragment.updateListStyle()
|
||||
} else if (item.itemId == R.id.menu_list_style_grid_m) {
|
||||
PrefsUtils.updateStoryListStyle(activity, fs, StoryListStyle.GRID_M)
|
||||
fragment.updateListStyle()
|
||||
} else if (item.itemId == R.id.menu_list_style_grid_c) {
|
||||
PrefsUtils.updateStoryListStyle(activity, fs, StoryListStyle.GRID_C)
|
||||
fragment.updateListStyle()
|
||||
} else if (item.itemId == R.id.menu_save_search) {
|
||||
saveSearchFeedId?.let {
|
||||
val query: String = searchInputView.text.toString()
|
||||
val frag = SaveSearchFragment.newInstance(it, query)
|
||||
frag.show(activity.supportFragmentManager, SaveSearchFragment::class.java.name)
|
||||
}
|
||||
} else if (item.itemId == R.id.menu_story_content_preview_none) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(activity, StoryContentPreviewStyle.NONE)
|
||||
fragment.notifyContentPrefsChanged()
|
||||
} else if (item.itemId == R.id.menu_story_content_preview_small) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(activity, StoryContentPreviewStyle.SMALL)
|
||||
fragment.notifyContentPrefsChanged()
|
||||
} else if (item.itemId == R.id.menu_story_content_preview_medium) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(activity, StoryContentPreviewStyle.MEDIUM)
|
||||
fragment.notifyContentPrefsChanged()
|
||||
} else if (item.itemId == R.id.menu_story_content_preview_large) {
|
||||
PrefsUtils.setStoryContentPreviewStyle(activity, StoryContentPreviewStyle.LARGE)
|
||||
fragment.notifyContentPrefsChanged()
|
||||
} else if (item.itemId == R.id.menu_mark_read_on_scroll_disabled) {
|
||||
PrefsUtils.setMarkReadOnScroll(activity, false)
|
||||
} else if (item.itemId == R.id.menu_mark_read_on_scroll_enabled) {
|
||||
PrefsUtils.setMarkReadOnScroll(activity, true)
|
||||
} else if (item.itemId == R.id.menu_story_thumbnail_left_small) {
|
||||
PrefsUtils.setThumbnailStyle(activity, ThumbnailStyle.LEFT_SMALL)
|
||||
fragment.updateThumbnailStyle()
|
||||
} else if (item.itemId == R.id.menu_story_thumbnail_left_large) {
|
||||
PrefsUtils.setThumbnailStyle(activity, ThumbnailStyle.LEFT_LARGE)
|
||||
fragment.updateThumbnailStyle()
|
||||
} else if (item.itemId == R.id.menu_story_thumbnail_right_small) {
|
||||
PrefsUtils.setThumbnailStyle(activity, ThumbnailStyle.RIGHT_SMALL)
|
||||
fragment.updateThumbnailStyle()
|
||||
} else if (item.itemId == R.id.menu_story_thumbnail_right_large) {
|
||||
PrefsUtils.setThumbnailStyle(activity, ThumbnailStyle.RIGHT_LARGE)
|
||||
fragment.updateThumbnailStyle()
|
||||
} else if (item.itemId == R.id.menu_story_thumbnail_no_preview) {
|
||||
PrefsUtils.setThumbnailStyle(activity, ThumbnailStyle.OFF)
|
||||
fragment.updateThumbnailStyle()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun updateTextSizeStyle(fragment: ItemSetFragment, listTextSize: ListTextSize) {
|
||||
PrefsUtils.setListTextSize(activity, listTextSize.size)
|
||||
fragment.updateTextSize()
|
||||
}
|
||||
|
||||
private fun updateSpacingStyle(fragment: ItemSetFragment, spacingStyle: SpacingStyle) {
|
||||
PrefsUtils.setSpacingStyle(activity, spacingStyle)
|
||||
fragment.updateSpacingStyle()
|
||||
}
|
||||
|
||||
private fun updateStoryOrder(fragment: ItemSetFragment, fs: FeedSet, storyOrder: StoryOrder) {
|
||||
PrefsUtils.updateStoryOrder(activity, fs, storyOrder)
|
||||
restartReadingSession(fragment, fs)
|
||||
}
|
||||
|
||||
private fun updateReadFilter(fragment: ItemSetFragment, fs: FeedSet, readFilter: ReadFilter) {
|
||||
PrefsUtils.updateReadFilter(activity, fs, readFilter)
|
||||
restartReadingSession(fragment, fs)
|
||||
}
|
||||
|
||||
private fun restartReadingSession(fragment: ItemSetFragment, fs: FeedSet) {
|
||||
NBSyncService.resetFetchState(fs)
|
||||
feedUtils.prepareReadingSession(fs, true)
|
||||
triggerSync(activity)
|
||||
fragment.resetEmptyState()
|
||||
fragment.hasUpdated()
|
||||
fragment.scrollToTop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package com.newsblur.delegate
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.newsblur.R
|
||||
import com.newsblur.activity.*
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.fragment.FolderListFragment
|
||||
import com.newsblur.fragment.LoginAsDialogFragment
|
||||
import com.newsblur.fragment.LogoutDialogFragment
|
||||
import com.newsblur.service.NBSyncService
|
||||
import com.newsblur.util.ListTextSize
|
||||
import com.newsblur.util.ListTextSize.Companion.fromSize
|
||||
import com.newsblur.util.PrefConstants.ThemeValue
|
||||
import com.newsblur.util.PrefsUtils
|
||||
import com.newsblur.util.SpacingStyle
|
||||
import com.newsblur.util.UIUtils
|
||||
import com.newsblur.widget.WidgetUtils
|
||||
|
||||
interface MainContextMenuDelegate {
|
||||
|
||||
fun onMenuClick(anchor: View, listener: PopupMenu.OnMenuItemClickListener)
|
||||
|
||||
fun onMenuItemClick(item: MenuItem, fragment: FolderListFragment): Boolean
|
||||
}
|
||||
|
||||
class MainContextMenuDelegateImpl(
|
||||
private val activity: Main,
|
||||
private val dbHelper: BlurDatabaseHelper,
|
||||
) : MainContextMenuDelegate {
|
||||
|
||||
override fun onMenuClick(anchor: View, listener: PopupMenu.OnMenuItemClickListener) {
|
||||
val pm = PopupMenu(activity, anchor)
|
||||
val menu = pm.menu
|
||||
pm.menuInflater.inflate(R.menu.main, menu)
|
||||
|
||||
if (NBSyncService.isStaff == true) {
|
||||
menu.findItem(R.id.menu_loginas).isVisible = true
|
||||
}
|
||||
|
||||
when (PrefsUtils.getSelectedTheme(activity)) {
|
||||
ThemeValue.LIGHT -> menu.findItem(R.id.menu_theme_light).isChecked = true
|
||||
ThemeValue.DARK -> menu.findItem(R.id.menu_theme_dark).isChecked = true
|
||||
ThemeValue.BLACK -> menu.findItem(R.id.menu_theme_black).isChecked = true
|
||||
ThemeValue.AUTO -> menu.findItem(R.id.menu_theme_auto).isChecked = true
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
val spacingStyle = PrefsUtils.getSpacingStyle(activity)
|
||||
if (spacingStyle == SpacingStyle.COMFORTABLE) {
|
||||
menu.findItem(R.id.menu_spacing_comfortable).isChecked = true
|
||||
} else if (spacingStyle == SpacingStyle.COMPACT) {
|
||||
menu.findItem(R.id.menu_spacing_compact).isChecked = true
|
||||
}
|
||||
|
||||
when (fromSize(PrefsUtils.getListTextSize(activity))) {
|
||||
ListTextSize.XS -> menu.findItem(R.id.menu_text_size_xs).isChecked = true
|
||||
ListTextSize.S -> menu.findItem(R.id.menu_text_size_s).isChecked = true
|
||||
ListTextSize.M -> menu.findItem(R.id.menu_text_size_m).isChecked = true
|
||||
ListTextSize.L -> menu.findItem(R.id.menu_text_size_l).isChecked = true
|
||||
ListTextSize.XL -> menu.findItem(R.id.menu_text_size_xl).isChecked = true
|
||||
ListTextSize.XXL -> menu.findItem(R.id.menu_text_size_xxl).isChecked = true
|
||||
}
|
||||
|
||||
if (WidgetUtils.hasActiveAppWidgets(activity)) {
|
||||
menu.findItem(R.id.menu_widget).isVisible = true
|
||||
}
|
||||
|
||||
pm.setOnMenuItemClickListener(listener)
|
||||
pm.show()
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem, fragment: FolderListFragment): Boolean = when (item.itemId) {
|
||||
R.id.menu_logout -> {
|
||||
val newFragment: DialogFragment = LogoutDialogFragment()
|
||||
newFragment.show(activity.supportFragmentManager, "dialog")
|
||||
true
|
||||
}
|
||||
R.id.menu_settings -> {
|
||||
val settingsIntent = Intent(activity, Settings::class.java)
|
||||
activity.startActivity(settingsIntent)
|
||||
true
|
||||
}
|
||||
R.id.menu_widget -> {
|
||||
val widgetIntent = Intent(activity, WidgetConfig::class.java)
|
||||
activity.startActivity(widgetIntent)
|
||||
true
|
||||
}
|
||||
R.id.menu_feedback_email -> {
|
||||
PrefsUtils.sendLogEmail(activity, dbHelper)
|
||||
true
|
||||
}
|
||||
R.id.menu_feedback_post -> {
|
||||
try {
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = Uri.parse(PrefsUtils.createFeedbackLink(activity, dbHelper))
|
||||
activity.startActivity(i)
|
||||
} catch (e: Exception) {
|
||||
Log.wtf(this.javaClass.name, "device cannot even open URLs to report feedback")
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_xs -> {
|
||||
fragment.setListTextSize(ListTextSize.XS)
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_s -> {
|
||||
fragment.setListTextSize(ListTextSize.S)
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_m -> {
|
||||
fragment.setListTextSize(ListTextSize.M)
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_l -> {
|
||||
fragment.setListTextSize(ListTextSize.L)
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_xl -> {
|
||||
fragment.setListTextSize(ListTextSize.XL)
|
||||
true
|
||||
}
|
||||
R.id.menu_text_size_xxl -> {
|
||||
fragment.setListTextSize(ListTextSize.XXL)
|
||||
true
|
||||
}
|
||||
R.id.menu_spacing_comfortable -> {
|
||||
fragment.setSpacingStyle(SpacingStyle.COMFORTABLE)
|
||||
true
|
||||
}
|
||||
R.id.menu_spacing_compact -> {
|
||||
fragment.setSpacingStyle(SpacingStyle.COMPACT)
|
||||
true
|
||||
}
|
||||
R.id.menu_loginas -> {
|
||||
val newFragment: DialogFragment = LoginAsDialogFragment()
|
||||
newFragment.show(activity.supportFragmentManager, "dialog")
|
||||
true
|
||||
}
|
||||
R.id.menu_theme_auto -> {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.AUTO)
|
||||
UIUtils.restartActivity(activity)
|
||||
false
|
||||
}
|
||||
R.id.menu_theme_light -> {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.LIGHT)
|
||||
UIUtils.restartActivity(activity)
|
||||
false
|
||||
}
|
||||
R.id.menu_theme_dark -> {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.DARK)
|
||||
UIUtils.restartActivity(activity)
|
||||
false
|
||||
}
|
||||
R.id.menu_theme_black -> {
|
||||
PrefsUtils.setSelectedTheme(activity, ThemeValue.BLACK)
|
||||
UIUtils.restartActivity(activity)
|
||||
false
|
||||
}
|
||||
R.id.menu_premium_account -> {
|
||||
val intent = Intent(activity, Premium::class.java)
|
||||
activity.startActivity(intent)
|
||||
true
|
||||
}
|
||||
R.id.menu_mute_sites -> {
|
||||
val intent = Intent(activity, MuteConfig::class.java)
|
||||
activity.startActivity(intent)
|
||||
true
|
||||
}
|
||||
R.id.menu_import_export -> {
|
||||
val intent = Intent(activity, ImportExportActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
true
|
||||
}
|
||||
R.id.menu_notifications -> {
|
||||
val intent = Intent(activity, NotificationsActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
|
@ -10,10 +10,6 @@ annotation class StoryFileCache
|
|||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class IconFileCache
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ThumbnailFileCache
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class IconLoader
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
|||
import com.google.gson.annotations.SerializedName;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.util.FeedListOrder;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
||||
public class Feed implements Comparable<Feed>, Serializable {
|
||||
|
||||
|
@ -74,7 +75,6 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
@SerializedName("notification_types")
|
||||
public List<String> notificationTypes;
|
||||
|
||||
// NB: only stored if notificationTypes was set to include android
|
||||
@SerializedName("notification_filter")
|
||||
public String notificationFilter;
|
||||
|
||||
|
@ -102,9 +102,7 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
values.put(DatabaseConstants.FEED_TITLE, title);
|
||||
values.put(DatabaseConstants.FEED_UPDATED_SECONDS, lastUpdated);
|
||||
values.put(DatabaseConstants.FEED_NOTIFICATION_TYPES, DatabaseConstants.flattenStringList(notificationTypes));
|
||||
if (isNotifyAndroid()) {
|
||||
values.put(DatabaseConstants.FEED_NOTIFICATION_FILTER, notificationFilter);
|
||||
}
|
||||
values.put(DatabaseConstants.FEED_NOTIFICATION_FILTER, notificationFilter);
|
||||
values.put(DatabaseConstants.FEED_FETCH_PENDING, fetchPending);
|
||||
return values;
|
||||
}
|
||||
|
@ -156,7 +154,7 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
public boolean equals(Object o) {
|
||||
if (! (o instanceof Feed)) return false;
|
||||
Feed otherFeed = (Feed) o;
|
||||
return (TextUtils.equals(feedId, otherFeed.feedId));
|
||||
return (FeedUtils.textUtilsEquals(feedId, otherFeed.feedId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -168,47 +166,6 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
return title.compareToIgnoreCase(f.title);
|
||||
}
|
||||
|
||||
private boolean isNotifyAndroid() {
|
||||
if (notificationTypes == null) return false;
|
||||
for (String type : notificationTypes) {
|
||||
if (type.equals(NOTIFY_TYPE_ANDROID)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void enableAndroidNotifications(boolean enable) {
|
||||
if (notificationTypes == null) notificationTypes = new ArrayList<String>();
|
||||
if (enable && (!notificationTypes.contains(NOTIFY_TYPE_ANDROID))) {
|
||||
notificationTypes.add(NOTIFY_TYPE_ANDROID);
|
||||
}
|
||||
if (!enable) {
|
||||
notificationTypes.remove(NOTIFY_TYPE_ANDROID);
|
||||
notificationFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotifyUnread() {
|
||||
if (!isNotifyAndroid()) return false;
|
||||
return NOTIFY_FILTER_UNREAD.equals(notificationFilter);
|
||||
}
|
||||
|
||||
public boolean isNotifyFocus() {
|
||||
if (!isNotifyAndroid()) return false;
|
||||
return NOTIFY_FILTER_FOCUS.equals(notificationFilter);
|
||||
}
|
||||
|
||||
public void setNotifyUnread() {
|
||||
this.notificationFilter = NOTIFY_FILTER_UNREAD;
|
||||
}
|
||||
|
||||
public void setNotifyFocus() {
|
||||
this.notificationFilter = NOTIFY_FILTER_FOCUS;
|
||||
}
|
||||
|
||||
private static final String NOTIFY_TYPE_ANDROID = "android";
|
||||
public static final String NOTIFY_FILTER_UNREAD = "unread";
|
||||
public static final String NOTIFY_FILTER_FOCUS = "focus";
|
||||
|
||||
public static Comparator<Feed> getFeedListOrderComparator(FeedListOrder feedListOrder) {
|
||||
return (o1, o2) -> {
|
||||
if (feedListOrder == FeedListOrder.MOST_USED_AT_TOP) {
|
||||
|
@ -218,4 +175,7 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static String NOTIFY_FILTER_UNREAD = "unread";
|
||||
public static String NOTIFY_FILTER_FOCUS = "focus";
|
||||
}
|
||||
|
|
|
@ -18,4 +18,13 @@ data class FeedResult(
|
|||
|
||||
val faviconUrl: String
|
||||
get() = "${APIConstants.buildUrl(APIConstants.PATH_FEED_FAVICON_URL)}$id"
|
||||
|
||||
companion object {
|
||||
fun createFeedResultForUrl(url: String) = FeedResult(
|
||||
id = -1,
|
||||
tagline = "Add feed manually by URL",
|
||||
label = url,
|
||||
url = url
|
||||
)
|
||||
}
|
||||
}
|
|
@ -51,7 +51,10 @@ import com.newsblur.domain.Feed;
|
|||
import com.newsblur.domain.Folder;
|
||||
import com.newsblur.domain.SavedSearch;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedExt;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
@ -277,11 +280,11 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
} else {
|
||||
menu.removeItem(R.id.menu_mute_feed);
|
||||
}
|
||||
if (feed.isNotifyUnread()) {
|
||||
if (FeedExt.isAndroidNotifyUnread(feed)) {
|
||||
menu.findItem(R.id.menu_notifications_disable).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_unread).setChecked(true);
|
||||
menu.findItem(R.id.menu_notifications_focus).setChecked(false);
|
||||
} else if (feed.isNotifyFocus()) {
|
||||
} else if (FeedExt.isAndroidNotifyFocus(feed)) {
|
||||
menu.findItem(R.id.menu_notifications_disable).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_unread).setChecked(false);
|
||||
menu.findItem(R.id.menu_notifications_focus).setChecked(true);
|
||||
|
@ -391,7 +394,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);
|
||||
adapter.lastFeedViewedId = fs.getSingleFeed();
|
||||
adapter.lastFolderViewed = fs.getFolderName();
|
||||
}
|
||||
|
@ -436,7 +439,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
|
||||
@Override
|
||||
public boolean onGroupClick(ExpandableListView list, View group, int groupPosition, long id) {
|
||||
Intent i = null;
|
||||
if (adapter.isRowSavedSearches(groupPosition)) {
|
||||
// group not clickable
|
||||
return true;
|
||||
}
|
||||
|
||||
FeedSet fs = adapter.getGroup(groupPosition);
|
||||
Intent i;
|
||||
if (adapter.isRowAllStories(groupPosition)) {
|
||||
if (currentState == StateFilter.SAVED) {
|
||||
// the existence of this row in saved mode is something of a framework artifact and may
|
||||
|
@ -455,17 +464,15 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
i = new Intent(getActivity(), ReadStoriesItemsList.class);
|
||||
} else if (adapter.isRowSavedStories(groupPosition)) {
|
||||
i = new Intent(getActivity(), SavedStoriesItemsList.class);
|
||||
} else if (adapter.isRowSavedSearches(groupPosition)) {
|
||||
// group not clickable
|
||||
return true;
|
||||
} else {
|
||||
i = new Intent(getActivity(), FolderItemsList.class);
|
||||
String canonicalFolderName = adapter.getGroupFolderName(groupPosition);
|
||||
SessionDataSource sessionDataSource = getSessionData(fs, canonicalFolderName, null);
|
||||
i.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, canonicalFolderName);
|
||||
i.putExtra(ItemsList.EXTRA_SESSION_DATA, sessionDataSource);
|
||||
adapter.lastFeedViewedId = null;
|
||||
adapter.lastFolderViewed = canonicalFolderName;
|
||||
}
|
||||
FeedSet fs = adapter.getGroup(groupPosition);
|
||||
i.putExtra(ItemsList.EXTRA_FEED_SET, fs);
|
||||
startActivity(i);
|
||||
|
||||
|
@ -540,7 +547,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
|
||||
feedUtils.currentFolderName = folderName;
|
||||
}
|
||||
FeedItemsList.startActivity(getActivity(), fs, feed, folderName);
|
||||
SessionDataSource sessionDataSource = getSessionData(fs, folderName, feed);
|
||||
FeedItemsList.startActivity(getActivity(), fs, feed, folderName, sessionDataSource);
|
||||
adapter.lastFeedViewedId = feed.feedId;
|
||||
adapter.lastFolderViewed = null;
|
||||
}
|
||||
|
@ -618,4 +626,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SessionDataSource getSessionData(FeedSet fs, String folderName, @Nullable Feed feed) {
|
||||
if (PrefsUtils.loadNextOnMarkRead(requireContext())) {
|
||||
Session activeSession = new Session(fs, folderName, feed);
|
||||
return adapter.buildSessionDataSource(activeSession);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package com.newsblur.fragment;
|
||||
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
import com.newsblur.util.ReadingActionListener;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
@ -24,39 +27,40 @@ public class ReadingActionConfirmationFragment extends DialogFragment {
|
|||
private static final String DIALOG_TITLE = "dialog_title";
|
||||
private static final String DIALOG_MESSAGE = "dialog_message";
|
||||
private static final String DIALOG_CHOICES_RID = "dialog_choices_rid";
|
||||
private static final String FINISH_AFTER = "finish_after";
|
||||
|
||||
public static ReadingActionConfirmationFragment newInstance(ReadingAction ra, CharSequence title, CharSequence message, int choicesId, boolean finishAfter) {
|
||||
private static final String ACTION_CALLBACK = "action_callback";
|
||||
|
||||
public static ReadingActionConfirmationFragment newInstance(ReadingAction ra, CharSequence title, CharSequence message, int choicesId, @Nullable ReadingActionListener callback) {
|
||||
ReadingActionConfirmationFragment fragment = new ReadingActionConfirmationFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(READING_ACTION, ra);
|
||||
args.putCharSequence(DIALOG_TITLE, title);
|
||||
args.putCharSequence(DIALOG_MESSAGE, message);
|
||||
args.putInt(DIALOG_CHOICES_RID, choicesId);
|
||||
args.putBoolean(FINISH_AFTER, finishAfter);
|
||||
args.putSerializable(ACTION_CALLBACK, callback);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
|
||||
|
||||
final ReadingAction ra = (ReadingAction)getArguments().getSerializable(READING_ACTION);
|
||||
final ReadingAction ra = (ReadingAction) getArguments().getSerializable(READING_ACTION);
|
||||
CharSequence title = getArguments().getCharSequence(DIALOG_TITLE);
|
||||
CharSequence message = getArguments().getCharSequence(DIALOG_MESSAGE);
|
||||
int choicesId = getArguments().getInt(DIALOG_CHOICES_RID);
|
||||
final boolean finishAfter = getArguments().getBoolean(FINISH_AFTER);
|
||||
|
||||
@Nullable ReadingActionListener callback = (ReadingActionListener) getArguments().getSerializable(ACTION_CALLBACK);
|
||||
|
||||
builder.setTitle(title);
|
||||
// NB: setting a message will override the display of the buttons, making the dialogue a no-op
|
||||
if (message != null) builder.setMessage(message);
|
||||
builder.setItems(choicesId, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == 0) {
|
||||
feedUtils.doAction(ra, getActivity());
|
||||
if (finishAfter) {
|
||||
getActivity().finish();
|
||||
feedUtils.doAction(ra, requireContext());
|
||||
if (callback != null) {
|
||||
callback.onReadingActionCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -419,7 +419,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
|||
true
|
||||
}
|
||||
R.id.menu_go_to_feed -> {
|
||||
FeedItemsList.startActivity(context, fs, dbHelper.getFeed(story!!.feedId), null)
|
||||
FeedItemsList.startActivity(context, fs, dbHelper.getFeed(story!!.feedId), null, null)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -570,6 +570,7 @@ public class APIManager {
|
|||
return response.getResponse(gson, AddFeedResponse.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FeedResult[] searchForFeed(String searchTerm) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(APIConstants.PARAMETER_FEED_SEARCH_TERM, searchTerm);
|
||||
|
|
|
@ -95,4 +95,19 @@ public class AppConstants {
|
|||
// Free standard account sites limit
|
||||
public final static int FREE_ACCOUNT_SITE_LIMIT = 64;
|
||||
|
||||
// The following keys are used to mark the position of the special meta-folders within
|
||||
// the folders array. Since the ExpandableListView doesn't handle collapsing of views
|
||||
// set to View.GONE, we have to totally remove any hidden groups from the group count
|
||||
// and adjust all folder indicies accordingly. Fake folders are created with these
|
||||
// very unlikely names and layout methods check against them before assuming a row is
|
||||
// a normal folder. All the string comparison is a small price to pay to avoid the
|
||||
// alternative of index-counting in a situation where some rows might be disabled.
|
||||
public static final String GLOBAL_SHARED_STORIES_GROUP_KEY = "GLOBAL_SHARED_STORIES_GROUP_KEY";
|
||||
public static final String ALL_SHARED_STORIES_GROUP_KEY = "ALL_SHARED_STORIES_GROUP_KEY";
|
||||
public static final String ALL_STORIES_GROUP_KEY = "ALL_STORIES_GROUP_KEY";
|
||||
public static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY";
|
||||
public static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY";
|
||||
public static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY";
|
||||
public static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY";
|
||||
|
||||
}
|
||||
|
|
54
clients/android/NewsBlur/src/com/newsblur/util/FeedExt.kt
Normal file
54
clients/android/NewsBlur/src/com/newsblur/util/FeedExt.kt
Normal file
|
@ -0,0 +1,54 @@
|
|||
package com.newsblur.util
|
||||
|
||||
import com.newsblur.domain.Feed
|
||||
|
||||
object FeedExt {
|
||||
|
||||
fun Feed.isNotifyEmail(): Boolean = isNotify(NOTIFY_EMAIL)
|
||||
|
||||
fun Feed.isNotifyWeb(): Boolean = isNotify(NOTIFY_WEB)
|
||||
|
||||
fun Feed.isNotifyIOS(): Boolean = isNotify(NOTIFY_IOS)
|
||||
|
||||
fun Feed.isNotifyAndroid(): Boolean = isNotify(NOTIFY_ANDROID)
|
||||
|
||||
fun Feed.enableNotificationType(type: String) {
|
||||
if (notificationTypes == null) notificationTypes = mutableListOf()
|
||||
if (!notificationTypes.contains(type)) notificationTypes.add(type)
|
||||
}
|
||||
|
||||
fun Feed.disableNotificationType(type: String) {
|
||||
notificationTypes?.remove(type)
|
||||
}
|
||||
|
||||
fun Feed.disableNotification() {
|
||||
notificationFilter = null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Feed.isAndroidNotifyUnread(): Boolean = isNotifyUnread() && isNotifyAndroid()
|
||||
|
||||
@JvmStatic
|
||||
fun Feed.isAndroidNotifyFocus(): Boolean = isNotifyFocus() && isNotifyAndroid()
|
||||
|
||||
@JvmStatic
|
||||
fun Feed.isNotifyUnread(): Boolean = notificationFilter == Feed.NOTIFY_FILTER_UNREAD
|
||||
|
||||
@JvmStatic
|
||||
fun Feed.isNotifyFocus(): Boolean = notificationFilter == Feed.NOTIFY_FILTER_FOCUS
|
||||
|
||||
fun Feed.setNotifyFocus() {
|
||||
notificationFilter = Feed.NOTIFY_FILTER_FOCUS
|
||||
}
|
||||
|
||||
fun Feed.setNotifyUnread() {
|
||||
notificationFilter = Feed.NOTIFY_FILTER_UNREAD
|
||||
}
|
||||
|
||||
private fun Feed.isNotify(type: String): Boolean = notificationTypes?.contains(type) ?: false
|
||||
|
||||
const val NOTIFY_EMAIL = "email"
|
||||
const val NOTIFY_WEB = "web"
|
||||
const val NOTIFY_IOS = "ios"
|
||||
const val NOTIFY_ANDROID = "android"
|
||||
}
|
|
@ -330,8 +330,8 @@ public class FeedSet implements Serializable {
|
|||
if (!( o instanceof FeedSet)) return false;
|
||||
FeedSet s = (FeedSet) o;
|
||||
|
||||
if ( !TextUtils.equals(searchQuery, s.searchQuery)) return false;
|
||||
if ( !TextUtils.equals(folderName, s.folderName)) return false;
|
||||
if ( !FeedUtils.textUtilsEquals(searchQuery, s.searchQuery)) return false;
|
||||
if ( !FeedUtils.textUtilsEquals(folderName, s.folderName)) return false;
|
||||
if ( isFilterSaved != s.isFilterSaved ) return false;
|
||||
if ( (feeds != null) && (s.feeds != null) && s.feeds.equals(feeds) ) return true;
|
||||
if ( (socialFeeds != null) && (s.socialFeeds != null) && s.socialFeeds.equals(socialFeeds) ) return true;
|
||||
|
|
|
@ -7,17 +7,20 @@ import android.text.TextUtils
|
|||
import com.newsblur.R
|
||||
import com.newsblur.activity.NbActivity
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.domain.*
|
||||
import com.newsblur.domain.Classifier
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.domain.Story
|
||||
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.Companion.UPDATE_METADATA
|
||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
|
||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
|
||||
import com.newsblur.service.NBSyncService
|
||||
import com.newsblur.util.FeedExt.disableNotification
|
||||
import com.newsblur.util.FeedExt.setNotifyFocus
|
||||
import com.newsblur.util.FeedExt.setNotifyUnread
|
||||
import com.newsblur.util.UIUtils.syncUpdateStatus
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.*
|
||||
|
||||
class FeedUtils(
|
||||
private val dbHelper: BlurDatabaseHelper,
|
||||
|
@ -207,10 +210,13 @@ class FeedUtils(
|
|||
triggerSync(context)
|
||||
}
|
||||
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int) =
|
||||
markRead(activity, fs, olderThan, newerThan, choicesRid, null)
|
||||
|
||||
/**
|
||||
* Marks some or all of the stories in a FeedSet as read for an activity, handling confirmation dialogues as necessary.
|
||||
*/
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int, finishAfter: Boolean) {
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int, callback: ReadingActionListener?) {
|
||||
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.
|
||||
|
@ -249,9 +255,7 @@ class FeedUtils(
|
|||
}
|
||||
if (doImmediate) {
|
||||
doAction(ra, activity)
|
||||
if (finishAfter) {
|
||||
activity.finish()
|
||||
}
|
||||
callback?.onReadingActionCompleted()
|
||||
} else {
|
||||
val title: String? = when {
|
||||
fs.isAllNormal -> {
|
||||
|
@ -267,32 +271,29 @@ class FeedUtils(
|
|||
dbHelper.getFeed(fs.singleFeed)?.title ?: ""
|
||||
}
|
||||
}
|
||||
val dialog = ReadingActionConfirmationFragment.newInstance(ra, title, optionalOverrideMessage, choicesRid, finishAfter)
|
||||
val dialog = ReadingActionConfirmationFragment.newInstance(ra, title, optionalOverrideMessage, choicesRid, callback)
|
||||
dialog.show(activity.supportFragmentManager, "dialog")
|
||||
}
|
||||
}
|
||||
|
||||
fun disableNotifications(context: Context, feed: Feed) {
|
||||
updateFeedNotifications(context, feed, enable = false, focusOnly = false)
|
||||
feed.disableNotification()
|
||||
updateFeedNotifications(context, feed)
|
||||
}
|
||||
|
||||
fun enableUnreadNotifications(context: Context, feed: Feed) {
|
||||
updateFeedNotifications(context, feed, enable = true, focusOnly = false)
|
||||
feed.setNotifyUnread()
|
||||
updateFeedNotifications(context, feed)
|
||||
}
|
||||
|
||||
fun enableFocusNotifications(context: Context, feed: Feed) {
|
||||
updateFeedNotifications(context, feed, enable = true, focusOnly = true)
|
||||
feed.setNotifyFocus()
|
||||
updateFeedNotifications(context, feed)
|
||||
}
|
||||
|
||||
private fun updateFeedNotifications(context: Context, feed: Feed, enable: Boolean, focusOnly: Boolean) {
|
||||
fun updateFeedNotifications(context: Context, feed: Feed) {
|
||||
NBScope.executeAsyncTask(
|
||||
doInBackground = {
|
||||
if (focusOnly) {
|
||||
feed.setNotifyFocus()
|
||||
} else {
|
||||
feed.setNotifyUnread()
|
||||
}
|
||||
feed.enableAndroidNotifications(enable)
|
||||
dbHelper.updateFeed(feed)
|
||||
val ra = ReadingAction.setNotify(feed.feedId, feed.notificationTypes, feed.notificationFilter)
|
||||
doAction(ra, context)
|
||||
|
@ -508,5 +509,23 @@ class FeedUtils(
|
|||
val parts = TextUtils.split(storyHash, ":")
|
||||
return if (parts.size != 2) null else parts[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of TextUtils.equals because of Java for unit tests
|
||||
*/
|
||||
@JvmStatic
|
||||
fun textUtilsEquals(a: CharSequence?, b: CharSequence?): Boolean {
|
||||
if (a === b) return true
|
||||
return if (a != null && b != null && a.length == b.length) {
|
||||
if (a is String && b is String) {
|
||||
a == b
|
||||
} else {
|
||||
for (i in a.indices) {
|
||||
if (a[i] != b[i]) return false
|
||||
}
|
||||
true
|
||||
}
|
||||
} else false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@ public class NotificationUtils {
|
|||
markreadIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash);
|
||||
PendingIntent markreadPendingIntent = PendingIntentUtils.getImmutableBroadcast(context.getApplicationContext(), story.hashCode(), markreadIntent, 0);
|
||||
|
||||
Intent shareIntent = new Intent(context, NotifyShareReceiver.class);
|
||||
shareIntent.putExtra(Reading.EXTRA_STORY, story);
|
||||
PendingIntent sharePendingIntent = PendingIntentUtils.getImmutableBroadcast(context.getApplicationContext(), story.hashCode(), shareIntent, 0);
|
||||
|
||||
String feedTitle = cursor.getString(cursor.getColumnIndex(DatabaseConstants.FEED_TITLE));
|
||||
StringBuilder title = new StringBuilder();
|
||||
title.append(feedTitle).append(": ").append(story.title);
|
||||
|
@ -144,8 +148,9 @@ public class NotificationUtils {
|
|||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setWhen(story.timestamp)
|
||||
.addAction(0, "Save", savePendingIntent)
|
||||
.addAction(0, "Mark Read", markreadPendingIntent)
|
||||
.addAction(0, "Save", savePendingIntent)
|
||||
.addAction(0, "Share", sharePendingIntent)
|
||||
.setColor(NOTIFY_COLOUR);
|
||||
if (feedIcon != null) {
|
||||
nb.setLargeIcon(feedIcon);
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.newsblur.util
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.newsblur.activity.Reading
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.domain.Story
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotifyShareReceiver : BroadcastReceiver() {
|
||||
|
||||
@Inject
|
||||
lateinit var feedUtils: FeedUtils
|
||||
|
||||
@Inject
|
||||
lateinit var dbHelper: BlurDatabaseHelper
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val story = intent.getSerializableExtra(Reading.EXTRA_STORY) as? Story?
|
||||
NotificationUtils.cancel(context, story?.storyHash.hashCode())
|
||||
story?.let {
|
||||
NBScope.executeAsyncTask(
|
||||
doInBackground = {
|
||||
dbHelper.putStoryDismissed(it.storyHash)
|
||||
feedUtils.shareStory(it, "", it.sourceUserId, context)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -123,4 +123,5 @@ public class PrefConstants {
|
|||
public static final String FEED_CHOOSER_FOLDER_VIEW = "feed_chooser_folder_view";
|
||||
public static final String WIDGET_BACKGROUND = "widget_background";
|
||||
public static final String IN_APP_REVIEW = "in_app_review";
|
||||
public static final String LOAD_NEXT_ON_MARK_READ = "load_next_on_mark_read";
|
||||
}
|
||||
|
|
|
@ -1064,4 +1064,9 @@ public class PrefsUtils {
|
|||
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return MarkStoryReadBehavior.valueOf(preferences.getString(PrefConstants.STORY_MARK_READ_BEHAVIOR, MarkStoryReadBehavior.IMMEDIATELY.name()));
|
||||
}
|
||||
|
||||
public static boolean loadNextOnMarkRead(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return prefs.getBoolean(PrefConstants.LOAD_NEXT_ON_MARK_READ, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package com.newsblur.util
|
||||
|
||||
import com.newsblur.domain.Feed
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* @return Set of folder keys that don't support
|
||||
* mark all read action
|
||||
*/
|
||||
private val invalidMarkAllReadFolderKeys by lazy {
|
||||
setOf(
|
||||
AppConstants.GLOBAL_SHARED_STORIES_GROUP_KEY,
|
||||
AppConstants.ALL_SHARED_STORIES_GROUP_KEY,
|
||||
AppConstants.INFREQUENT_SITE_STORIES_GROUP_KEY,
|
||||
AppConstants.READ_STORIES_GROUP_KEY,
|
||||
AppConstants.SAVED_STORIES_GROUP_KEY,
|
||||
AppConstants.SAVED_SEARCHES_GROUP_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* As of writing this function, the zipping of the two sources
|
||||
* is valid as the "activeFolderNames" and "activeFolderChildren"
|
||||
* can be mapped by their folder name.
|
||||
* @return Map of folder names to their feed list.
|
||||
*/
|
||||
private fun List<String>.zipFolderFeed(foldersChildren: List<List<Feed>>): Map<String, List<Feed>> {
|
||||
val first = this.iterator()
|
||||
val second = foldersChildren.iterator()
|
||||
return buildMap {
|
||||
while (first.hasNext() && second.hasNext()) {
|
||||
this[first.next()] = second.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Feed.toFeedSet() = FeedSet.singleFeed(this.feedId).apply {
|
||||
isMuted = !this@toFeedSet.active
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the user's current reading session data source
|
||||
* as constructed and filtered by the home list adapter
|
||||
* based on settings and preferences.
|
||||
*/
|
||||
class SessionDataSource private constructor(
|
||||
private val folders: List<String>,
|
||||
private val foldersChildrenMap: Map<String, List<Feed>>
|
||||
) : Serializable {
|
||||
|
||||
private lateinit var session: Session
|
||||
|
||||
constructor(
|
||||
activeSession: Session,
|
||||
folders: List<String>,
|
||||
foldersChildren: List<List<Feed>>,
|
||||
) : this(
|
||||
folders = folders.filterNot { invalidMarkAllReadFolderKeys.contains(it) },
|
||||
foldersChildrenMap = folders.zipFolderFeed(foldersChildren)
|
||||
.filterNot { invalidMarkAllReadFolderKeys.contains(it.key) },
|
||||
) {
|
||||
this.session = activeSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next feed within a folder or null if the folder
|
||||
* is showing the last feed.
|
||||
*/
|
||||
private fun getNextFolderFeed(feed: Feed, folderName: String): Feed? {
|
||||
val cleanFolderName =
|
||||
// ROOT FOLDER maps to ALL_STORIES_GROUP_KEY
|
||||
if (folderName == AppConstants.ROOT_FOLDER)
|
||||
AppConstants.ALL_STORIES_GROUP_KEY
|
||||
else folderName
|
||||
val folderFeeds = foldersChildrenMap[cleanFolderName]
|
||||
return folderFeeds?.let { feeds ->
|
||||
val feedIndex = feeds.indexOf(feed)
|
||||
if (feedIndex == -1) return null // invalid feed
|
||||
|
||||
val nextFeedIndex = when (feedIndex) {
|
||||
feeds.size - 1 -> null // null feed if EOL
|
||||
in feeds.indices -> feedIndex + 1 // next feed
|
||||
else -> null // no valid feed found
|
||||
}
|
||||
|
||||
nextFeedIndex?.let { feeds[it] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next non empty folder and its feeds based on the given folder name.
|
||||
* If the next folder doesn't have feeds, it will call itself with the new folder name
|
||||
* until it finds a non empty folder or it will get to the end of the folder list.
|
||||
*/
|
||||
private fun getNextNonEmptyFolder(folderName: String): Pair<String, List<Feed>>? = with(folders.indexOf(folderName)) {
|
||||
val nextIndex = if (this == folders.size - 1) {
|
||||
0 // first folder if EOL
|
||||
} else if (this in folders.indices) {
|
||||
this + 1 // next folder
|
||||
} else this // no folder found
|
||||
|
||||
val nextFolderName = if (nextIndex in folders.indices) {
|
||||
folders[nextIndex]
|
||||
} else null
|
||||
|
||||
if (nextFolderName == null || nextFolderName == folderName)
|
||||
return null
|
||||
|
||||
val feeds = foldersChildrenMap[nextFolderName]
|
||||
if (feeds == null || feeds.isEmpty())
|
||||
// try and get the next non empty folder name
|
||||
getNextNonEmptyFolder(nextFolderName)
|
||||
else nextFolderName to feeds
|
||||
}
|
||||
|
||||
fun getNextSession(): Session? = if (session.feedSet.isFolder) {
|
||||
val folderName = session.feedSet.folderName
|
||||
getNextNonEmptyFolder(folderName)?.let { (nextFolderName, nextFolderFeeds) ->
|
||||
val nextFeedSet = FeedSet.folder(nextFolderName, nextFolderFeeds.map { it.feedId }.toSet())
|
||||
Session(feedSet = nextFeedSet, folderName = nextFolderName).also { nextSession ->
|
||||
session = nextSession
|
||||
}
|
||||
}
|
||||
} else if (session.feed != null && session.folderName != null) {
|
||||
val nextFeed = getNextFolderFeed(feed = session.feed!!, folderName = session.folderName!!)
|
||||
nextFeed?.let {
|
||||
Session(feedSet = it.toFeedSet(), session.folderName, it).also { nextSession ->
|
||||
session = nextSession
|
||||
}
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the user's current reading session.
|
||||
*
|
||||
* When reading a folder, [folderName] and [feed] will be null.
|
||||
*
|
||||
* When reading a feed, [folderName] and [feed] will be non null.
|
||||
*/
|
||||
data class Session(
|
||||
val feedSet: FeedSet,
|
||||
val folderName: String? = null,
|
||||
val feed: Feed? = null,
|
||||
) : Serializable
|
||||
|
||||
interface ReadingActionListener : Serializable {
|
||||
fun onReadingActionCompleted()
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.newsblur.viewModel
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.newsblur.util.Session
|
||||
|
||||
class ItemListViewModel : ViewModel() {
|
||||
|
||||
private val _nextSession = MutableLiveData<Session>()
|
||||
val nextSession: LiveData<Session> = _nextSession
|
||||
|
||||
fun updateSession(session: Session) {
|
||||
_nextSession.value = session
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package com.newsblur.viewModel
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.os.CancellationSignal
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.newsblur.database.BlurDatabaseHelper
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.util.FeedUtils
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NotificationsViewModel
|
||||
@Inject constructor(
|
||||
private val dbHelper: BlurDatabaseHelper,
|
||||
private val feedUtils: FeedUtils,
|
||||
) : ViewModel() {
|
||||
|
||||
private val cancellationSignal = CancellationSignal()
|
||||
|
||||
private val _feeds = MutableStateFlow<Map<String, Feed>>(emptyMap())
|
||||
val feeds: StateFlow<Map<String, Feed>> = _feeds.asStateFlow()
|
||||
|
||||
init {
|
||||
loadFeeds()
|
||||
}
|
||||
|
||||
fun updateFeed(context: Context, feed: Feed) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
feedUtils.updateFeedNotifications(context, feed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFeeds() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val cursor = dbHelper.getFeedsCursor(cancellationSignal)
|
||||
val feeds = extractFeeds(cursor).filterValues(notificationFeedFilter)
|
||||
_feeds.emit(feeds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractFeeds(cursor: Cursor): Map<String, Feed> = buildMap {
|
||||
if (!cursor.isBeforeFirst) return@buildMap
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val feed = Feed.fromCursor(cursor)
|
||||
this[feed.feedId] = feed
|
||||
}
|
||||
}
|
||||
|
||||
private val notificationFeedFilter: (Feed) -> Boolean = {
|
||||
it.active && !it.notificationFilter.isNullOrBlank()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
cancellationSignal.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package com.newsblur
|
||||
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.util.FeedSet
|
||||
import com.newsblur.util.Session
|
||||
import com.newsblur.util.SessionDataSource
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class SessionDataSourceTest {
|
||||
|
||||
private val folders = listOf(
|
||||
"F1",
|
||||
"F2",
|
||||
"F3",
|
||||
"F4",
|
||||
"F5",
|
||||
)
|
||||
|
||||
private val folderChildren = listOf(
|
||||
emptyList(),
|
||||
listOf(
|
||||
createFeed("20"),
|
||||
createFeed("21"),
|
||||
createFeed("22"),
|
||||
),
|
||||
listOf(
|
||||
createFeed("30"),
|
||||
),
|
||||
emptyList(),
|
||||
listOf(
|
||||
createFeed("50"),
|
||||
createFeed("51"),
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `session constructor test`() {
|
||||
val feedSet = FeedSet.singleFeed("1")
|
||||
val session = Session(feedSet)
|
||||
Assert.assertEquals(feedSet, session.feedSet)
|
||||
Assert.assertNull(session.feed)
|
||||
Assert.assertNull(session.folderName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `session full constructor test`() {
|
||||
val feedSet = FeedSet.singleFeed("10")
|
||||
val feed = createFeed("10")
|
||||
val session = Session(feedSet, "folderName", feed)
|
||||
Assert.assertEquals(feedSet, session.feedSet)
|
||||
Assert.assertEquals("folderName", session.folderName)
|
||||
Assert.assertEquals(feed, session.feed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for unknown feedId`() {
|
||||
val session = Session(FeedSet.singleFeed("123"))
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for empty folder`() {
|
||||
val feedSet = FeedSet.singleFeed("123")
|
||||
val feed = createFeed("123")
|
||||
val session = Session(feedSet, "F1", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return the next [Session] containing feed id 11
|
||||
* as the second feed in folder F2 after feed id 10
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F2 feedSet`() {
|
||||
val feedSet = FeedSet.singleFeed("20")
|
||||
val feed = createFeed("20")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertEquals("F2", it.folderName)
|
||||
Assert.assertEquals("21", it.feed?.feedId)
|
||||
with(it.feedSet) {
|
||||
Assert.assertNotNull(this)
|
||||
Assert.assertTrue(it.feedSet.flatFeedIds.size == 1)
|
||||
Assert.assertEquals("21", it.feedSet.flatFeedIds.first())
|
||||
}
|
||||
} ?: Assert.fail("Next session was null")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return a null [Session] because feed id 12
|
||||
* is the last feed id in folder F2
|
||||
*/
|
||||
@Test
|
||||
fun `next session for end of F2 feedSet`() {
|
||||
val feedSet = FeedSet.singleFeed("22")
|
||||
val feed = createFeed("22")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for F2 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F2", setOf("21"))
|
||||
val feed = createFeed("21")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F3", it.folderName)
|
||||
Assert.assertEquals("F3", it.feedSet.folderName)
|
||||
Assert.assertEquals("30", it.feedSet.flatFeedIds.firstOrNull())
|
||||
} ?: Assert.fail("Next session is null for F2 feedSetFolder")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return folder "F5" because folder "F3"
|
||||
* doesn't have any feeds
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F3 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F3", setOf("30"))
|
||||
val feed = createFeed("30")
|
||||
val session = Session(feedSet, "F3", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F5", it.folderName)
|
||||
Assert.assertEquals("F5", it.feedSet.folderName)
|
||||
Assert.assertEquals("50", it.feedSet.flatFeedIds.firstOrNull())
|
||||
} ?: Assert.fail("Next session is null for F5 feedSetFolder")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return session for F1 feedSetFolder
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F5 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F5", setOf("50"))
|
||||
val feed = createFeed("50")
|
||||
val session = Session(feedSet, "F5", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F2", it.folderName)
|
||||
Assert.assertEquals("F2", it.feedSet.folderName)
|
||||
Assert.assertEquals(setOf("20", "21", "22"), it.feedSet.flatFeedIds)
|
||||
} ?: Assert.fail("Next session is null for F5 feedSetFolder")
|
||||
}
|
||||
|
||||
private fun createFeed(id: String) = Feed().apply {
|
||||
feedId = id
|
||||
title = "Feed #$id"
|
||||
}
|
||||
}
|
|
@ -683,13 +683,14 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
activityButton.accessibilityLabel = @"Activities";
|
||||
[activityButton setImage:activityImage forState:UIControlStateNormal];
|
||||
activityButton.tintColor = UIColorFromRGB(0x8F918B);
|
||||
[activityButton setImageEdgeInsets:UIEdgeInsetsMake(4, 12, 4, -12)];
|
||||
[activityButton setImageEdgeInsets:UIEdgeInsetsMake(4, 0, 4, 0)];
|
||||
[activityButton addTarget:self
|
||||
action:@selector(showInteractionsPopover:)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
activitiesButton = [[UIBarButtonItem alloc]
|
||||
initWithCustomView:activityButton];
|
||||
activitiesButton.width = 32;
|
||||
// activityButton.backgroundColor = UIColor.redColor;
|
||||
self.navigationItem.rightBarButtonItem = activitiesButton;
|
||||
|
||||
NSMutableDictionary *sortedFolders = [[NSMutableDictionary alloc] init];
|
||||
|
@ -2765,7 +2766,7 @@ heightForHeaderInSection:(NSInteger)section {
|
|||
userAvatarButton.pointerInteractionEnabled = YES;
|
||||
userAvatarButton.accessibilityLabel = @"User info";
|
||||
userAvatarButton.accessibilityHint = @"Double-tap for information about your account.";
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 10, 0);
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(0, -10, 10, 0);
|
||||
userAvatarButton.contentEdgeInsets = insets;
|
||||
|
||||
NSMutableURLRequest *avatarRequest = [NSMutableURLRequest requestWithURL:imageURL];
|
||||
|
@ -2785,7 +2786,7 @@ heightForHeaderInSection:(NSInteger)section {
|
|||
|
||||
[userInfoView addSubview:userAvatarButton];
|
||||
|
||||
userLabel = [[UILabel alloc] initWithFrame:CGRectMake(54, yOffset, userInfoView.frame.size.width, 16)];
|
||||
userLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, yOffset, userInfoView.frame.size.width, 16)];
|
||||
userLabel.text = appDelegate.activeUsername;
|
||||
userLabel.font = userLabelFont;
|
||||
userLabel.textColor = UIColorFromRGB(0x404040);
|
||||
|
@ -2817,6 +2818,8 @@ heightForHeaderInSection:(NSInteger)section {
|
|||
|
||||
[userInfoView sizeToFit];
|
||||
|
||||
// userInfoView.backgroundColor = UIColor.blueColor;
|
||||
|
||||
self.navigationItem.titleView = userInfoView;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,24 +2,15 @@
|
|||
<svg width="150px" height="150px" viewBox="0 0 150 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>dialog-notifications</title>
|
||||
<defs>
|
||||
<polygon id="path-1" points="0 0 90 0 90 150 0 150"></polygon>
|
||||
<polygon id="path-3" points="90 0 0 84.8333333 39.0265487 84.8333333 17.7212389 150 90 72 51.7699115 72"></polygon>
|
||||
<polygon id="path-1" points="76 0 0 84.8333333 32.9557522 84.8333333 14.9646018 150 76 72 43.7168142 72"></polygon>
|
||||
</defs>
|
||||
<g id="dialog-notifications" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="noun-lightning-bolt-77533-95968E" transform="translate(23.000000, 0.000000)">
|
||||
<g id="noun-lightning-bolt-77533-95968E" transform="translate(37.000000, 0.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Path"></g>
|
||||
<g id="Clipped" mask="url(#mask-2)">
|
||||
<g transform="translate(0.000000, 0.000000)">
|
||||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<g id="Path" stroke="none" fill="none"></g>
|
||||
<polygon id="Path" stroke="none" fill="#95958E" fill-rule="evenodd" mask="url(#mask-4)" points="-3.78318584 -3.00027778 93.5840708 -3.00027778 93.5840708 153.166389 -3.78318584 153.166389"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
<polygon id="Path" fill="#95958E" mask="url(#mask-2)" points="-3.19469027 -3.00027778 79.0265487 -3.00027778 79.0265487 153.166389 -3.19469027 153.166389"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 914 B |
Binary file not shown.
Loading…
Add table
Reference in a new issue