Merge branch 'master' into pro

* master: (28 commits)
  Bumping up node images size.
  Android v11.3.0.
  #1600 Mark story as read button width
  Cleaning up preferences on android
  Merge master into sictiru
  #1600 Mark story read timer WIP
  #1600 Mark story read immediately or manually.
  #1634 Update infrequent logo
  #1606 Adjust story title length based on story content preview style.
  #1628 Handle feed missing metadata.
  Merge master into sictiru
  #1618 Handle multi window mode lifecycle.
  #1624 Offer context before potentially logging out.
  Android v11.2.2.
  #1598 Add immutable flag for PendingIntents
  Android v11.2.1.
  Fix reading pager fragment transaction.
  Open billing connection for the subscription sync service.
  Android v11.2
  Finishing up PostgreSQL migration. Needs to test backups.
  ...
This commit is contained in:
Samuel Clay 2022-03-09 17:32:07 -05:00
commit d231499672
32 changed files with 364 additions and 165 deletions

View file

@ -15,6 +15,7 @@ groups:
node: inventory_hostname.startswith('node') node: inventory_hostname.startswith('node')
node_socket: inventory_hostname.startswith('node-socket') node_socket: inventory_hostname.startswith('node-socket')
node_images: inventory_hostname.startswith('node-images')
# debugs: inventory_hostname.startswith('debug') # debugs: inventory_hostname.startswith('debug')

View file

@ -27,7 +27,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
dependencies { dependencies {
implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.squareup.okhttp3:okhttp:4.9.2'
@ -40,7 +40,7 @@ dependencies {
implementation "androidx.browser:browser:1.4.0" implementation "androidx.browser:browser:1.4.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation 'androidx.lifecycle:lifecycle-process:2.4.0' implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' implementation 'androidx.core:core-splashscreen:1.0.0-beta01'
} }
android { android {
@ -49,8 +49,8 @@ android {
applicationId "com.newsblur" applicationId "com.newsblur"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 199 versionCode 202
versionName "11.2" versionName "11.3.0"
} }
compileOptions.with { compileOptions.with {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8

View file

@ -211,7 +211,7 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:scrollbars="none" /> android:scrollbars="none" />
<include layout="@layout/include_reading_item_comment" /> <include layout="@layout/reading_item_actions" />
</LinearLayout> </LinearLayout>

View file

@ -2,26 +2,39 @@
<merge xmlns:app="http://schemas.android.com/apk/res-auto" <merge xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:layout_marginBottom="15dp" android:layout_marginBottom="15dp"
android:baselineAligned="false" > android:gravity="center_horizontal"
android:layout_gravity="center_horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/mark_read_story_button"
style="?storyButtons"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_alignStart="@+id/train_story_button"
android:layout_alignEnd="@+id/save_story_button"
android:text="@string/story_mark_read_state"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/train_story_button" android:id="@+id/train_story_button"
style="?storyButtons" style="?storyButtons"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="44dp" android:layout_height="44dp"
android:layout_below="@+id/mark_read_story_button"
android:text="@string/train_this" android:text="@string/train_this"
app:icon="@drawable/ic_train"/> app:icon="@drawable/ic_train" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/share_story_button" android:id="@+id/share_story_button"
style="?storyButtons" style="?storyButtons"
android:layout_marginHorizontal="12dp"
android:layout_toEndOf="@+id/train_story_button"
android:layout_below="@+id/mark_read_story_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="44dp" android:layout_height="44dp"
android:text="@string/share_this" android:text="@string/share_this"
@ -30,12 +43,14 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/save_story_button" android:id="@+id/save_story_button"
style="?storyButtons" style="?storyButtons"
android:layout_toEndOf="@+id/share_story_button"
android:layout_below="@+id/mark_read_story_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="44dp" android:layout_height="44dp"
android:text="@string/save_this" android:text="@string/save_this"
app:icon="@drawable/ic_clock" /> app:icon="@drawable/ic_clock" />
</LinearLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -109,7 +109,7 @@
android:paddingTop="6dp" android:paddingTop="6dp"
android:paddingBottom="6dp" android:paddingBottom="6dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:maxLines="2" android:maxLines="3"
android:ellipsize="end" /> android:ellipsize="end" />
<TextView <TextView

View file

@ -95,7 +95,7 @@
android:layout_marginLeft="26dp" android:layout_marginLeft="26dp"
android:layout_marginRight="2dp" android:layout_marginRight="2dp"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
android:maxLines="2" android:maxLines="3"
android:ellipsize="end" android:ellipsize="end"
/> />

View file

@ -18,6 +18,7 @@
<attr name="chip" format="string" /> <attr name="chip" format="string" />
<attr name="actionButtons" format="string" /> <attr name="actionButtons" format="string" />
<attr name="storyButtons" format="string" /> <attr name="storyButtons" format="string" />
<attr name="storyButtonsDimmed" format="string" />
<attr name="shareBarBackground" format="string" /> <attr name="shareBarBackground" format="string" />
<attr name="shareBarText" format="string" /> <attr name="shareBarText" format="string" />
<attr name="commentsHeader" format="string" /> <attr name="commentsHeader" format="string" />

View file

@ -5,26 +5,26 @@
<string name="login_username_hint">username</string> <string name="login_username_hint">username</string>
<string name="login_password_hint">password</string> <string name="login_password_hint">password</string>
<string name="login_registration_email_hint">email address</string> <string name="login_registration_email_hint">email address</string>
<string name="login_button_login">Log In</string> <string name="login_button_login">Login</string>
<string name="login_forgot_password"><u>I forgot my password</u></string> <string name="login_forgot_password"><u>Forgot password</u></string>
<string name="login_custom_server"><u>I have a custom server</u></string> <string name="login_custom_server"><u>Self-hosted custom server</u></string>
<string name="login_custom_server_hint">https://www.newsblur.com</string> <string name="login_custom_server_hint">https://www.newsblur.com</string>
<string name="login_message_error">There was problem logging in to NewsBlur.</string> <string name="login_message_error">There was problem logging in to NewsBlur.</string>
<string name="login_logging_in">Logging in…</string> <string name="login_logging_in">Logging in…</string>
<string name="login_logged_in">Logged in!</string> <string name="login_logged_in">Reticulating splines…</string>
<string name="login_retrieving_feeds">Retrieving feeds…</string> <string name="login_retrieving_feeds">Retrieving feeds…</string>
<string name="login_next">Next</string> <string name="login_next">Next</string>
<string name="login_custom_server_scheme_error">Expected \'https://\' custom server URL</string> <string name="login_custom_server_scheme_error">Expected \'https://\' in custom server URL</string>
<string name="search_for_feed">Search for feeds</string> <string name="search_for_feed">Search for feeds</string>
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
<string name="orig_text_loading">Fetching story text…</string> <string name="orig_text_loading">Fetching story text…</string>
<string name="follow_error">There was a problem following the user. Check your internet connection and try again.</string> <string name="follow_error">There was a problem following the user.</string>
<string name="unfollow_error">There was a problem unfollowing the user. Check your internet connection and try again.</string> <string name="unfollow_error">There was a problem unfollowing the user.</string>
<string name="description_login_button">Press to log in.</string> <string name="description_login_button">Press to log in</string>
<string name="description_login_logo">NewsBlur Logo</string> <string name="description_login_logo">NewsBlur Logo</string>
<string name="description_profile_picture">The user\'s profile picture</string> <string name="description_profile_picture">The user\'s profile picture</string>
<string name="description_row_folder_icon">folder icon</string> <string name="description_row_folder_icon">folder icon</string>
@ -36,9 +36,9 @@
<string name="description_empty_list_image">Empty list placeholder</string> <string name="description_empty_list_image">Empty list placeholder</string>
<string name="description_menu">Menu</string> <string name="description_menu">Menu</string>
<string name="title_choose_folders">Choose Folders for Feed %s</string> <string name="title_choose_folders">Choose folders for feed %s</string>
<string name="title_rename_feed">Rename Feed %s</string> <string name="title_rename_feed">Rename feed %s</string>
<string name="title_rename_folder">Rename Folder %s</string> <string name="title_rename_folder">Rename folder %s</string>
<string name="all_stories_title">All Stories</string> <string name="all_stories_title">All Stories</string>
<string name="infrequent_title">Infrequent Site Stories</string> <string name="infrequent_title">Infrequent Site Stories</string>
@ -50,7 +50,7 @@
<string name="top_level">TOP LEVEL</string> <string name="top_level">TOP LEVEL</string>
<string name="send_full">%1$s \n\%2$s \n\n%3$s \n\n—\nShared with NewsBlur.com</string> <string name="send_full">%1$s \n\%2$s \n\n%3$s \n\n—\nShared with NewsBlur</string>
<string name="share_comment_hint">Comment (Optional)</string> <string name="share_comment_hint">Comment (Optional)</string>
<string name="share_newsblur">Share \"%s\" to your Blurblog?</string> <string name="share_newsblur">Share \"%s\" to your Blurblog?</string>
<string name="share_this">Share</string> <string name="share_this">Share</string>
@ -64,6 +64,8 @@
<string name="save_this">Save</string> <string name="save_this">Save</string>
<string name="unsave_this">Unsave</string> <string name="unsave_this">Unsave</string>
<string name="train_this">Train</string> <string name="train_this">Train</string>
<string name="story_mark_unread_state">Read</string>
<string name="story_mark_read_state">Mark story read</string>
<string name="overlay_next">Next</string> <string name="overlay_next">Next</string>
<string name="overlay_done">Done</string> <string name="overlay_done">Done</string>
@ -223,8 +225,8 @@
<string name="menu_feedback">Send app feedback</string> <string name="menu_feedback">Send app feedback</string>
<string name="menu_feedback_post">Create a feedback post</string> <string name="menu_feedback_post">Create a feedback post</string>
<string name="menu_feedback_email">Email a bug report</string> <string name="menu_feedback_email">Email a bug report</string>
<string name="menu_theme_choose">Theme</string> <string name="menu_theme_choose">Theme</string>
<string name="menu_premium_account">Premium Account</string> <string name="menu_premium_account">Premium subscription…</string>
<string name="description_add_new_folder_icon">Add new folder icon</string> <string name="description_add_new_folder_icon">Add new folder icon</string>
@ -237,7 +239,7 @@
<string name="empty_list_view_no_stories_unread">No unread stories</string> <string name="empty_list_view_no_stories_unread">No unread stories</string>
<string name="empty_list_view_no_unread_stories">You have no unread stories.</string> <string name="empty_list_view_no_unread_stories">You have no unread stories.</string>
<string name="empty_list_view_no_focus_stories">You have no unread stories in Focus mode.\n\nSwitch to All or Unread.</string> <string name="empty_list_view_no_focus_stories">You have no unread stories in Focus.\n\nSwitch to All or Unread.</string>
<string name="empty_list_view_no_saved_stories">You have no saved stories.\n\nSwitch to All or Unread.</string> <string name="empty_list_view_no_saved_stories">You have no saved stories.\n\nSwitch to All or Unread.</string>
<string name="login_registration_custom_server">Custom server</string> <string name="login_registration_custom_server">Custom server</string>
@ -267,9 +269,9 @@
<string name="feed_opens">%d opens</string> <string name="feed_opens">%d opens</string>
<string name="feed_stories_per_month">%d stories/month</string> <string name="feed_stories_per_month">%d stories/month</string>
<string name="settings">Preferences</string> <string name="settings">Preferences</string>
<string name="mute_sites">Mute Sites</string> <string name="mute_sites">Mute Sites</string>
<string name="widget">Widget</string> <string name="widget">Widget</string>
<string name="title_widget_setup">Tap to setup in NewsBlur</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_no_subscriptions">No active subscriptions detected</string>
<string name="title_widget_loading">Loading…</string> <string name="title_widget_loading">Loading…</string>
@ -297,14 +299,14 @@
<string name="premium_text_view">Text view conveniently extracts the story</string> <string name="premium_text_view">Text view conveniently extracts the story</string>
<string name="premium_shiloh">You feed Shiloh, my poor, hungry dog, for a month</string> <string name="premium_shiloh">You feed Shiloh, my poor, hungry dog, for a month</string>
<string name="settings_cat_offline">Offline</string> <string name="settings_cat_offline">Offline reading</string>
<string name="settings_enable_offline">Download Stories</string> <string name="settings_enable_offline">Download stories</string>
<string name="settings_enable_offline_sum">Periodically fetch stories in background</string> <string name="settings_enable_offline_sum">Periodically fetch stories in background</string>
<string name="settings_enable_image_prefetch">Download Images</string> <string name="settings_enable_image_prefetch">Download images</string>
<string name="settings_enable_image_prefetch_sum">Pre-fetch images for offline reading</string> <string name="settings_enable_image_prefetch_sum">Pre-fetch images for offline reading</string>
<string name="settings_enable_text_prefetch">Download Text</string> <string name="settings_enable_text_prefetch">Download full text</string>
<string name="settings_enable_text_prefetch_sum">Pre-fetch text for offline reading</string> <string name="settings_enable_text_prefetch_sum">Pre-fetch full text for offline reading</string>
<string name="settings_keep_old_stories">Keep Stories after Reading</string> <string name="settings_keep_old_stories">Keep stories after reading</string>
<string name="settings_keep_old_stories_sum">Disable to reduce storage usage</string> <string name="settings_keep_old_stories_sum">Disable to reduce storage usage</string>
<string name="mute_config_title">You can follow up to 64 sites with a free standard account</string> <string name="mute_config_title">You can follow up to 64 sites with a free standard account</string>
@ -313,7 +315,7 @@
<string name="mute_config_upgrade">UPGRADE</string> <string name="mute_config_upgrade">UPGRADE</string>
<string name="mute_config_sites">%1$s/%2$s</string> <string name="mute_config_sites">%1$s/%2$s</string>
<string name="menu_network_select">Download Using</string> <string name="menu_network_select">Download using…</string>
<string name="menu_network_select_sum">Restrict background data to chosen networks</string> <string name="menu_network_select_sum">Restrict background data to chosen networks</string>
<string name="menu_network_select_opt_any">Any Network</string> <string name="menu_network_select_opt_any">Any Network</string>
<string name="menu_network_select_opt_nomo">Any Wifi or Ethernet</string> <string name="menu_network_select_opt_nomo">Any Wifi or Ethernet</string>
@ -330,13 +332,13 @@
</string-array> </string-array>
<string name="default_network_select_value">NOMONONME</string> <string name="default_network_select_value">NOMONONME</string>
<string name="menu_delete_offline_stories">Delete offline stories </string> <string name="menu_delete_offline_stories">Delete offline stories…</string>
<string name="menu_delete_offline_stories_key">delete_offline_stories</string> <string name="menu_delete_offline_stories_key">delete_offline_stories</string>
<string name="menu_delete_offline_stories_sum">Clear all offline stories and images</string> <string name="menu_delete_offline_stories_sum">Free up space by clearing stored stories and images</string>
<string name="menu_delete_offline_stories_confirmation">Cleared all stories and images!</string> <string name="menu_delete_offline_stories_confirmation">Cleared all stories and images!</string>
<string name="menu_cache_age_select">Maximum Cache Age</string> <string name="menu_cache_age_select">How long to keep stories…</string>
<string name="menu_cache_age_select_sum">Clean up cached story content after…</string> <string name="menu_cache_age_select_sum">Clear stored stories for offline reading</string>
<string name="menu_cache_age_select_opt_2d">2 days (reduce storage use)</string> <string name="menu_cache_age_select_opt_2d">2 days (reduce storage use)</string>
<string name="menu_cache_age_select_opt_7d">7 days</string> <string name="menu_cache_age_select_opt_7d">7 days</string>
<string name="menu_cache_age_select_opt_14d">14 days</string> <string name="menu_cache_age_select_opt_14d">14 days</string>
@ -355,9 +357,9 @@
</string-array> </string-array>
<string name="default_cache_age_select_value">CACHE_AGE_30D</string> <string name="default_cache_age_select_value">CACHE_AGE_30D</string>
<string name="settings_cat_feed_list">Feed List</string> <string name="settings_cat_feed_list">Feed list</string>
<string name="setting_feed_list_order">Feed list order</string> <string name="setting_feed_list_order">Feed list order</string>
<string name="settings_enable_row_global_shared">Show Global Shared Stories</string> <string name="settings_enable_row_global_shared">Show Global Shared Stories</string>
<string name="settings_enable_row_global_shared_sum">Show the Global Shared Stories folder</string> <string name="settings_enable_row_global_shared_sum">Show the Global Shared Stories folder</string>
<string name="settings_enable_row_infrequent_stories">Show Infrequent Stories</string> <string name="settings_enable_row_infrequent_stories">Show Infrequent Stories</string>
@ -366,8 +368,8 @@
<string name="settings_cat_story_list">Story List</string> <string name="settings_cat_story_list">Story List</string>
<string name="oldest">Oldest</string> <string name="oldest">Oldest</string>
<string name="newest">Newest First</string> <string name="newest">Newest first</string>
<string name="menu_story_order">Story Order</string> <string name="menu_story_order">Story order…</string>
<string-array name="default_story_order_entries"> <string-array name="default_story_order_entries">
<item>@string/newest</item> <item>@string/newest</item>
<item>@string/oldest</item> <item>@string/oldest</item>
@ -390,12 +392,12 @@
</string-array> </string-array>
<string name="default_feed_list_order_value">ALPHABETICAL</string> <string name="default_feed_list_order_value">ALPHABETICAL</string>
<string name="all_stories">All Stories</string> <string name="all_stories">All stories</string>
<string name="unread_only">Unread Only</string> <string name="unread_only">Unread only</string>
<string name="menu_story_content_preview">Content Preview</string> <string name="menu_story_content_preview">Story content preview</string>
<string name="menu_story_thumbnail_preview">Thumbnail Preview</string> <string name="menu_story_thumbnail_preview">Thumbnail preview</string>
<string name="menu_mark_read_on_scroll">Mark Read On Scroll</string> <string name="menu_mark_read_on_scroll">Mark read on scroll</string>
<string name="menu_read_filter">Read Filter</string> <string name="menu_read_filter">Read story filter…</string>
<string-array name="default_read_filter_entries"> <string-array name="default_read_filter_entries">
<item>@string/all_stories</item> <item>@string/all_stories</item>
<item>@string/unread_only</item> <item>@string/unread_only</item>
@ -419,19 +421,52 @@
<item>Cancel</item> <item>Cancel</item>
</string-array> </string-array>
<string name="settings_auto_open_first_unread">Auto-Open First Story</string> <string name="settings_auto_open_first_unread">Auto-open first story</string>
<string name="settings_auto_open_first_unread_sum">Automatically open first unread story from story list</string> <string name="settings_auto_open_first_unread_sum">Automatically open first unread story in the story list</string>
<string name="settings_mark_read_on_scroll">Mark Stories Read On Scroll</string> <string name="settings_mark_read_on_feed_scroll">Mark story titles read on scroll</string>
<string name="settings_mark_read_on_scroll_sum">Automatically mark stories as read when scrolled past</string> <string name="settings_mark_read_on_feed_scroll_sum">Automatically mark stories as read when scrolled past</string>
<string name="settings_social">Social</string> <string name="settings_mark_story_read">Mark a story as read…</string>
<string name="settings_show_public_comments">Show Public Comments</string> <string name="mark_story_read_immediately">Immediately</string>
<!-- <string name="mark_story_read_5_seconds">After 5 seconds</string>-->
<!-- <string name="mark_story_read_10_seconds">After 10 seconds</string>-->
<!-- <string name="mark_story_read_20_seconds">After 20 seconds</string>-->
<!-- <string name="mark_story_read_30_seconds">After 30 seconds</string>-->
<!-- <string name="mark_story_read_45_seconds">After 45 seconds</string>-->
<!-- <string name="mark_story_read_60_seconds">After 60 seconds</string>-->
<string name="mark_story_read_manually">Manually</string>
<string-array name="mark_story_read_entries">
<item>@string/mark_story_read_immediately</item>
<!-- <item>@string/mark_story_read_5_seconds</item>-->
<!-- <item>@string/mark_story_read_10_seconds</item>-->
<!-- <item>@string/mark_story_read_20_seconds</item>-->
<!-- <item>@string/mark_story_read_30_seconds</item>-->
<!-- <item>@string/mark_story_read_45_seconds</item>-->
<!-- <item>@string/mark_story_read_60_seconds</item>-->
<item>@string/mark_story_read_manually</item>
</string-array>
<string-array name="mark_story_read_values">
<item>IMMEDIATELY</item>
<!-- <item>SECONDS_5</item>-->
<!-- <item>SECONDS_10</item>-->
<!-- <item>SECONDS_20</item>-->
<!-- <item>SECONDS_30</item>-->
<!-- <item>SECONDS_45</item>-->
<!-- <item>SECONDS_60</item>-->
<item>MANUALLY</item>
</string-array>
<string name="mark_story_read_default_value">IMMEDIATELY</string>
<string name="settings_social">Sharing stories</string>
<string name="settings_show_public_comments">Show public comments</string>
<string name="settings_reading">Reading</string> <string name="settings_reading">Reading</string>
<string name="settings_content_preview">Content Preview Text</string> <string name="settings_content_preview">Content preview text…</string>
<string name="settings_thumbnails_style">Image Preview Thumbnails</string> <string name="settings_thumbnails_style">Image preview thumbnails…</string>
<string name="settings_notifications">Notifications</string> <string name="settings_notifications">Notifications</string>
<string name="settings_enable_notifications">Enable Notifications</string> <string name="settings_enable_notifications">Enable notifications</string>
<string-array name="show_content_preview_entries"> <string-array name="show_content_preview_entries">
<item>@string/story_content_preview_none</item> <item>@string/story_content_preview_none</item>
@ -509,10 +544,10 @@
<string name="sync_status_offline">Offline</string> <string name="sync_status_offline">Offline</string>
<string name="sync_status_feed_add">Adding feed …</string> <string name="sync_status_feed_add">Adding feed …</string>
<string name="volume_key_navigation">Volume Key Navigation</string> <string name="volume_key_navigation">Volume key navigation…</string>
<string name="off">Off</string> <string name="off">Off</string>
<string name="volume_up_next">Up = Next Story</string> <string name="volume_up_next">Up = next story</string>
<string name="volume_down_next">Down = Next Story</string> <string name="volume_down_next">Down = next story</string>
<string-array name="volume_key_navigation_entries"> <string-array name="volume_key_navigation_entries">
<item>@string/off</item> <item>@string/off</item>
<item>@string/volume_up_next</item> <item>@string/volume_up_next</item>
@ -525,10 +560,10 @@
</string-array> </string-array>
<string name="default_volume_key_navigation_value">OFF</string> <string name="default_volume_key_navigation_value">OFF</string>
<string name="settings_confirm_mark_all_read">Confirm Mark All Read</string> <string name="settings_confirm_mark_all_read">Confirm mark all read on…</string>
<string name="none">None</string> <string name="none">Neither</string>
<string name="feed_and_folder">Feeds and Folders</string> <string name="feed_and_folder">Feeds and folders</string>
<string name="folder_only">Folders Only</string> <string name="folder_only">Folders only</string>
<string-array name="confirm_mark_all_read_entries"> <string-array name="confirm_mark_all_read_entries">
<item>@string/feed_and_folder</item> <item>@string/feed_and_folder</item>
<item>@string/folder_only</item> <item>@string/folder_only</item>
@ -541,15 +576,15 @@
</string-array> </string-array>
<string name="confirm_mark_all_read_value">FOLDER_ONLY</string> <string name="confirm_mark_all_read_value">FOLDER_ONLY</string>
<string name="settings_confirm_mark_range_read">Confirm Mark Older/Newer Read</string> <string name="settings_confirm_mark_range_read">Confirm mark older/newer read</string>
<string name="settings_ltr_gesture_action">Rightward Swipe on Story Title</string> <string name="settings_ltr_gesture_action">Rightward swipe on story title…</string>
<string name="gest_action_none">No Action</string> <string name="gest_action_none">No action</string>
<string name="gest_action_markread">Mark Story Read</string> <string name="gest_action_markread">Mark story as read</string>
<string name="gest_action_markunread">Mark Story Unread</string> <string name="gest_action_markunread">Mark story as unread</string>
<string name="gest_action_save">Save Story</string> <string name="gest_action_save">Save story</string>
<string name="gest_action_unsave">Unsave Story</string> <string name="gest_action_unsave">Unsave story</string>
<string name="gest_action_statistics">Statistics</string> <string name="gest_action_statistics">Site statistics</string>
<string-array name="ltr_gesture_action_entries"> <string-array name="ltr_gesture_action_entries">
<item>@string/gest_action_none</item> <item>@string/gest_action_none</item>
<item>@string/gest_action_markread</item> <item>@string/gest_action_markread</item>
@ -568,7 +603,7 @@
</string-array> </string-array>
<string name="ltr_gesture_action_value">GEST_ACTION_MARKREAD</string> <string name="ltr_gesture_action_value">GEST_ACTION_MARKREAD</string>
<string name="settings_rtl_gesture_action">Leftward Swipe on Story Title</string> <string name="settings_rtl_gesture_action">Leftward swipe on story title…</string>
<string-array name="rtl_gesture_action_entries"> <string-array name="rtl_gesture_action_entries">
<item>@string/gest_action_none</item> <item>@string/gest_action_none</item>
<item>@string/gest_action_markread</item> <item>@string/gest_action_markread</item>
@ -587,8 +622,8 @@
</string-array> </string-array>
<string name="rtl_gesture_action_value">GEST_ACTION_MARKUNREAD</string> <string name="rtl_gesture_action_value">GEST_ACTION_MARKUNREAD</string>
<string name="default_browser">Default browser</string> <string name="default_browser">Default browser</string>
<string name="font">Font</string> <string name="font">Font</string>
<string name="default_font">Default</string> <string name="default_font">Default</string>
<string name="gotham_narrow_font">Gotham Narrow</string> <string name="gotham_narrow_font">Gotham Narrow</string>
<string name="chronicle_font">Chronicle</string> <string name="chronicle_font">Chronicle</string>
@ -649,7 +684,7 @@
<string name="default_default_browser">@string/system_default_prefvalue</string> <string name="default_default_browser">@string/system_default_prefvalue</string>
<string name="story_notification_channel_id">story_notification_channel</string> <string name="story_notification_channel_id">story_notification_channel</string>
<string name="story_notification_channel_name">New Stories</string> <string name="story_notification_channel_name">New stories</string>
<string name="go_to_feed">Go to feed</string> <string name="go_to_feed">Go to feed</string>
<string name="js_get_selection">(function(){return window.getSelection().toString()})()</string> <string name="js_get_selection">(function(){return window.getSelection().toString()})()</string>

View file

@ -184,8 +184,6 @@
<style name="storyButtons" parent="Widget.MaterialComponents.Button.OutlinedButton"> <style name="storyButtons" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
<item name="android:layout_marginLeft">8dp</item>
<item name="android:layout_marginRight">8dp</item>
<item name="android:textColor">@color/gray75</item> <item name="android:textColor">@color/gray75</item>
<item name="android:textAllCaps">false</item> <item name="android:textAllCaps">false</item>
<item name="android:letterSpacing">0</item> <item name="android:letterSpacing">0</item>
@ -195,8 +193,6 @@
</style> </style>
<style name="storyButtons.dark" parent="Widget.MaterialComponents.Button.OutlinedButton"> <style name="storyButtons.dark" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
<item name="android:layout_marginLeft">8dp</item>
<item name="android:layout_marginRight">8dp</item>
<item name="android:textColor">@color/gray46</item> <item name="android:textColor">@color/gray46</item>
<item name="android:textAllCaps">false</item> <item name="android:textAllCaps">false</item>
<item name="android:letterSpacing">0</item> <item name="android:letterSpacing">0</item>
@ -205,6 +201,13 @@
<item name="iconSize">16dp</item> <item name="iconSize">16dp</item>
</style> </style>
<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>
<style name="shareBarBackground"> <style name="shareBarBackground">
<item name="android:background">@color/share_bar_background</item> <item name="android:background">@color/share_bar_background</item>
</style> </style>

View file

@ -28,6 +28,7 @@
<item name="chip">@style/chip</item> <item name="chip">@style/chip</item>
<item name="actionButtons">@style/actionButtons</item> <item name="actionButtons">@style/actionButtons</item>
<item name="storyButtons">@style/storyButtons</item> <item name="storyButtons">@style/storyButtons</item>
<item name="storyButtonsDimmed">@style/storyButtonsDimmed</item>
<item name="shareBarBackground">@style/shareBarBackground</item> <item name="shareBarBackground">@style/shareBarBackground</item>
<item name="shareBarText">@style/shareBarText</item> <item name="shareBarText">@style/shareBarText</item>
<item name="commentsHeader">@style/commentsHeader</item> <item name="commentsHeader">@style/commentsHeader</item>
@ -81,6 +82,7 @@
<item name="chip">@style/chip.dark</item> <item name="chip">@style/chip.dark</item>
<item name="actionButtons">@style/actionButtons.dark</item> <item name="actionButtons">@style/actionButtons.dark</item>
<item name="storyButtons">@style/storyButtons.dark</item> <item name="storyButtons">@style/storyButtons.dark</item>
<item name="storyButtonsDimmed">@style/storyButtonsDimmed.dark</item>
<item name="shareBarBackground">@style/shareBarBackground.dark</item> <item name="shareBarBackground">@style/shareBarBackground.dark</item>
<item name="shareBarText">@style/shareBarText.dark</item> <item name="shareBarText">@style/shareBarText.dark</item>
<item name="commentsHeader">@style/commentsHeader.dark</item> <item name="commentsHeader">@style/commentsHeader.dark</item>
@ -135,6 +137,7 @@
<item name="chip">@style/chip.dark</item> <item name="chip">@style/chip.dark</item>
<item name="actionButtons">@style/actionButtons.dark</item> <item name="actionButtons">@style/actionButtons.dark</item>
<item name="storyButtons">@style/storyButtons.dark</item> <item name="storyButtons">@style/storyButtons.dark</item>
<item name="storyButtonsDimmed">@style/storyButtonsDimmed.dark</item>
<item name="shareBarBackground">@style/shareBarBackground.black</item> <item name="shareBarBackground">@style/shareBarBackground.black</item>
<item name="shareBarText">@style/shareBarText.dark</item> <item name="shareBarText">@style/shareBarText.dark</item>
<item name="commentsHeader">@style/commentsHeader.black</item> <item name="commentsHeader">@style/commentsHeader.black</item>

View file

@ -57,15 +57,11 @@
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="enable_row_global_shared" android:key="enable_row_global_shared"
android:title="@string/settings_enable_row_global_shared" android:title="@string/settings_enable_row_global_shared" />
android:summary="@string/settings_enable_row_global_shared_sum"
/>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="enable_row_infrequent_stories" android:key="enable_row_infrequent_stories"
android:title="@string/settings_enable_row_infrequent_stories" android:title="@string/settings_enable_row_infrequent_stories" />
android:summary="@string/settings_enable_row_infrequent_stories_sum"
/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
@ -100,6 +96,11 @@
android:key="pref_auto_open_first_unread" android:key="pref_auto_open_first_unread"
android:title="@string/settings_auto_open_first_unread" android:title="@string/settings_auto_open_first_unread"
android:summary="@string/settings_auto_open_first_unread_sum" /> android:summary="@string/settings_auto_open_first_unread_sum" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_mark_read_on_scroll"
android:title="@string/settings_mark_read_on_feed_scroll"
android:summary="@string/settings_mark_read_on_feed_scroll_sum" />
<ListPreference <ListPreference
android:key="pref_show_content_preview_style" android:key="pref_show_content_preview_style"
android:title="@string/settings_content_preview" android:title="@string/settings_content_preview"
@ -114,11 +115,13 @@
android:entries="@array/thumbnail_style_entries" android:entries="@array/thumbnail_style_entries"
android:entryValues="@array/thumbnail_style_values" android:entryValues="@array/thumbnail_style_values"
android:defaultValue="@string/thumbnail_style_default_value" /> android:defaultValue="@string/thumbnail_style_default_value" />
<CheckBoxPreference <ListPreference
android:defaultValue="false" android:key="pref_story_mark_read_behavior"
android:key="pref_mark_read_on_scroll" android:title="@string/settings_mark_story_read"
android:title="@string/settings_mark_read_on_scroll" android:dialogTitle="@string/settings_mark_story_read"
android:summary="@string/settings_mark_read_on_scroll_sum" /> android:entries="@array/mark_story_read_entries"
android:entryValues="@array/mark_story_read_values"
android:defaultValue="@string/mark_story_read_default_value" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View file

@ -196,21 +196,32 @@ public class FeedChooserAdapter extends BaseExpandableListAdapter {
private Comparator<Feed> getListComparator() { private Comparator<Feed> getListComparator() {
return (o1, o2) -> { return (o1, o2) -> {
// some feeds have missing data
if (o1.title == null) o1.title = "";
if (o2.title == null) o2.title = "";
if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.ASCENDING) { if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.ASCENDING) {
return o1.title.compareTo(o2.title); return o1.title.compareTo(o2.title);
} else if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.DESCENDING) { } else if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.DESCENDING) {
return o2.title.compareTo(o1.title); return o2.title.compareTo(o1.title);
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.ASCENDING) { } else if (o1.subscribers != null && o2.subscribers != null &&
feedOrderFilter == FeedOrderFilter.SUBSCRIBERS &&
listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.valueOf(o1.subscribers).compareTo(Integer.valueOf(o2.subscribers)); return Integer.valueOf(o1.subscribers).compareTo(Integer.valueOf(o2.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.DESCENDING) { } else if (o1.subscribers != null && o2.subscribers != null &&
feedOrderFilter == FeedOrderFilter.SUBSCRIBERS &&
listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.valueOf(o2.subscribers).compareTo(Integer.valueOf(o1.subscribers)); return Integer.valueOf(o2.subscribers).compareTo(Integer.valueOf(o1.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.ASCENDING) { } else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.feedOpens, o2.feedOpens); return Integer.compare(o1.feedOpens, o2.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.DESCENDING) { } else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.compare(o2.feedOpens, o1.feedOpens); return Integer.compare(o2.feedOpens, o1.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.ASCENDING) { } else if (o1.lastStoryDate != null && o2.lastStoryDate != null &&
feedOrderFilter == FeedOrderFilter.RECENT_STORY &&
listOrderFilter == ListOrderFilter.ASCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter); return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.DESCENDING) { } else if (o1.lastStoryDate != null && o2.lastStoryDate != null &&
feedOrderFilter == FeedOrderFilter.RECENT_STORY &&
listOrderFilter == ListOrderFilter.DESCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter); return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.ASCENDING) { } else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.storiesPerMonth, o2.storiesPerMonth); return Integer.compare(o1.storiesPerMonth, o2.storiesPerMonth);

View file

@ -16,7 +16,7 @@ public class InfrequentItemsList extends ItemsList implements InfrequentCutoffCh
protected void onCreate(Bundle bundle) { protected void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
UIUtils.setupToolbar(this, R.drawable.ak_icon_allstories, getResources().getString(R.string.infrequent_title), false); UIUtils.setupToolbar(this, R.drawable.ak_icon_infrequent, getResources().getString(R.string.infrequent_title), false);
} }
@Override @Override

View file

@ -11,7 +11,7 @@ public class InfrequentReading extends Reading {
protected void onCreate(Bundle savedInstanceBundle) { protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle); super.onCreate(savedInstanceBundle);
UIUtils.setupToolbar(this, R.drawable.ak_icon_allstories, getResources().getString(R.string.infrequent_title), false); UIUtils.setupToolbar(this, R.drawable.ak_icon_infrequent, getResources().getString(R.string.infrequent_title), false);
} }
} }

View file

@ -18,7 +18,7 @@ class InitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
installSplashScreen().also { installSplashScreen().also {
it.setKeepVisibleCondition { it.setKeepOnScreenCondition() {
// keep showing the splash screen until FeedUtils.offerInitContext(...) // keep showing the splash screen until FeedUtils.offerInitContext(...)
// finishes and UI ready to display // finishes and UI ready to display
FeedUtils.dbHelper != null || FeedUtils.thumbnailLoader != null FeedUtils.dbHelper != null || FeedUtils.thumbnailLoader != null

View file

@ -266,7 +266,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
menu.findItem(R.id.menu_story_thumbnail_no_preview).setChecked(true); menu.findItem(R.id.menu_story_thumbnail_no_preview).setChecked(true);
} }
boolean isMarkReadOnScroll = PrefsUtils.isMarkReadOnScroll(this); boolean isMarkReadOnScroll = PrefsUtils.isMarkReadOnFeedScroll(this);
if (isMarkReadOnScroll) { if (isMarkReadOnScroll) {
menu.findItem(R.id.menu_mark_read_on_scroll_enabled).setChecked(true); menu.findItem(R.id.menu_mark_read_on_scroll_enabled).setChecked(true);
} else { } else {

View file

@ -36,6 +36,9 @@ open class NbActivity : AppCompatActivity() {
PrefsUtils.applyThemePreference(this) PrefsUtils.applyThemePreference(this)
lastTheme = PrefsUtils.getSelectedTheme(this) lastTheme = PrefsUtils.getSelectedTheme(this)
super.onCreate(bundle)
offerInitContext(this)
// in rare cases of process interruption or DB corruption, an activity can launch without valid // in rare cases of process interruption or DB corruption, an activity can launch without valid
// login creds. redirect the user back to the loging workflow. // login creds. redirect the user back to the loging workflow.
if (PrefsUtils.getUserId(this) == null) { if (PrefsUtils.getUserId(this) == null) {
@ -44,9 +47,6 @@ open class NbActivity : AppCompatActivity() {
finish() finish()
} }
super.onCreate(bundle)
offerInitContext(this)
bundle?.let { bundle?.let {
uniqueLoginKey = it.getString(UNIQUE_LOGIN_KEY) uniqueLoginKey = it.getString(UNIQUE_LOGIN_KEY)
} }

View file

@ -33,7 +33,6 @@ import com.newsblur.util.PrefConstants.ThemeValue
import com.newsblur.view.ReadingScrollView.ScrollChangeListener import com.newsblur.view.ReadingScrollView.ScrollChangeListener
import com.newsblur.viewModel.StoriesViewModel import com.newsblur.viewModel.StoriesViewModel
import java.lang.Runnable import java.lang.Runnable
import java.util.*
import kotlin.math.abs import kotlin.math.abs
abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeListener, abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeListener,
@ -52,6 +51,8 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
private var readingAdapter: ReadingAdapter? = null private var readingAdapter: ReadingAdapter? = null
private var stopLoading = false private var stopLoading = false
private var unreadSearchActive = false private var unreadSearchActive = false
// marking a story as read immediately on reading page scroll
private var isMarkStoryReadImmediately = false
// unread count for the circular progress overlay. set to nonzero to activate the progress indicator overlay // unread count for the circular progress overlay. set to nonzero to activate the progress indicator overlay
private var startingUnreadCount = 0 private var startingUnreadCount = 0
@ -59,6 +60,14 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
private var overlayRangeBotPx = 0f private var overlayRangeBotPx = 0f
private var lastVScrollPos = 0 private var lastVScrollPos = 0
// enabling multi window mode from recent apps on the device
// creates a different activity lifecycle compared to a device rotation
// resulting in onPause being called when the app is actually on the screen.
// calling onPause sets stopLoading as true and content wouldn't be loaded.
// track the multi window mode config change and skip stopLoading in first onPause call.
// refactor stopLoading mechanism as a cancellation signal tied to the view lifecycle.
private var isMultiWindowModeHack = false
private val pageHistory = mutableListOf<Story>() private val pageHistory = mutableListOf<Story>()
private lateinit var volumeKeyNavigation: VolumeKeyNavigation private lateinit var volumeKeyNavigation: VolumeKeyNavigation
@ -107,6 +116,7 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
intelState = PrefsUtils.getStateFilter(this) intelState = PrefsUtils.getStateFilter(this)
volumeKeyNavigation = PrefsUtils.getVolumeKeyNavigation(this) volumeKeyNavigation = PrefsUtils.getVolumeKeyNavigation(this)
isMarkStoryReadImmediately = PrefsUtils.isMarkStoryReadImmediately(this)
setupViews() setupViews()
setupListeners() setupListeners()
@ -143,8 +153,17 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
} }
override fun onPause() { override fun onPause() {
stopLoading = true
super.onPause() super.onPause()
if (isMultiWindowModeHack) {
isMultiWindowModeHack = false
} else {
stopLoading = true
}
}
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration?) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
isMultiWindowModeHack = isInMultiWindowMode
} }
private fun setupViews() { private fun setupViews() {
@ -164,12 +183,10 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
enableProgressCircle(binding.readingOverlayProgressLeft, false) enableProgressCircle(binding.readingOverlayProgressLeft, false)
enableProgressCircle(binding.readingOverlayProgressRight, false) enableProgressCircle(binding.readingOverlayProgressRight, false)
supportFragmentManager.commit { supportFragmentManager.findFragmentByTag(ReadingPagerFragment::class.java.name)
val fragment = ?: supportFragmentManager.commit {
supportFragmentManager.findFragmentByTag(ReadingPagerFragment::class.java.name) as ReadingPagerFragment? add(R.id.activity_reading_container, ReadingPagerFragment.newInstance(), ReadingPagerFragment::class.java.name)
?: ReadingPagerFragment.newInstance() }
add(R.id.activity_reading_container, fragment, ReadingPagerFragment::class.java.name)
}
} }
private fun setupListeners() { private fun setupListeners() {
@ -371,13 +388,15 @@ abstract class Reading : NbActivity(), OnPageChangeListener, OnSeekBarChangeList
readingAdapter?.let { readingAdapter -> readingAdapter?.let { readingAdapter ->
val story = readingAdapter.getStory(position) val story = readingAdapter.getStory(position)
if (story != null) { if (story != null) {
FeedUtils.markStoryAsRead(story, this@Reading)
synchronized(pageHistory) { synchronized(pageHistory) {
// if the history is just starting out or the last entry in it isn't this page, add this page // if the history is just starting out or the last entry in it isn't this page, add this page
if (pageHistory.size < 1 || story != pageHistory[pageHistory.size - 1]) { if (pageHistory.size < 1 || story != pageHistory[pageHistory.size - 1]) {
pageHistory.add(story) pageHistory.add(story)
} }
} }
if (isMarkStoryReadImmediately) {
FeedUtils.markStoryAsRead(story, this@Reading)
}
} }
checkStoryCount(position) checkStoryCount(position)
updateOverlayText() updateOverlayText()

View file

@ -570,13 +570,6 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
vh.intelDot.setImageResource(android.R.color.transparent); vh.intelDot.setImageResource(android.R.color.transparent);
} }
// M text size equals to 1.0
if (textSize > 1.0) {
vh.storyTitleView.setMaxLines(3);
} else {
vh.storyTitleView.setMaxLines(2);
}
vh.storyTitleView.setText(UIUtils.fromHtml(story.title)); vh.storyTitleView.setText(UIUtils.fromHtml(story.title));
vh.storyDate.setText(StoryUtils.formatShortDate(context, story.timestamp)); vh.storyDate.setText(StoryUtils.formatShortDate(context, story.timestamp));
@ -659,6 +652,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private void bindRow(StoryRowViewHolder vh, int position, Story story) { private void bindRow(StoryRowViewHolder vh, int position, Story story) {
StoryContentPreviewStyle storyContentPreviewStyle = PrefsUtils.getStoryContentPreviewStyle(context); StoryContentPreviewStyle storyContentPreviewStyle = PrefsUtils.getStoryContentPreviewStyle(context);
if (storyContentPreviewStyle != StoryContentPreviewStyle.NONE) { if (storyContentPreviewStyle != StoryContentPreviewStyle.NONE) {
vh.storyTitleView.setMaxLines(3);
if (storyContentPreviewStyle == StoryContentPreviewStyle.LARGE) { if (storyContentPreviewStyle == StoryContentPreviewStyle.LARGE) {
vh.storySnippet.setMaxLines(6); vh.storySnippet.setMaxLines(6);
} else if (storyContentPreviewStyle == StoryContentPreviewStyle.MEDIUM) { } else if (storyContentPreviewStyle == StoryContentPreviewStyle.MEDIUM) {
@ -669,6 +663,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
vh.storySnippet.setVisibility(View.VISIBLE); vh.storySnippet.setVisibility(View.VISIBLE);
vh.storySnippet.setText(story.shortContent); vh.storySnippet.setText(story.shortContent);
} else { } else {
vh.storyTitleView.setMaxLines(6);
vh.storySnippet.setVisibility(View.GONE); vh.storySnippet.setVisibility(View.GONE);
} }

View file

@ -441,7 +441,7 @@ public class ItemSetFragment extends NbFragment {
indexOfLastUnread = -1; indexOfLastUnread = -1;
} }
if (PrefsUtils.isMarkReadOnScroll(getActivity())) { if (PrefsUtils.isMarkReadOnFeedScroll(requireContext())) {
// we want the top row of stories that is partially obscured. go back one from the first fully visible // we want the top row of stories that is partially obscured. go back one from the first fully visible
int markEnd = layoutManager.findFirstCompletelyVisibleItemPosition() - 1; int markEnd = layoutManager.findFirstCompletelyVisibleItemPosition() - 1;
if (markEnd > lastAutoMarkIndex) { if (markEnd > lastAutoMarkIndex) {
@ -451,7 +451,7 @@ public class ItemSetFragment extends NbFragment {
int index = markEnd - i; int index = markEnd - i;
Story story = adapter.getStory(index); Story story = adapter.getStory(index);
if (story != null) { if (story != null) {
FeedUtils.markStoryAsRead(story, getActivity()); FeedUtils.markStoryAsRead(story, requireContext());
} }
} }
} }

View file

@ -5,6 +5,7 @@ import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
@ -16,12 +17,13 @@ import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.newsblur.R import com.newsblur.R
import com.newsblur.activity.FeedItemsList import com.newsblur.activity.FeedItemsList
import com.newsblur.activity.Reading import com.newsblur.activity.Reading
import com.newsblur.databinding.FragmentReadingitemBinding import com.newsblur.databinding.FragmentReadingitemBinding
import com.newsblur.databinding.IncludeReadingItemCommentBinding import com.newsblur.databinding.ReadingItemActionsBinding
import com.newsblur.domain.Classifier import com.newsblur.domain.Classifier
import com.newsblur.domain.Story import com.newsblur.domain.Story
import com.newsblur.domain.UserDetails import com.newsblur.domain.UserDetails
@ -33,7 +35,6 @@ import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_TEXT
import com.newsblur.service.OriginalTextService import com.newsblur.service.OriginalTextService
import com.newsblur.util.* import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue import com.newsblur.util.PrefConstants.ThemeValue
import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -81,7 +82,9 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
private val webViewContentMutex = Any() private val webViewContentMutex = Any()
private lateinit var binding: FragmentReadingitemBinding private lateinit var binding: FragmentReadingitemBinding
private lateinit var itemCommentBinding: IncludeReadingItemCommentBinding private lateinit var readingItemActionsBinding: ReadingItemActionsBinding
private lateinit var markStoryReadBehavior: MarkStoryReadBehavior
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -97,7 +100,8 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
classifier = requireArguments().getSerializable("classifier") as Classifier? classifier = requireArguments().getSerializable("classifier") as Classifier?
sourceUserId = requireArguments().getString("sourceUserId") sourceUserId = requireArguments().getString("sourceUserId")
user = PrefsUtils.getUserDetails(requireActivity()) user = PrefsUtils.getUserDetails(requireContext())
markStoryReadBehavior = PrefsUtils.getMarkStoryReadBehavior(requireContext())
textSizeReceiver = TextSizeReceiver() textSizeReceiver = TextSizeReceiver()
requireActivity().registerReceiver(textSizeReceiver, IntentFilter(TEXT_SIZE_CHANGED)) requireActivity().registerReceiver(textSizeReceiver, IntentFilter(TEXT_SIZE_CHANGED))
@ -142,7 +146,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_readingitem, container, false) val view = inflater.inflate(R.layout.fragment_readingitem, container, false)
binding = FragmentReadingitemBinding.bind(view) binding = FragmentReadingitemBinding.bind(view)
itemCommentBinding = IncludeReadingItemCommentBinding.bind(binding.root) readingItemActionsBinding = ReadingItemActionsBinding.bind(binding.root)
val readingActivity = requireActivity() as Reading val readingActivity = requireActivity() as Reading
fs = readingActivity.fs fs = readingActivity.fs
@ -160,6 +164,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
updateTrainButton() updateTrainButton()
updateShareButton() updateShareButton()
updateSaveButton() updateSaveButton()
updateMarkReadButton()
setupItemCommentsAndShares() setupItemCommentsAndShares()
binding.readingScrollview.registerScrollChangeListener(readingActivity) binding.readingScrollview.registerScrollChangeListener(readingActivity)
@ -170,9 +175,10 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.storyContextMenuButton.setOnClickListener { onClickMenuButton() } binding.storyContextMenuButton.setOnClickListener { onClickMenuButton() }
itemCommentBinding.trainStoryButton.setOnClickListener { clickTrain() } readingItemActionsBinding.markReadStoryButton.setOnClickListener { clickMarkStoryRead() }
itemCommentBinding.saveStoryButton.setOnClickListener { clickSave() } readingItemActionsBinding.trainStoryButton.setOnClickListener { clickTrain() }
itemCommentBinding.shareStoryButton.setOnClickListener { clickShare() } readingItemActionsBinding.saveStoryButton.setOnClickListener { clickSave() }
readingItemActionsBinding.shareStoryButton.setOnClickListener { clickShare() }
} }
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
@ -330,13 +336,27 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
} }
} }
private fun clickMarkStoryRead() {
if (story!!.read) FeedUtils.markStoryUnread(story!!, requireContext())
else FeedUtils.markStoryAsRead(story!!, requireContext())
}
private fun updateMarkReadButton() {
if (markStoryReadBehavior == MarkStoryReadBehavior.MANUALLY) {
readingItemActionsBinding.markReadStoryButton.visibility = View.VISIBLE
readingItemActionsBinding.markReadStoryButton.setStoryReadState(requireContext(), story!!.read)
} else {
readingItemActionsBinding.markReadStoryButton.visibility = View.GONE
}
}
private fun clickTrain() { private fun clickTrain() {
val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs) val intelFrag = StoryIntelTrainerFragment.newInstance(story, fs)
intelFrag.show(requireActivity().supportFragmentManager, StoryIntelTrainerFragment::class.java.name) intelFrag.show(requireActivity().supportFragmentManager, StoryIntelTrainerFragment::class.java.name)
} }
private fun updateTrainButton() { private fun updateTrainButton() {
itemCommentBinding.trainStoryButton.visibility = if (story!!.feedId == "0") View.GONE else View.VISIBLE readingItemActionsBinding.trainStoryButton.visibility = if (story!!.feedId == "0") View.GONE else View.VISIBLE
} }
private fun clickSave() { private fun clickSave() {
@ -348,7 +368,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
} }
private fun updateSaveButton() { private fun updateSaveButton() {
itemCommentBinding.saveStoryButton.setText(if (story!!.starred) R.string.unsave_this else R.string.save_this) readingItemActionsBinding.saveStoryButton.setText(if (story!!.starred) R.string.unsave_this else R.string.save_this)
} }
private fun clickShare() { private fun clickShare() {
@ -359,11 +379,11 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
private fun updateShareButton() { private fun updateShareButton() {
for (userId in story!!.sharedUserIds) { for (userId in story!!.sharedUserIds) {
if (TextUtils.equals(userId, user!!.id)) { if (TextUtils.equals(userId, user!!.id)) {
itemCommentBinding.shareStoryButton.setText(R.string.already_shared) readingItemActionsBinding.shareStoryButton.setText(R.string.already_shared)
return return
} }
} }
itemCommentBinding.shareStoryButton.setText(R.string.share_this) readingItemActionsBinding.shareStoryButton.setText(R.string.share_this)
} }
private fun setupItemCommentsAndShares() { private fun setupItemCommentsAndShares() {
@ -603,6 +623,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
if (updateType and UPDATE_STORY != 0) { if (updateType and UPDATE_STORY != 0) {
updateSaveButton() updateSaveButton()
updateShareButton() updateShareButton()
updateMarkReadButton()
setupItemCommentsAndShares() setupItemCommentsAndShares()
} }
if (updateType and UPDATE_TEXT != 0) { if (updateType and UPDATE_TEXT != 0) {
@ -908,4 +929,27 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
private val altSniff4 = Pattern.compile("<img[^>]*title=(['\"])((?:(?!\\1).)*)\\1[^>]*src=(['\"])((?:(?!\\3).)*)\\3[^>]*>", Pattern.CASE_INSENSITIVE) private val altSniff4 = Pattern.compile("<img[^>]*title=(['\"])((?:(?!\\1).)*)\\1[^>]*src=(['\"])((?:(?!\\3).)*)\\3[^>]*>", Pattern.CASE_INSENSITIVE)
private val imgSniff = Pattern.compile("<img[^>]*(src\\s*=\\s*)\"([^\"]*)\"[^>]*>", Pattern.CASE_INSENSITIVE) private val imgSniff = Pattern.compile("<img[^>]*(src\\s*=\\s*)\"([^\"]*)\"[^>]*>", Pattern.CASE_INSENSITIVE)
} }
}
private fun MaterialButton.setStoryReadState(context: Context, isRead: Boolean) {
var selectedTheme = PrefsUtils.getSelectedTheme(context)
if (selectedTheme == ThemeValue.AUTO) {
selectedTheme = when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> ThemeValue.DARK
else -> ThemeValue.LIGHT
}
}
val styleResId: Int = when (selectedTheme) {
ThemeValue.LIGHT -> if (isRead) R.style.storyButtonsDimmed else R.style.storyButtons
else -> if (isRead) R.style.storyButtonsDimmed_dark else R.style.storyButtons_dark
}
val stringResId: Int = if (isRead) R.string.story_mark_unread_state else R.string.story_mark_read_state
this.text = context.getString(stringResId)
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
this.setTextAppearance(styleResId)
} else {
this.setTextAppearance(context, styleResId)
}
} }

View file

@ -7,11 +7,11 @@ import android.app.job.JobService
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import com.newsblur.subscription.SubscriptionManagerImpl import com.newsblur.subscription.SubscriptionManagerImpl
import com.newsblur.subscription.SubscriptionsListener
import com.newsblur.util.AppConstants import com.newsblur.util.AppConstants
import com.newsblur.util.Log import com.newsblur.util.Log
import com.newsblur.util.NBScope import com.newsblur.util.NBScope
import com.newsblur.util.PrefsUtils import com.newsblur.util.PrefsUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@ -24,6 +24,8 @@ import kotlinx.coroutines.launch
*/ */
class SubscriptionSyncService : JobService() { class SubscriptionSyncService : JobService() {
private val scope = NBScope
override fun onStartJob(params: JobParameters?): Boolean { override fun onStartJob(params: JobParameters?): Boolean {
Log.d(this, "onStartJob") Log.d(this, "onStartJob")
if (!PrefsUtils.hasCookie(this)) { if (!PrefsUtils.hasCookie(this)) {
@ -31,15 +33,22 @@ class SubscriptionSyncService : JobService() {
return false return false
} }
NBScope.launch(Dispatchers.Default) { val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, scope)
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, this) subscriptionManager.startBillingConnection(object : SubscriptionsListener {
val job = subscriptionManager.syncActiveSubscription() override fun onBillingConnectionReady() {
job.invokeOnCompletion { scope.launch {
Log.d(this, "sync active subscription completed.") subscriptionManager.syncActiveSubscription()
// manually trigger jobFinished after work is done Log.d(this, "sync active subscription completed.")
// manually call jobFinished after work is done
jobFinished(params, false)
}
}
override fun onBillingConnectionError(message: String?) {
// manually call jobFinished on error
jobFinished(params, false) jobFinished(params, false)
} }
} })
return true // returning true due to background thread work return true // returning true due to background thread work
} }

View file

@ -58,7 +58,7 @@ interface SubscriptionManager {
/** /**
* Sync subscription state between NewsBlur and Play Store. * Sync subscription state between NewsBlur and Play Store.
*/ */
fun syncActiveSubscription(): Job suspend fun syncActiveSubscription(): Job
/** /**
* Notify backend of active Play Store subscription. * Notify backend of active Play Store subscription.
@ -70,13 +70,13 @@ interface SubscriptionManager {
interface SubscriptionsListener { interface SubscriptionsListener {
fun onActiveSubscription(renewalMessage: String?) fun onActiveSubscription(renewalMessage: String?) {}
fun onAvailableSubscription(skuDetails: SkuDetails) fun onAvailableSubscription(skuDetails: SkuDetails) {}
fun onBillingConnectionReady() fun onBillingConnectionReady() {}
fun onBillingConnectionError(message: String? = null) fun onBillingConnectionError(message: String? = null) {}
} }
class SubscriptionManagerImpl( class SubscriptionManagerImpl(
@ -90,7 +90,9 @@ class SubscriptionManagerImpl(
when (billingResult.responseCode) { when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> { BillingClient.BillingResponseCode.OK -> {
Log.d(this, "acknowledgePurchaseResponseListener OK") Log.d(this, "acknowledgePurchaseResponseListener OK")
syncActiveSubscription() scope.launch(Dispatchers.Default) {
syncActiveSubscription()
}
} }
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> { BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> {
// Billing API version is not supported for the type requested. // Billing API version is not supported for the type requested.
@ -174,7 +176,7 @@ class SubscriptionManagerImpl(
billingClient.launchBillingFlow(activity, billingFlowParams) billingClient.launchBillingFlow(activity, billingFlowParams)
} }
override fun syncActiveSubscription() = scope.launch(Dispatchers.Default) { override suspend fun syncActiveSubscription() = scope.launch(Dispatchers.Default) {
val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context) val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context)
val activePlayStoreSubscription = getActiveSubscriptionAsync().await() val activePlayStoreSubscription = getActiveSubscriptionAsync().await()
@ -258,7 +260,9 @@ class SubscriptionManagerImpl(
private fun handlePurchase(purchase: Purchase) { private fun handlePurchase(purchase: Purchase) {
Log.d(this, "handlePurchase: ${purchase.orderId}") Log.d(this, "handlePurchase: ${purchase.orderId}")
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged) { if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged) {
syncActiveSubscription() scope.launch(Dispatchers.Default) {
syncActiveSubscription()
}
} else if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged) { } else if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged) {
// need to acknowledge first time sub otherwise it will void // need to acknowledge first time sub otherwise it will void
Log.d(this, "acknowledge purchase: ${purchase.orderId}") Log.d(this, "acknowledge purchase: ${purchase.orderId}")

View file

@ -0,0 +1,15 @@
package com.newsblur.util
/**
* Same values as R.string.mark_story_read_values
*/
enum class MarkStoryReadBehavior {
IMMEDIATELY,
SECONDS_5,
SECONDS_10,
SECONDS_20,
SECONDS_30,
SECONDS_45,
SECONDS_60,
MANUALLY,
}

View file

@ -114,19 +114,19 @@ public class NotificationUtils {
// UI on some devices. // UI on some devices.
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// set the requestCode to the story hashcode to prevent the PI re-using the wrong Intent // set the requestCode to the story hashcode to prevent the PI re-using the wrong Intent
PendingIntent pendingIntent = PendingIntent.getActivity(context, story.hashCode(), i, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntentUtils.getImmutableActivity(context, story.hashCode(), i, PendingIntent.FLAG_UPDATE_CURRENT);
Intent dismissIntent = new Intent(context, NotifyDismissReceiver.class); Intent dismissIntent = new Intent(context, NotifyDismissReceiver.class);
dismissIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash); dismissIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash);
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), story.hashCode(), dismissIntent, 0); PendingIntent dismissPendingIntent = PendingIntentUtils.getImmutableBroadcast(context.getApplicationContext(), story.hashCode(), dismissIntent, 0);
Intent saveIntent = new Intent(context, NotifySaveReceiver.class); Intent saveIntent = new Intent(context, NotifySaveReceiver.class);
saveIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash); saveIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash);
PendingIntent savePendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), story.hashCode(), saveIntent, 0); PendingIntent savePendingIntent = PendingIntentUtils.getImmutableBroadcast(context.getApplicationContext(), story.hashCode(), saveIntent, 0);
Intent markreadIntent = new Intent(context, NotifyMarkreadReceiver.class); Intent markreadIntent = new Intent(context, NotifyMarkreadReceiver.class);
markreadIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash); markreadIntent.putExtra(Reading.EXTRA_STORY_HASH, story.storyHash);
PendingIntent markreadPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), story.hashCode(), markreadIntent, 0); PendingIntent markreadPendingIntent = PendingIntentUtils.getImmutableBroadcast(context.getApplicationContext(), story.hashCode(), markreadIntent, 0);
String feedTitle = cursor.getString(cursor.getColumnIndex(DatabaseConstants.FEED_TITLE)); String feedTitle = cursor.getString(cursor.getColumnIndex(DatabaseConstants.FEED_TITLE));
StringBuilder title = new StringBuilder(); StringBuilder title = new StringBuilder();

View file

@ -0,0 +1,29 @@
package com.newsblur.util
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
object PendingIntentUtils {
@JvmStatic
fun getImmutableActivity(
context: Context, requestCode: Int,
intent: Intent, flags: Int): PendingIntent? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(context, requestCode, intent, flags or PendingIntent.FLAG_IMMUTABLE, null)
} else {
PendingIntent.getActivity(context, requestCode, intent, flags, null)
}
@JvmStatic
fun getImmutableBroadcast(
context: Context, requestCode: Int,
intent: Intent, flags: Int): PendingIntent? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(context, requestCode, intent, flags or PendingIntent.FLAG_IMMUTABLE)
} else {
PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
}

View file

@ -63,6 +63,7 @@ public class PrefConstants {
public static final String STORIES_MARK_READ_ON_SCROLL = "pref_mark_read_on_scroll"; public static final String STORIES_MARK_READ_ON_SCROLL = "pref_mark_read_on_scroll";
public static final String STORIES_SHOW_PREVIEWS_STYLE = "pref_show_content_preview_style"; public static final String STORIES_SHOW_PREVIEWS_STYLE = "pref_show_content_preview_style";
public static final String STORIES_THUMBNAIL_STYLE = "pref_thumbnail_style"; public static final String STORIES_THUMBNAIL_STYLE = "pref_thumbnail_style";
public static final String STORY_MARK_READ_BEHAVIOR = "pref_story_mark_read_behavior";
public static final String ENABLE_OFFLINE = "enable_offline"; public static final String ENABLE_OFFLINE = "enable_offline";
public static final String ENABLE_IMAGE_PREFETCH = "enable_image_prefetch"; public static final String ENABLE_IMAGE_PREFETCH = "enable_image_prefetch";

View file

@ -734,7 +734,7 @@ public class PrefsUtils {
return prefs.getBoolean(PrefConstants.STORIES_AUTO_OPEN_FIRST, false); return prefs.getBoolean(PrefConstants.STORIES_AUTO_OPEN_FIRST, false);
} }
public static boolean isMarkReadOnScroll(Context context) { public static boolean isMarkReadOnFeedScroll(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return prefs.getBoolean(PrefConstants.STORIES_MARK_READ_ON_SCROLL, false); return prefs.getBoolean(PrefConstants.STORIES_MARK_READ_ON_SCROLL, false);
} }
@ -1040,4 +1040,13 @@ public class PrefsUtils {
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, Context.MODE_PRIVATE);
return preferences.getString(PrefConstants.PREF_COOKIE, null) != null; return preferences.getString(PrefConstants.PREF_COOKIE, null) != null;
} }
public static MarkStoryReadBehavior getMarkStoryReadBehavior(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return MarkStoryReadBehavior.valueOf(preferences.getString(PrefConstants.STORY_MARK_READ_BEHAVIOR, MarkStoryReadBehavior.IMMEDIATELY.name()));
}
public static boolean isMarkStoryReadImmediately(Context context) {
return getMarkStoryReadBehavior(context).equals(MarkStoryReadBehavior.IMMEDIATELY);
}
} }

View file

@ -15,6 +15,7 @@ import com.newsblur.activity.AllStoriesItemsList;
import com.newsblur.activity.ItemsList; import com.newsblur.activity.ItemsList;
import com.newsblur.activity.WidgetConfig; import com.newsblur.activity.WidgetConfig;
import com.newsblur.util.FeedSet; import com.newsblur.util.FeedSet;
import com.newsblur.util.PendingIntentUtils;
import com.newsblur.util.PrefsUtils; import com.newsblur.util.PrefsUtils;
import com.newsblur.util.WidgetBackground; import com.newsblur.util.WidgetBackground;
@ -88,8 +89,7 @@ public class WidgetProvider extends AppWidgetProvider {
Intent configIntent = new Intent(context, WidgetProvider.class); Intent configIntent = new Intent(context, WidgetProvider.class);
configIntent.setAction(WidgetUtils.ACTION_OPEN_CONFIG); configIntent.setAction(WidgetUtils.ACTION_OPEN_CONFIG);
PendingIntent configIntentTemplate = PendingIntent.getBroadcast(context, WidgetUtils.RC_WIDGET_CONFIG, configIntent, PendingIntent configIntentTemplate = PendingIntentUtils.getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_CONFIG, configIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widget_empty_view, configIntentTemplate); rv.setOnClickPendingIntent(R.id.widget_empty_view, configIntentTemplate);
// This section makes it possible for items to have individualized behavior. // This section makes it possible for items to have individualized behavior.
@ -104,7 +104,7 @@ public class WidgetProvider extends AppWidgetProvider {
touchIntent.setAction(WidgetUtils.ACTION_OPEN_STORY); touchIntent.setAction(WidgetUtils.ACTION_OPEN_STORY);
touchIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); touchIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent touchIntentTemplate = PendingIntent.getBroadcast(context, WidgetUtils.RC_WIDGET_STORY, touchIntent, PendingIntent touchIntentTemplate = PendingIntentUtils.getImmutableBroadcast(context, WidgetUtils.RC_WIDGET_STORY, touchIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
rv.setPendingIntentTemplate(R.id.widget_list, touchIntentTemplate); rv.setPendingIntentTemplate(R.id.widget_list, touchIntentTemplate);

View file

@ -8,6 +8,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.SystemClock; import android.os.SystemClock;
import com.newsblur.util.PendingIntentUtils;
import com.newsblur.util.Log; import com.newsblur.util.Log;
import com.newsblur.util.PrefsUtils; import com.newsblur.util.PrefsUtils;
@ -28,7 +29,7 @@ public class WidgetUtils {
Log.d(TAG, "enableWidgetUpdate"); Log.d(TAG, "enableWidgetUpdate");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = getUpdateIntent(context); Intent intent = getUpdateIntent(context);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, RC_WIDGET_UPDATE, intent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
int widgetUpdateInterval = 1000 * 60 * 5; int widgetUpdateInterval = 1000 * 60 * 5;
long startAlarmAt = SystemClock.currentThreadTimeMillis() + widgetUpdateInterval; long startAlarmAt = SystemClock.currentThreadTimeMillis() + widgetUpdateInterval;
@ -38,7 +39,7 @@ public class WidgetUtils {
public static void disableWidgetUpdate(Context context) { public static void disableWidgetUpdate(Context context) {
Log.d(TAG, "disableWidgetUpdate"); Log.d(TAG, "disableWidgetUpdate");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent); alarmManager.cancel(pendingIntent);
} }
@ -68,7 +69,7 @@ public class WidgetUtils {
} }
public static void checkWidgetUpdateAlarm(Context context) { public static void checkWidgetUpdateAlarm(Context context) {
boolean hasActiveUpdates = PendingIntent.getBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_NO_CREATE) != null; boolean hasActiveUpdates = PendingIntentUtils.getImmutableBroadcast(context, RC_WIDGET_UPDATE, getUpdateIntent(context), PendingIntent.FLAG_NO_CREATE) != null;
if (!hasActiveUpdates) { if (!hasActiveUpdates) {
enableWidgetUpdate(context); enableWidgetUpdate(context);
} }

View file

@ -26,6 +26,7 @@ DEBUG = True
# DEBUG_ASSETS controls JS/CSS asset packaging. Turning this off requires you to run # DEBUG_ASSETS controls JS/CSS asset packaging. Turning this off requires you to run
# `./manage.py collectstatic` first. Turn this on for development so you can see # `./manage.py collectstatic` first. Turn this on for development so you can see
# changes in your JS/CSS. # changes in your JS/CSS.
DEBUG_ASSETS = False # Make sure to run `./manage.py collectstatic` first
DEBUG_ASSETS = False # Make sure to run `./manage.py collectstatic` first DEBUG_ASSETS = False # Make sure to run `./manage.py collectstatic` first
DEBUG_ASSETS = True DEBUG_ASSETS = True

View file

@ -243,7 +243,7 @@ resource "digitalocean_droplet" "node-images" {
image = var.droplet_os image = var.droplet_os
name = "node-images" name = "node-images"
region = var.droplet_region region = var.droplet_region
size = var.droplet_size size = var.droplet_size_15
ssh_keys = [digitalocean_ssh_key.default.fingerprint] ssh_keys = [digitalocean_ssh_key.default.fingerprint]
provisioner "local-exec" { provisioner "local-exec" {
command = "/srv/newsblur/ansible/utils/generate_inventory.py; sleep 120" command = "/srv/newsblur/ansible/utils/generate_inventory.py; sleep 120"