Merge branch 'django1.10' into django1.11

* django1.10: (66 commits)
  Styling new features for dark mode.
  Adding style option for grid height. XS/S/M/L/XL
  Adding applinks to .well-known/apple app json. For #1361.
  #1377 (mark as read for titles only)
  iOS: project upgrade check
  https for all links.
  Saving saved story slugs.
  Slugifying saved story tags that have no slugs.
  Adding ssl to db machines.
  Fixing subscription cancelation error messge.
  Android v10.1b7.
  #1364 In App Review
  Showing addresses in email story share.
  #1369 Notification alerts. Skip old stories
  Android v10.1b6.
  iOS v10.1.1.
  Fixing backgrounds in newsletters.
  Fixing newsltter check which inaccurately added newsletter div to all stories.
  Styling side option buttons in iOS to be cleaner.
  iOS v10.1.
  ...
This commit is contained in:
Samuel Clay 2020-11-09 11:38:35 -05:00
commit 0b92bca89d
179 changed files with 3738 additions and 970 deletions

View file

@ -279,7 +279,7 @@ If you need to move search servers and want to just delete everything in the sea
NewsBlur comes complete with a test suite that tests the functionality of the rss_feeds,
reader, and feed importer. To run the test suite:
./manage.py test --settings=utils.test-settings
./manage.py test --settings=utils.test_settings
## In Case of Downtime

View file

@ -492,7 +492,7 @@ class Profile(models.Model):
return ipn[0].payer_email
def activate_ios_premium(self, product_identifier, transaction_identifier, amount=36):
def activate_ios_premium(self, transaction_identifier=None, amount=36):
payments = PaymentHistory.objects.filter(user=self.user,
payment_identifier=transaction_identifier,
payment_date__gte=datetime.datetime.now()-datetime.timedelta(days=3))
@ -512,7 +512,30 @@ class Profile(models.Model):
if not self.is_premium:
self.activate_premium()
logging.user(self.user, "~FG~BBNew iOS premium subscription: $%s~FW" % product_identifier)
logging.user(self.user, "~FG~BBNew iOS premium subscription: $%s~FW" % amount)
return True
def activate_android_premium(self, order_id=None, amount=36):
payments = PaymentHistory.objects.filter(user=self.user,
payment_identifier=order_id,
payment_date__gte=datetime.datetime.now()-datetime.timedelta(days=3))
if len(payments):
# Already paid
logging.user(self.user, "~FG~BBAlready paid Android premium subscription: $%s~FW" % amount)
return False
PaymentHistory.objects.create(user=self.user,
payment_date=datetime.datetime.now(),
payment_amount=amount,
payment_provider='android-subscription',
payment_identifier=order_id)
self.setup_premium_history()
if not self.is_premium:
self.activate_premium()
logging.user(self.user, "~FG~BBNew Android premium subscription: $%s~FW" % amount)
return True
@classmethod

View file

@ -22,6 +22,7 @@ urlpatterns = [
url(r'^never_expire_premium/?', views.never_expire_premium, name='profile-never-expire-premium'),
url(r'^upgrade_premium/?', views.upgrade_premium, name='profile-upgrade-premium'),
url(r'^save_ios_receipt/?', views.save_ios_receipt, name='save-ios-receipt'),
url(r'^save_android_receipt/?', views.save_android_receipt, name='save-android-receipt'),
url(r'^update_payment_history/?', views.update_payment_history, name='profile-update-payment-history'),
url(r'^delete_account/?', views.delete_account, name='profile-delete-account'),
url(r'^forgot_password_return/?', views.forgot_password_return, name='profile-forgot-password-return'),

View file

@ -106,16 +106,17 @@ def signup(request):
recaptcha = request.POST.get('g-recaptcha-response', None)
recaptcha_error = None
if not recaptcha:
recaptcha_error = "Please hit the \"I'm not a robot\" button."
else:
response = requests.post('https://www.google.com/recaptcha/api/siteverify', {
'secret': settings.RECAPTCHA_SECRET_KEY,
'response': recaptcha,
})
result = response.json()
if not result['success']:
recaptcha_error = "Really, please hit the \"I'm not a robot\" button."
if settings.ENFORCE_SIGNUP_CAPTCHA:
if not recaptcha:
recaptcha_error = "Please hit the \"I'm not a robot\" button."
else:
response = requests.post('https://www.google.com/recaptcha/api/siteverify', {
'secret': settings.RECAPTCHA_SECRET_KEY,
'response': recaptcha,
})
result = response.json()
if not result['success']:
recaptcha_error = "Really, please hit the \"I'm not a robot\" button."
if request.method == "POST":
form = SignupForm(data=request.POST, prefix="signup")
@ -331,7 +332,7 @@ def save_ios_receipt(request):
logging.user(request, "~BM~FBSaving iOS Receipt: %s %s" % (product_identifier, transaction_identifier))
paid = request.user.profile.activate_ios_premium(product_identifier, transaction_identifier)
paid = request.user.profile.activate_ios_premium(transaction_identifier)
if paid:
logging.user(request, "~BM~FBSending iOS Receipt email: %s %s" % (product_identifier, transaction_identifier))
subject = "iOS Premium: %s (%s)" % (request.user.profile, product_identifier)
@ -343,6 +344,26 @@ def save_ios_receipt(request):
return request.user.profile
@ajax_login_required
@json.json_view
def save_android_receipt(request):
order_id = request.POST.get('order_id')
product_id = request.POST.get('product_id')
logging.user(request, "~BM~FBSaving Android Receipt: %s %s" % (product_id, order_id))
paid = request.user.profile.activate_android_premium(order_id)
if paid:
logging.user(request, "~BM~FBSending Android Receipt email: %s %s" % (product_id, order_id))
subject = "Android Premium: %s (%s)" % (request.user.profile, product_identifier)
message = """User: %s (%s) -- Email: %s, product: %s, order: %s, receipt: %s""" % (request.user.username, request.user.pk, request.user.email, product_id, order_id, receipt)
mail_admins(subject, message, fail_silently=True)
else:
logging.user(request, "~BM~FBNot sending Android Receipt email, already paid: %s %s" % (product_id, order_id))
return request.user.profile
@login_required
def stripe_form(request):
user = request.user
@ -692,4 +713,4 @@ def ios_subscription_status(request):
return {
"code": 1
}
}

View file

@ -1016,10 +1016,15 @@ def starred_story_hashes(request):
mstories = MStarredStory.objects(
user_id=user.pk
).only('story_hash', 'starred_date').order_by('-starred_date')
).only('story_hash', 'starred_date', 'starred_updated').order_by('-starred_date')
if include_timestamps:
story_hashes = [(s.story_hash, s.starred_date.strftime("%s")) for s in mstories]
story_hashes = []
for s in mstories:
date = s.starred_date
if s.starred_updated:
date = s.starred_updated
story_hashes.append((s.story_hash, date.strftime("%s")))
else:
story_hashes = [s.story_hash for s in mstories]
@ -2653,8 +2658,8 @@ def send_story_email(request):
share_user_profile.save_sent_email()
logging.user(request, '~BMSharing story by email to %s recipient%s: ~FY~SB%s~SN~BM~FY/~SB%s' %
(len(to_addresses), '' if len(to_addresses) == 1 else 's',
logging.user(request, '~BMSharing story by email to %s recipient%s (%s): ~FY~SB%s~SN~BM~FY/~SB%s' %
(len(to_addresses), '' if len(to_addresses) == 1 else 's', to_addresses,
story['story_title'][:50], feed and feed.feed_title[:50]))
return {'code': code, 'message': message}

View file

@ -2834,6 +2834,7 @@ class MStarredStory(mongo.DynamicDocument):
mongoengine's inheritance model on every single row."""
user_id = mongo.IntField(unique_with=('story_guid',))
starred_date = mongo.DateTimeField()
starred_updated = mongo.DateTimeField()
story_feed_id = mongo.IntField()
story_date = mongo.DateTimeField()
story_title = mongo.StringField(max_length=1024)
@ -2880,7 +2881,8 @@ class MStarredStory(mongo.DynamicDocument):
self.story_original_content_z = zlib.compress(self.story_original_content)
self.story_original_content = None
self.story_hash = self.feed_guid_hash
self.starred_updated = datetime.datetime.now()
return super(MStarredStory, self).save(*args, **kwargs)
@classmethod
@ -3011,6 +3013,11 @@ class MStarredStoryCounts(mongo.Document):
secret_token = user.profile.secret_token
slug = self.slug if self.slug else ""
if not self.slug and self.tag:
slug = slugify(self.tag)
self.slug = slug
self.save()
return "%s/reader/starred_rss/%s/%s/%s" % (settings.NEWSBLUR_URL, self.user_id,
secret_token, slug)

View file

@ -128,6 +128,13 @@
<activity
android:name=".activity.InAppBrowser" />
<activity
android:name=".activity.Premium" />
<activity
android:name=".activity.MuteConfig"
android:launchMode="singleTask"/>
<activity
android:name=".activity.SearchForFeeds" android:launchMode="singleTop" >
@ -174,7 +181,7 @@
android:exported="false">
</receiver>
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="com.newsblur.fileprovider"
android:exported="false"
android:grantUriPermissions="true">

View file

@ -1,4 +1,5 @@
buildscript {
ext.kotlin_version = '1.4.10'
repositories {
mavenCentral()
maven {
@ -8,7 +9,8 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -18,33 +20,40 @@ repositories {
url 'https://maven.google.com'
}
jcenter()
google()
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'checkstyle'
dependencies {
implementation 'com.android.support:support-core-utils:28.0.0'
implementation 'com.android.support:support-fragment:28.0.0'
implementation 'com.android.support:support-core-ui:28.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.fragment:fragment:1.2.5'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.billingclient:billing:3.0.0'
implementation 'nl.dionsegijn:konfetti:1.2.2'
implementation 'com.github.jinatonic.confetti:confetti:1.1.2'
implementation 'com.google.android.play:core:1.8.2'
}
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId "com.newsblur"
minSdkVersion 21
targetSdkVersion 28
versionCode 168
targetSdkVersion 29
versionCode 176
versionName "10.1"
}
compileOptions.with {
sourceCompatibility = JavaVersion.VERSION_1_7
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
viewBinding.enabled = true
android.buildFeatures.viewBinding = true
sourceSets {
main {

View file

@ -0,0 +1,2 @@
android.enableJetifier=true
android.useAndroidX=true

View file

@ -0,0 +1,20 @@
{
"version": 1,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.newsblur",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"properties": [],
"versionCode": 171,
"versionName": "171",
"enabled": true,
"outputFile": "NewsBlur-release.apk"
}
]
}

View file

@ -9,8 +9,7 @@
<item
android:top="0.5dp"
android:bottom="0.5dp">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<shape android:shape="rectangle">
<solid android:color="@color/dark_feed_background_selected_end"/>
</shape>
</item>

View file

@ -9,8 +9,7 @@
<item
android:top="0.5dp"
android:bottom="0.5dp">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<shape android:shape="rectangle">
<solid android:color="@color/feed_background_selected_end"/>
</shape>
</item>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M19,18l2,1V3c0,-1.1 -0.9,-2 -2,-2H8.99C7.89,1 7,1.9 7,3h10c1.1,0 2,0.9 2,2v13zM15,5H5c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3V7c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M8.1,13.34l2.83,-2.83L3.91,3.5c-1.56,1.56 -1.56,4.09 0,5.66l4.19,4.18zM14.88,11.53c1.53,0.71 3.68,0.21 5.27,-1.38 1.91,-1.91 2.28,-4.65 0.81,-6.12 -1.46,-1.46 -4.2,-1.1 -6.12,0.81 -1.59,1.59 -2.09,3.74 -1.38,5.27L3.7,19.87l1.41,1.41L12,14.41l6.88,6.88 1.41,-1.41L13.41,13l1.47,-1.47z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M21,9A1,1 0,0 1,22 10A1,1 0,0 1,21 11H16.53L16.4,12.21L14.2,17.15C14,17.65 13.47,18 12.86,18H8.5C7.7,18 7,17.27 7,16.5V10C7,9.61 7.16,9.26 7.43,9L11.63,4.1L12.4,4.84C12.6,5.03 12.72,5.29 12.72,5.58L12.69,5.8L11,9H21M2,18V10H5V18H2Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M6.18,17.82m-2.18,0a2.18,2.18 0,1 1,4.36 0a2.18,2.18 0,1 1,-4.36 0" />
<path
android:fillColor="#A6A6A6"
android:pathData="M4,4.44v2.83c7.03,0 12.73,5.7 12.73,12.73h2.83c0,-8.59 -6.97,-15.56 -15.56,-15.56zM4,10.1v2.83c3.9,0 7.07,3.17 7.07,7.07h2.83c0,-5.47 -4.43,-9.9 -9.9,-9.9z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#A6A6A6"
android:pathData="M14,17L4,17v2h10v-2zM20,9L4,9v2h16L20,9zM4,15h16v-2L4,13v2zM4,5v2h16L20,5L4,5z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View file

@ -27,6 +27,13 @@
android:layout_below="@id/itemlist_search_query"
/>
<include layout="@layout/row_fleuron"
android:id="@+id/footer_fleuron"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/itemlist_search_query"
android:visibility="gone"/>
<TextView
android:id="@+id/itemlist_sync_status"
android:layout_width="fill_parent"

View file

@ -167,7 +167,7 @@
/>
<!-- The scrollable and pull-able feed list. -->
<android.support.v4.widget.SwipeRefreshLayout
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -180,7 +180,7 @@
android:layout_height="match_parent"
android:tag="folderFeedListFragment" />
</android.support.v4.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- top_bar_border -->
<View

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<FrameLayout
android:id="@+id/container_sites_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="16dp"
android:visibility="gone">
<TextView
android:id="@+id/text_reset_sites"
style="?linkText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:padding="4dp"
android:text="@string/mute_config_reset_button"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_sites"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:text="@string/mute_config_sites"
android:textColor="@color/positive"
android:textSize="12sp"
android:textStyle="bold" />
</FrameLayout>
<ExpandableListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:groupIndicator="@null" />
<TextView
android:id="@+id/text_sync_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/status_overlay_background"
android:gravity="center"
android:padding="2dp"
android:textColor="@color/status_overlay_text"
android:textSize="14sp"
android:visibility="gone" />
</FrameLayout>

View file

@ -0,0 +1,293 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<LinearLayout
android:id="@+id/container_going_premium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/premium_title_going_premium"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:lineSpacingExtra="@dimen/extra_line_spacing"
android:text="@string/premium_subtitle"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_policies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:lineSpacingExtra="@dimen/extra_line_spacing"
android:text="@string/premium_policies"
android:textSize="12sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_hand_pointing_right" />
<LinearLayout
android:id="@+id/container_sub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/text_sub_title"
style="?linkText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/premium_subscription_title"
android:textSize="14sp" />
<TextView
android:id="@+id/text_sub_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="4dp"
android:text="@string/premium_subscription_price"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/text_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/loading"
android:textSize="18sp" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_sync" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_sync" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_folder" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_read_by_folder" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_search" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_search" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_bookmark" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_searchable_tags" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_lock" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_privacy_options" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_rss_feed" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_custom_rss" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_text" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_text_view" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_dining" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="40dp"
android:text="@string/premium_shiloh" />
</FrameLayout>
<ImageView
android:id="@+id/img_shiloh"
android:layout_width="104dp"
android:layout_height="104dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:scaleType="centerCrop" />
</LinearLayout>
<FrameLayout
android:id="@+id/container_gone_premium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
android:gravity="center_horizontal"
android:text="@string/premium_title_gone_premium"
android:textSize="40sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_subscription_renewal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="320dp"
android:gravity="center_horizontal"
android:lineSpacingExtra="@dimen/extra_line_spacing"
android:textSize="18sp"
android:visibility="gone" />
</FrameLayout>
<nl.dionsegijn.konfetti.KonfettiView
android:id="@+id/konfetti"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</ScrollView>

View file

@ -10,13 +10,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/activity_details_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/profile_details">
<android.support.v4.view.PagerTitleStrip
<androidx.viewpager.widget.PagerTitleStrip
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
@ -24,6 +24,6 @@
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
</androidx.viewpager.widget.ViewPager>
</RelativeLayout>

View file

@ -78,11 +78,11 @@
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_folders"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>

View file

@ -8,7 +8,7 @@
android:id="@+id/choose_folders_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?divider"
style="?android:listDivider"
android:dividerHeight="2dp" />
</RelativeLayout>

View file

@ -44,7 +44,7 @@
android:layout_height="6dp"
/>
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/itemgridfragment_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -130,7 +130,7 @@
android:id="@+id/share_bar_underline"
android:layout_width="match_parent"
android:layout_height="3dp"
style="?divider"
style="?android:divider"
android:visibility="gone" />
<TextView

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
>
<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/reading_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -2,17 +2,41 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
>
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/fleuron"
android:src="@drawable/fleuron"
android:layout_height="32dp"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
/>
android:src="@drawable/fleuron" />
<LinearLayout
android:id="@+id/container_subscribe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/text_subscription"
style="?defaultText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/premium_subscribers_folder" />
<TextView
style="?linkText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_horizontal"
android:text="@string/premium_subscribers" />
</LinearLayout>
</LinearLayout>

View file

@ -12,6 +12,7 @@
android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:clickable="false"
android:focusable="false" />
@ -20,7 +21,6 @@
android:layout_width="19dp"
android:layout_height="19dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:contentDescription="@string/description_row_feed_icon"
android:scaleType="centerCrop" />
@ -39,4 +39,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img_toggle"
android:layout_width="32dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:contentDescription="@string/description_row_feed_mute_toggle"
android:visibility="gone" />
</LinearLayout>

View file

@ -63,6 +63,9 @@
</group>
</menu>
</item>
<item android:id="@+id/menu_statistics"
android:title="@string/menu_statistics"
android:showAsAction="never" />
<item android:id="@+id/menu_delete_feed"
android:title="@string/menu_delete_feed"
android:showAsAction="never" />

View file

@ -21,6 +21,10 @@
android:title="@string/settings"
android:showAsAction="never" />
<item android:id="@+id/menu_mute_sites"
android:title="@string/mute_sites"
android:showAsAction="never" />
<item android:id="@+id/menu_widget"
android:title="@string/widget"
android:showAsAction="never" />
@ -43,6 +47,10 @@
android:title="@string/menu_loginas"
android:showAsAction="never"
android:visible="false"/>
<item android:id="@+id/menu_premium_account"
android:title="@string/menu_premium_account"
android:showAsAction="never" />
<item android:id="@+id/menu_logout"
android:title="@string/menu_logout"

View file

@ -54,6 +54,12 @@
</group>
</menu>
</item>
<item
android:id="@+id/menu_mute_all"
android:title="@string/menu_mute_all" />
<item
android:id="@+id/menu_mute_none"
android:title="@string/menu_mute_none" />
<item
android:id="@+id/menu_select_all"
android:title="@string/menu_select_all" />

View file

@ -23,7 +23,6 @@
<attr name="commentsHeader" format="string" />
<attr name="rowBorderTop" format="string" />
<attr name="rowBorderBottom" format="string" />
<attr name="divider" format="string" />
<attr name="profileCount" format="string" />
<attr name="profileActivityList" format="string" />
<attr name="itemHeaderDivider" format="string" />

View file

@ -2,4 +2,5 @@
<resources>
<dimen name="thumbnails_small_size">50dp</dimen>
<dimen name="thumbnails_size">90dp</dimen>
<dimen name="extra_line_spacing">4dp</dimen>
</resources>

View file

@ -16,8 +16,7 @@
<string name="login_next">Next</string>
<string name="title_feed_search">Search for feeds</string>
<string name="add_feed_message">Add \"%s\" to your feeds?</string>
<string name="loading">Loading…</string>
<string name="orig_text_loading">Fetching story text…</string>
@ -29,6 +28,7 @@
<string name="description_profile_picture">The user\'s profile picture</string>
<string name="description_row_folder_icon">folder icon</string>
<string name="description_row_feed_icon">feed icon</string>
<string name="description_row_feed_mute_toggle">feed mute toggle</string>
<string name="description_activity_icon">An icon illustrating the user\'s activity</string>
<string name="description_follow_button">Follow or unfollow a user</string>
<string name="description_comment_user">Comment user image</string>
@ -135,6 +135,7 @@
<string name="menu_send_story_full">Send story to…</string>
<string name="menu_mark_feed_as_read">Mark feed as read</string>
<string name="menu_delete_feed">Delete feed</string>
<string name="menu_statistics">Statistics</string>
<string name="menu_delete_saved_search">Delete saved search</string>
<string name="menu_unfollow">Unfollow user</string>
<string name="menu_choose_folders">Choose folders</string>
@ -184,6 +185,8 @@
<string name="menu_folder_view">Folder View</string>
<string name="menu_folder_view_nested">Nested</string>
<string name="menu_folder_view_flat">Flat</string>
<string name="menu_mute_all">Mute All</string>
<string name="menu_mute_none">Mute None</string>
<string name="menu_select_all">Select All</string>
<string name="menu_select_none">Select None</string>
<string name="menu_widget_background">Widget Background</string>
@ -213,6 +216,7 @@
<string name="menu_feedback_post">Create a feedback post</string>
<string name="menu_feedback_email">Email a bug report</string>
<string name="menu_theme_choose">Theme…</string>
<string name="menu_premium_account">Premium Account</string>
<string name="description_add_new_folder_icon">Add new folder icon</string>
@ -255,10 +259,34 @@
<string name="feed_stories_per_month">%d stories/month</string>
<string name="settings">Preferences</string>
<string name="mute_sites">Mute Sites</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="premium_subscribers_folder">Reading by folder is only available to</string>
<string name="premium_subscribers_search">Search is only available to</string>
<string name="premium_subscribers">premium subscribers</string>
<string name="premium_toolbar_title">NewsBlur Premium</string>
<string name="premium_title_going_premium">Thank you so much for going premium!</string>
<string name="premium_title_gone_premium">Thank you for going premium!</string>
<string name="premium_subscription_renewal">Your premium subscription is set to\nrenew on %s</string>
<string name="premium_subscription_expiration">Your premium subscription is set\nto expire on %s</string>
<string name="premium_subscription_no_expiration">Your premium subscription is set\nto never expire. Whoa!</string>
<string name="premium_subtitle">Upgrading to a NewsBlur premium subscription gives you all of these features. Payments will be charged to your Play Store account at confirmation of purchase. Subscription renew unless auto-renew is turned off at least 24 hours before the end of the current period. Cancel at any time from Account Settings in Play Store.</string>
<string name="premium_policies"><![CDATA[See NewsBlur\'s <a href="https://newsblur.com/privacy">privacy policy</a> and <a href="https://newsblur.com/tos">terms of use</a> for details.]]></string>
<string name="premium_subscription_title">NewsBlur Premium Subscription</string>
<string name="premium_subscription_price">$35.99 per year ($3.00/month)</string>
<string name="premium_subscription_details_error">Error retrieving subscription details</string>
<string name="premium_sync">Sites updated up to 10x more often</string>
<string name="premium_read_by_folder">River of News (reading by folder)</string>
<string name="premium_search">Search sites and folders</string>
<string name="premium_searchable_tags">Save stories with searchable tags</string>
<string name="premium_privacy_options">Privacy options for your blurblog</string>
<string name="premium_custom_rss">Custom RSS feeds for folders and saves stories</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="settings_cat_offline">Offline</string>
<string name="settings_enable_offline">Download Stories</string>
@ -268,6 +296,12 @@
<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="mute_config_title">You can follow up to 64 sites with a free standard account</string>
<string name="mute_config_message">Please mute %d sites or reset to most popular sites.</string>
<string name="mute_config_reset_button">RESET TO POPULAR SITES</string>
<string name="mute_config_upgrade">UPGRADE</string>
<string name="mute_config_sites">%1$s/%2$s</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_opt_any">Any Network</string>
@ -364,7 +398,6 @@
<string name="settings_reading">Reading</string>
<string name="settings_immersive_enter_single_tap">Immersive Mode Via Single Tap</string>
<string name="settings_show_content_preview">Show Content Preview Text</string>
<string name="settings_show_thumbnails">Show Image Preview Thumbnails</string>
<string name="settings_thumbnails_style">Image Preview Thumbnails</string>
<string name="settings_notifications">Notifications</string>
<string name="settings_enable_notifications">Enable Notifications</string>
@ -461,12 +494,14 @@
<string name="gest_action_markunread">Mark Story Unread</string>
<string name="gest_action_save">Save Story</string>
<string name="gest_action_unsave">Unsave Story</string>
<string name="gest_action_statistics">Statistics</string>
<string-array name="ltr_gesture_action_entries">
<item>@string/gest_action_none</item>
<item>@string/gest_action_markread</item>
<item>@string/gest_action_markunread</item>
<item>@string/gest_action_save</item>
<item>@string/gest_action_unsave</item>
<item>@string/gest_action_statistics</item>
</string-array>
<string-array name="ltr_gesture_action_values">
<item>GEST_ACTION_NONE</item>
@ -474,6 +509,7 @@
<item>GEST_ACTION_MARKUNREAD</item>
<item>GEST_ACTION_SAVE</item>
<item>GEST_ACTION_UNSAVE</item>
<item>GEST_ACTION_STATISTICS</item>
</string-array>
<string name="ltr_gesture_action_value">GEST_ACTION_MARKREAD</string>
@ -484,6 +520,7 @@
<item>@string/gest_action_markunread</item>
<item>@string/gest_action_save</item>
<item>@string/gest_action_unsave</item>
<item>@string/gest_action_statistics</item>
</string-array>
<string-array name="rtl_gesture_action_values">
<item>GEST_ACTION_NONE</item>
@ -491,6 +528,7 @@
<item>GEST_ACTION_MARKUNREAD</item>
<item>GEST_ACTION_SAVE</item>
<item>GEST_ACTION_UNSAVE</item>
<item>GEST_ACTION_STATISTICS</item>
</string-array>
<string name="rtl_gesture_action_value">GEST_ACTION_MARKUNREAD</string>
@ -556,7 +594,5 @@
<string name="story_notification_channel_id">story_notification_channel</string>
<string name="story_notification_channel_name">New Stories</string>
<string name="save_widget">Save Widget</string>
<string name="select_feed">Select Feed</string>
<string name="go_to_feed">Go to feed</string>
</resources>

View file

@ -1,9 +1,9 @@
package com.newsblur.activity;
import android.content.res.Resources;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.newsblur.R;
import com.newsblur.domain.UserDetails;
@ -21,7 +21,7 @@ public class ActivityDetailsPagerAdapter extends FragmentPagerAdapter {
private final Profile profile;
public ActivityDetailsPagerAdapter(FragmentManager fragmentManager, Profile profile) {
super(fragmentManager);
super(fragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.profile = profile;

View file

@ -3,7 +3,7 @@ package com.newsblur.activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.View;
import com.newsblur.R;

View file

@ -2,8 +2,8 @@ package com.newsblur.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@ -46,6 +46,7 @@ public class AddSocial extends NbActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (resultCode) {
case AddTwitter.TWITTER_AUTHED:
addSocialFragment.setTwitterAuthed();

View file

@ -0,0 +1,193 @@
package com.newsblur.activity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import androidx.loader.content.Loader;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedOrderFilter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.FolderViewFilter;
import com.newsblur.util.ListOrderFilter;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.WidgetBackground;
import com.newsblur.widget.WidgetUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
abstract public class FeedChooser extends NbActivity {
protected FeedChooserAdapter adapter;
protected ArrayList<Feed> feeds;
protected ArrayList<Folder> folders;
protected Map<String, Feed> feedMap = new HashMap<>();
protected ArrayList<String> folderNames = new ArrayList<>();
protected ArrayList<ArrayList<Feed>> folderChildren = new ArrayList<>();
abstract void bindLayout();
abstract void setupList();
abstract void processFeeds(Cursor cursor);
abstract void processData();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindLayout();
getActionBar().setDisplayHomeAsUpEnabled(true);
setupList();
loadFeeds();
loadFolders();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_feed_chooser, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
ListOrderFilter listOrderFilter = PrefsUtils.getFeedChooserListOrder(this);
if (listOrderFilter == ListOrderFilter.ASCENDING) {
menu.findItem(R.id.menu_sort_order_ascending).setChecked(true);
} else if (listOrderFilter == ListOrderFilter.DESCENDING) {
menu.findItem(R.id.menu_sort_order_descending).setChecked(true);
}
FeedOrderFilter feedOrderFilter = PrefsUtils.getFeedChooserFeedOrder(this);
if (feedOrderFilter == FeedOrderFilter.NAME) {
menu.findItem(R.id.menu_sort_by_name).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS) {
menu.findItem(R.id.menu_sort_by_subs).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH) {
menu.findItem(R.id.menu_sort_by_stories_month).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY) {
menu.findItem(R.id.menu_sort_by_recent_story).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.OPENS) {
menu.findItem(R.id.menu_sort_by_number_opens).setChecked(true);
}
FolderViewFilter folderViewFilter = PrefsUtils.getFeedChooserFolderView(this);
if (folderViewFilter == FolderViewFilter.NESTED) {
menu.findItem(R.id.menu_folder_view_nested).setChecked(true);
} else if (folderViewFilter == FolderViewFilter.FLAT) {
menu.findItem(R.id.menu_folder_view_flat).setChecked(true);
}
WidgetBackground widgetBackground = PrefsUtils.getWidgetBackground(this);
if (widgetBackground == WidgetBackground.DEFAULT) {
menu.findItem(R.id.menu_widget_background_default).setChecked(true);
} else if (widgetBackground == WidgetBackground.TRANSPARENT) {
menu.findItem(R.id.menu_widget_background_transparent).setChecked(true);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.menu_sort_order_ascending:
replaceListOrderFilter(ListOrderFilter.ASCENDING);
return true;
case R.id.menu_sort_order_descending:
replaceListOrderFilter(ListOrderFilter.DESCENDING);
return true;
case R.id.menu_sort_by_name:
replaceFeedOrderFilter(FeedOrderFilter.NAME);
return true;
case R.id.menu_sort_by_subs:
replaceFeedOrderFilter(FeedOrderFilter.SUBSCRIBERS);
return true;
case R.id.menu_sort_by_recent_story:
replaceFeedOrderFilter(FeedOrderFilter.RECENT_STORY);
return true;
case R.id.menu_sort_by_stories_month:
replaceFeedOrderFilter(FeedOrderFilter.STORIES_MONTH);
return true;
case R.id.menu_sort_by_number_opens:
replaceFeedOrderFilter(FeedOrderFilter.OPENS);
return true;
case R.id.menu_folder_view_nested:
replaceFolderView(FolderViewFilter.NESTED);
return true;
case R.id.menu_folder_view_flat:
replaceFolderView(FolderViewFilter.FLAT);
return true;
case R.id.menu_widget_background_default:
setWidgetBackground(WidgetBackground.DEFAULT);
return true;
case R.id.menu_widget_background_transparent:
setWidgetBackground(WidgetBackground.TRANSPARENT);
default:
return super.onOptionsItemSelected(item);
}
}
protected void setAdapterData() {
adapter.setData(this.folderNames, this.folderChildren, this.feeds);
}
private void replaceFeedOrderFilter(FeedOrderFilter feedOrderFilter) {
PrefsUtils.setFeedChooserFeedOrder(this, feedOrderFilter);
adapter.replaceFeedOrder(feedOrderFilter);
}
private void replaceListOrderFilter(ListOrderFilter listOrderFilter) {
PrefsUtils.setFeedChooserListOrder(this, listOrderFilter);
adapter.replaceListOrder(listOrderFilter);
}
private void replaceFolderView(FolderViewFilter folderViewFilter) {
PrefsUtils.setFeedChooserFolderView(this, folderViewFilter);
adapter.replaceFolderView(folderViewFilter);
setAdapterData();
}
private void setWidgetBackground(WidgetBackground widgetBackground) {
PrefsUtils.setWidgetBackground(this, widgetBackground);
WidgetUtils.updateWidget(this);
}
private void loadFeeds() {
Loader<Cursor> loader = FeedUtils.dbHelper.getFeedsLoader();
loader.registerListener(loader.getId(), (loader1, cursor) -> processFeeds(cursor));
loader.startLoading();
}
private void loadFolders() {
Loader<Cursor> loader = FeedUtils.dbHelper.getFoldersLoader();
loader.registerListener(loader.getId(), (loader1, cursor) -> processFolders(cursor));
loader.startLoading();
}
private void processFolders(Cursor cursor) {
ArrayList<Folder> folders = new ArrayList<>();
while (cursor != null && cursor.moveToNext()) {
Folder folder = Folder.fromCursor(cursor);
if (!folder.feedIds.isEmpty()) {
folders.add(folder);
}
}
this.folders = folders;
Collections.sort(this.folders, (o1, o2) -> Folder.compareFolderNames(o1.flatName(), o2.flatName()));
processData();
}
}

View file

@ -0,0 +1,252 @@
package com.newsblur.activity;
import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedOrderFilter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.FolderViewFilter;
import com.newsblur.util.ListOrderFilter;
import com.newsblur.util.PrefsUtils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
public class FeedChooserAdapter extends BaseExpandableListAdapter {
protected final static int defaultTextSizeChild = 14;
protected final static int defaultTextSizeGroup = 13;
protected Set<String> feedIds = new HashSet<>();
protected ArrayList<String> folderNames = new ArrayList<>();
protected ArrayList<ArrayList<Feed>> folderChildren = new ArrayList<>();
protected FolderViewFilter folderViewFilter;
protected ListOrderFilter listOrderFilter;
protected FeedOrderFilter feedOrderFilter;
protected float textSize;
FeedChooserAdapter(Context context) {
folderViewFilter = PrefsUtils.getFeedChooserFolderView(context);
listOrderFilter = PrefsUtils.getFeedChooserListOrder(context);
feedOrderFilter = PrefsUtils.getFeedChooserFeedOrder(context);
textSize = PrefsUtils.getListTextSize(context);
}
@Override
public int getGroupCount() {
return folderNames.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return folderChildren.get(groupPosition).size();
}
@Override
public String getGroup(int groupPosition) {
return folderNames.get(groupPosition);
}
@Override
public Feed getChild(int groupPosition, int childPosition) {
return folderChildren.get(groupPosition).get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return folderNames.get(groupPosition).hashCode();
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return folderChildren.get(groupPosition).get(childPosition).hashCode();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, final ViewGroup parent) {
String folderName = folderNames.get(groupPosition);
if (folderName.equals(AppConstants.ROOT_FOLDER)) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_root_folder, parent, false);
} else {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_folder, parent, false);
TextView textName = convertView.findViewById(R.id.text_folder_name);
textName.setTextSize(textSize * defaultTextSizeGroup);
textName.setText(folderName);
}
((ExpandableListView) parent).expandGroup(groupPosition);
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_feed, parent, false);
}
final Feed feed = folderChildren.get(groupPosition).get(childPosition);
TextView textTitle = convertView.findViewById(R.id.text_title);
TextView textDetails = convertView.findViewById(R.id.text_details);
final CheckBox checkBox = convertView.findViewById(R.id.check_box);
ImageView img = convertView.findViewById(R.id.img);
textTitle.setTextSize(textSize * defaultTextSizeChild);
textDetails.setTextSize(textSize * defaultTextSizeChild);
textTitle.setText(feed.title);
checkBox.setChecked(feedIds.contains(feed.feedId));
if (feedOrderFilter == FeedOrderFilter.NAME || feedOrderFilter == FeedOrderFilter.OPENS) {
textDetails.setText(parent.getContext().getString(R.string.feed_opens, feed.feedOpens));
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS) {
textDetails.setText(parent.getContext().getString(R.string.feed_subscribers, feed.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH) {
textDetails.setText(parent.getContext().getString(R.string.feed_stories_per_month, feed.storiesPerMonth));
} else {
// FeedOrderFilter.RECENT_STORY
try {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date dateTime = dateFormat.parse(feed.lastStoryDate);
CharSequence relativeTimeString = DateUtils.getRelativeTimeSpanString(dateTime.getTime(), System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
textDetails.setText(relativeTimeString);
} catch (Exception e) {
textDetails.setText(feed.lastStoryDate);
}
}
FeedUtils.iconLoader.displayImage(feed.faviconUrl, img, 0, false, img.getHeight(), true);
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public boolean areAllItemsEnabled() {
return super.areAllItemsEnabled();
}
protected void setData(ArrayList<String> activeFoldersNames, ArrayList<ArrayList<Feed>> activeFolderChildren, ArrayList<Feed> feeds) {
if (folderViewFilter == FolderViewFilter.NESTED) {
this.folderNames = activeFoldersNames;
this.folderChildren = activeFolderChildren;
} else {
this.folderNames = new ArrayList<>(1);
this.folderNames.add(AppConstants.ROOT_FOLDER);
this.folderChildren = new ArrayList<>();
this.folderChildren.add(feeds);
}
this.notifyDataChanged();
}
protected void replaceFeedOrder(FeedOrderFilter feedOrderFilter) {
this.feedOrderFilter = feedOrderFilter;
notifyDataChanged();
}
protected void replaceListOrder(ListOrderFilter listOrderFilter) {
this.listOrderFilter = listOrderFilter;
notifyDataChanged();
}
protected void replaceFolderView(FolderViewFilter folderViewFilter) {
this.folderViewFilter = folderViewFilter;
}
protected void notifyDataChanged() {
for (ArrayList<Feed> feedList : this.folderChildren) {
Collections.sort(feedList, getListComparator());
}
this.notifyDataSetChanged();
}
protected void setFeedIds(Set<String> feedIds) {
this.feedIds.clear();
this.feedIds.addAll(feedIds);
}
protected void replaceFeedIds(Set<String> feedIds) {
setFeedIds(feedIds);
this.notifyDataSetChanged();
}
private Comparator<Feed> getListComparator() {
return (o1, o2) -> {
if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.ASCENDING) {
return o1.title.compareTo(o2.title);
} else if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.DESCENDING) {
return o2.title.compareTo(o1.title);
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.valueOf(o1.subscribers).compareTo(Integer.valueOf(o2.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.valueOf(o2.subscribers).compareTo(Integer.valueOf(o1.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.feedOpens, o2.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.compare(o2.feedOpens, o1.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.ASCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.DESCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.storiesPerMonth, o2.storiesPerMonth);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.compare(o2.storiesPerMonth, o1.storiesPerMonth);
}
return o1.title.compareTo(o2.title);
};
}
private int compareLastStoryDateTimes(String firstDateTime, String secondDateTime, ListOrderFilter listOrderFilter) {
try {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// found null last story date times on feeds
if (TextUtils.isEmpty(firstDateTime)) {
firstDateTime = "2000-01-01 00:00:00";
}
if (TextUtils.isEmpty(secondDateTime)) {
secondDateTime = "2000-01-01 00:00:00";
}
Date firstDate = dateFormat.parse(firstDateTime);
Date secondDate = dateFormat.parse(secondDateTime);
if (listOrderFilter == ListOrderFilter.ASCENDING) {
return firstDate.compareTo(secondDate);
} else {
return secondDate.compareTo(firstDate);
}
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
}

View file

@ -3,10 +3,14 @@ package com.newsblur.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.play.core.review.ReviewInfo;
import com.google.android.play.core.review.ReviewManager;
import com.google.android.play.core.review.ReviewManagerFactory;
import com.google.android.play.core.tasks.Task;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.fragment.DeleteFeedFragment;
@ -14,6 +18,7 @@ import com.newsblur.fragment.FeedIntelTrainerFragment;
import com.newsblur.fragment.RenameDialogFragment;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
public class FeedItemsList extends ItemsList {
@ -22,6 +27,8 @@ public class FeedItemsList extends ItemsList {
public static final String EXTRA_FOLDER_NAME = "folderName";
private Feed feed;
private String folderName;
private ReviewManager reviewManager;
private ReviewInfo reviewInfo;
public static void startActivity(Context context, FeedSet feedSet,
Feed feed, String folderName) {
@ -36,13 +43,28 @@ public class FeedItemsList extends ItemsList {
protected void onCreate(Bundle bundle) {
feed = (Feed) getIntent().getSerializableExtra(EXTRA_FEED);
folderName = getIntent().getStringExtra(EXTRA_FOLDER_NAME);
super.onCreate(bundle);
UIUtils.setCustomActionBar(this, feed.faviconUrl, feed.title);
}
checkInAppReview();
}
public void deleteFeed() {
@Override
public void onBackPressed() {
// see checkInAppReview()
if (reviewInfo != null) {
Task<Void> flow = reviewManager.launchReviewFlow(this, reviewInfo);
flow.addOnCompleteListener(task -> {
PrefsUtils.setInAppReviewed(this);
super.onBackPressed();
});
} else {
super.onBackPressed();
}
}
public void deleteFeed() {
DialogFragment deleteFeedFragment = DeleteFeedFragment.newInstance(feed, folderName);
deleteFeedFragment.show(getSupportFragmentManager(), "dialog");
}
@ -85,6 +107,10 @@ public class FeedItemsList extends ItemsList {
// TODO: since this activity uses a feed object passed as an extra and doesn't query the DB,
// the name change won't be reflected until the activity finishes.
}
if (item.getItemId() == R.id.menu_statistics) {
FeedUtils.openStatistics(this, feed.feedId);
return true;
}
return false;
}
@ -122,4 +148,16 @@ public class FeedItemsList extends ItemsList {
String getSaveSearchFeedId() {
return "feed:" + feed.feedId;
}
private void checkInAppReview() {
if (!PrefsUtils.hasInAppReviewed(this)) {
reviewManager = ReviewManagerFactory.create(this);
Task<ReviewInfo> request = reviewManager.requestReviewFlow();
request.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
reviewInfo = task.getResult();
}
});
}
}
}

View file

@ -2,12 +2,13 @@ package com.newsblur.activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.fragment.app.FragmentActivity;
import com.newsblur.databinding.ActivityInAppBrowserBinding;
import com.newsblur.util.PrefsUtils;
@ -53,13 +54,4 @@ public class InAppBrowser extends FragmentActivity {
binding.webView.loadUrl(url);
}
@Override
public void onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack();
} else {
finish();
}
}
}
}

View file

@ -1,8 +1,8 @@
package com.newsblur.activity;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
@ -97,7 +97,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
if (activeSearchQuery != null) {
binding.itemlistSearchQuery.setText(activeSearchQuery);
binding.itemlistSearchQuery.setVisibility(View.VISIBLE);
fs.setSearchQuery(activeSearchQuery);
checkSearchQuery();
}
binding.itemlistSearchQuery.setOnKeyListener(new OnKeyListener() {
@ -191,6 +191,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
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()) {
@ -349,11 +350,16 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
}
private void checkSearchQuery() {
String oldQuery = fs.getSearchQuery();
String q = binding.itemlistSearchQuery.getText().toString().trim();
if (q.length() < 1) {
updateFleuron(false);
q = null;
} else if (!PrefsUtils.getIsPremium(this)) {
updateFleuron(true);
return;
}
String oldQuery = fs.getSearchQuery();
fs.setSearchQuery(q);
if (!TextUtils.equals(q, oldQuery)) {
FeedUtils.prepareReadingSession(fs, true);
@ -364,6 +370,26 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
}
}
private void updateFleuron(boolean requiresPremium) {
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
if (requiresPremium) {
transaction.hide(itemSetFragment);
binding.footerFleuron.textSubscription.setText(R.string.premium_subscribers_search);
binding.footerFleuron.containerSubscribe.setVisibility(View.VISIBLE);
binding.footerFleuron.getRoot().setVisibility(View.VISIBLE);
binding.footerFleuron.containerSubscribe.setOnClickListener(view -> UIUtils.startPremiumActivity(this));
} else {
transaction.show(itemSetFragment);
binding.footerFleuron.containerSubscribe.setVisibility(View.GONE);
binding.footerFleuron.getRoot().setVisibility(View.GONE);
binding.footerFleuron.containerSubscribe.setOnClickListener(null);
}
transaction.commit();
}
@Override
public void storyOrderChanged(StoryOrder newValue) {
updateStoryOrderPreference(newValue);

View file

@ -1,9 +1,9 @@
package com.newsblur.activity;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.view.Window;
import com.newsblur.R;

View file

@ -1,9 +1,9 @@
package com.newsblur.activity;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.view.Window;
import com.newsblur.R;

View file

@ -5,9 +5,9 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SwipeRefreshLayout;
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;
@ -373,6 +373,14 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
} 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;
}
return false;
}

View file

@ -0,0 +1,221 @@
package com.newsblur.activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.database.Cursor;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.core.content.ContextCompat;
import com.newsblur.R;
import com.newsblur.databinding.ActivityMuteConfigBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class MuteConfig extends FeedChooser implements MuteConfigAdapter.FeedStateChangedListener {
private ActivityMuteConfigBinding binding;
private boolean checkedInitFeedsLimit = false;
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.menu_select_all).setVisible(false);
menu.findItem(R.id.menu_select_none).setVisible(false);
menu.findItem(R.id.menu_widget_background).setVisible(false);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_mute_all:
setFeedsState(true);
return true;
case R.id.menu_mute_none:
setFeedsState(false);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
void bindLayout() {
binding = ActivityMuteConfigBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
void setupList() {
adapter = new MuteConfigAdapter(this, this);
binding.listView.setAdapter(adapter);
}
@Override
void processFeeds(Cursor cursor) {
ArrayList<Feed> feeds = new ArrayList<>();
while (cursor != null && cursor.moveToNext()) {
Feed feed = Feed.fromCursor(cursor);
feeds.add(feed);
feedMap.put(feed.feedId, feed);
}
this.feeds = feeds;
processData();
}
@Override
void processData() {
if (folders != null && feeds != null) {
for (Folder folder : folders) {
ArrayList<Feed> children = new ArrayList<>();
for (String feedId : folder.feedIds) {
Feed feed = feedMap.get(feedId);
if (!children.contains(feed)) {
children.add(feed);
}
}
folderNames.add(folder.flatName());
folderChildren.add(children);
}
setAdapterData();
syncActiveFeedCount();
checkedInitFeedsLimit = true;
}
}
@Override
public void setAdapterData() {
Set<String> feedIds = new HashSet<>(this.feeds.size());
for (Feed feed : this.feeds) {
feedIds.add(feed.feedId);
}
adapter.setFeedIds(feedIds);
super.setAdapterData();
}
@Override
protected void handleUpdate(int updateType) {
super.handleUpdate(updateType);
if ((updateType & UPDATE_STATUS) != 0) {
String syncStatus = NBSyncService.getSyncStatusMessage(this, false);
if (syncStatus != null) {
binding.textSyncStatus.setText(syncStatus);
binding.textSyncStatus.setVisibility(View.VISIBLE);
} else {
binding.textSyncStatus.setVisibility(View.GONE);
}
}
}
@Override
public void onFeedStateChanged() {
syncActiveFeedCount();
}
private void syncActiveFeedCount() {
// free standard accounts can follow up to 64 sites
boolean isPremium = PrefsUtils.getIsPremium(this);
if (!isPremium && feeds != null) {
int activeSites = 0;
for (Feed feed : feeds) {
if (feed.active) {
activeSites++;
}
}
int textColorRes = activeSites > AppConstants.FREE_ACCOUNT_SITE_LIMIT ? R.color.negative : R.color.positive;
binding.textSites.setTextColor(ContextCompat.getColor(this, textColorRes));
binding.textSites.setText(String.format(getString(R.string.mute_config_sites), activeSites, AppConstants.FREE_ACCOUNT_SITE_LIMIT));
showSitesCount();
if (activeSites > AppConstants.FREE_ACCOUNT_SITE_LIMIT && !checkedInitFeedsLimit) {
showAccountFeedsLimitDialog(activeSites - AppConstants.FREE_ACCOUNT_SITE_LIMIT);
}
} else {
hideSitesCount();
}
}
private void setFeedsState(boolean isMute) {
for (Feed feed : feeds) {
feed.active = !isMute;
}
adapter.notifyDataSetChanged();
if (isMute) FeedUtils.muteFeeds(this, adapter.feedIds);
else FeedUtils.unmuteFeeds(this, adapter.feedIds);
}
private void showAccountFeedsLimitDialog(int exceededLimitCount) {
new AlertDialog.Builder(this)
.setTitle(R.string.mute_config_title)
.setMessage(String.format(getString(R.string.mute_config_message), exceededLimitCount))
.setNeutralButton(android.R.string.ok, null)
.setPositiveButton(R.string.mute_config_upgrade, (dialogInterface, i) -> openUpgradeToPremium())
.show();
}
private void showSitesCount() {
ViewGroup.LayoutParams oldLayout = binding.listView.getLayoutParams();
FrameLayout.LayoutParams newLayout = new FrameLayout.LayoutParams(oldLayout);
newLayout.topMargin = UIUtils.dp2px(this, 56);
binding.listView.setLayoutParams(newLayout);
binding.containerSitesCount.setVisibility(View.VISIBLE);
binding.textResetSites.setOnClickListener(view -> resetToPopularFeeds());
}
private void hideSitesCount() {
ViewGroup.LayoutParams oldLayout = binding.listView.getLayoutParams();
FrameLayout.LayoutParams newLayout = new FrameLayout.LayoutParams(oldLayout);
newLayout.topMargin = UIUtils.dp2px(this, 0);
binding.listView.setLayoutParams(newLayout);
binding.containerSitesCount.setVisibility(View.GONE);
binding.textResetSites.setOnClickListener(null);
}
// reset to most popular sites based on subscribers
private void resetToPopularFeeds() {
// sort descending by subscribers
Collections.sort(feeds, (f1, f2) -> {
if (TextUtils.isEmpty(f1.subscribers)) f1.subscribers = "0";
if (TextUtils.isEmpty(f2.subscribers)) f2.subscribers = "0";
return Integer.valueOf(f2.subscribers).compareTo(Integer.valueOf(f1.subscribers));
});
Set<String> activeFeedIds = new HashSet<>();
Set<String> inactiveFeedIds = new HashSet<>();
for (int index = 0; index < feeds.size(); index++) {
Feed feed = feeds.get(index);
if (index < AppConstants.FREE_ACCOUNT_SITE_LIMIT) {
activeFeedIds.add(feed.feedId);
} else {
inactiveFeedIds.add(feed.feedId);
}
}
FeedUtils.unmuteFeeds(this, activeFeedIds);
FeedUtils.muteFeeds(this, inactiveFeedIds);
finish();
}
private void openUpgradeToPremium() {
Intent intent = new Intent(this, Premium.class);
startActivity(intent);
finish();
}
}

View file

@ -0,0 +1,86 @@
package com.newsblur.activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class MuteConfigAdapter extends FeedChooserAdapter {
private FeedStateChangedListener listener;
MuteConfigAdapter(Context context, FeedStateChangedListener listener) {
super(context);
this.listener = listener;
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, final ViewGroup parent) {
View groupView = super.getGroupView(groupPosition, isExpanded, convertView, parent);
groupView.setOnClickListener(v -> {
ArrayList<Feed> folderChild = MuteConfigAdapter.this.folderChildren.get(groupPosition);
boolean allAreMute = true;
for (Feed feed : folderChild) {
if (feed.active) {
allAreMute = false;
break;
}
}
Set<String> feedIds = new HashSet<>(folderChild.size());
for (Feed feed : folderChild) {
// flip active flag
feed.active = allAreMute;
feedIds.add(feed.feedId);
}
// if allAreMute initially, we need to unMute feeds
if (allAreMute) FeedUtils.unmuteFeeds(groupView.getContext(), feedIds);
else FeedUtils.muteFeeds(groupView.getContext(), feedIds);
listener.onFeedStateChanged();
notifyDataChanged();
});
return groupView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
View childView = super.getChildView(groupPosition, childPosition, isLastChild, convertView, parent);
final Feed feed = folderChildren.get(groupPosition).get(childPosition);
final CheckBox checkBox = childView.findViewById(R.id.check_box);
final ImageView imgToggle = childView.findViewById(R.id.img_toggle);
checkBox.setVisibility(View.GONE);
imgToggle.setVisibility(View.VISIBLE);
if (feed.active) imgToggle.setBackgroundResource(R.drawable.mute_feed_on);
else imgToggle.setBackgroundResource(R.drawable.mute_feed_off);
childView.setOnClickListener(v -> {
feed.active = !feed.active;
Set<String> feedIds = new HashSet<>(1);
feedIds.add(feed.feedId);
if (feed.active) FeedUtils.unmuteFeeds(childView.getContext(), feedIds);
else FeedUtils.muteFeeds(childView.getContext(), feedIds);
listener.onFeedStateChanged();
notifyDataChanged();
});
return childView;
}
interface FeedStateChangedListener {
void onFeedStateChanged();
}
}

View file

@ -1,11 +1,9 @@
package com.newsblur.activity;
import android.appwidget.AppWidgetManager;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import androidx.fragment.app.FragmentActivity;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.PrefConstants.ThemeValue;

View file

@ -0,0 +1,308 @@
package com.newsblur.activity;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.newsblur.R;
import com.newsblur.databinding.ActivityPremiumBinding;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.BetterLinkMovementMethod;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.Log;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import nl.dionsegijn.konfetti.emitters.StreamEmitter;
import nl.dionsegijn.konfetti.models.Shape;
import nl.dionsegijn.konfetti.models.Size;
public class Premium extends NbActivity {
private ActivityPremiumBinding binding;
private BillingClient billingClient;
private SkuDetails subscriptionDetails;
private Purchase purchasedSubscription;
private AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = billingResult -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener OK");
verifyUserSubscriptionStatus();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) {
// Billing API version is not supported for the type requested.
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener BILLING_UNAVAILABLE");
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
// Network connection is down.
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener SERVICE_UNAVAILABLE");
} else {
// Handle any other error codes.
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener ERROR - message: " + billingResult.getDebugMessage());
}
};
private PurchasesUpdatedListener purchaseUpdateListener = (billingResult, purchases) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener OK");
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener USER_CANCELLED");
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) {
// Billing API version is not supported for the type requested.
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener BILLING_UNAVAILABLE");
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
// Network connection is down.
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener SERVICE_UNAVAILABLE");
} else {
// Handle any other error codes.
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener ERROR - message: " + billingResult.getDebugMessage());
}
};
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
Log.d(Premium.this.getLocalClassName(), "onBillingSetupFinished OK");
retrievePlayStoreSubscriptions();
verifyUserSubscriptionStatus();
} else {
showSubscriptionDetailsError();
}
}
@Override
public void onBillingServiceDisconnected() {
Log.d(Premium.this.getLocalClassName(), "onBillingServiceDisconnected");
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
showSubscriptionDetailsError();
}
};
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
binding = ActivityPremiumBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setupUI();
setupBillingClient();
}
private void setupUI() {
UIUtils.setCustomActionBar(this, R.drawable.logo, getString(R.string.premium_toolbar_title));
// linkify before setting the string resource
BetterLinkMovementMethod.linkify(Linkify.WEB_URLS, binding.textPolicies)
.setOnLinkClickListener((textView, url) -> {
UIUtils.handleUri(Premium.this, Uri.parse(url));
return true;
});
binding.textPolicies.setText(UIUtils.fromHtml(getString(R.string.premium_policies)));
binding.textSubTitle.setPaintFlags(binding.textSubTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
FeedUtils.iconLoader.displayImage(AppConstants.SHILOH_PHOTO_URL, binding.imgShiloh, 0, false);
}
private void setupBillingClient() {
billingClient = BillingClient.newBuilder(this)
.setListener(purchaseUpdateListener)
.enablePendingPurchases()
.build();
billingClient.startConnection(billingClientStateListener);
}
private void verifyUserSubscriptionStatus() {
boolean hasNewsBlurSubscription = PrefsUtils.getIsPremium(this);
Purchase playStoreSubscription = null;
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
if (result.getPurchasesList() != null) {
for (Purchase purchase : result.getPurchasesList()) {
if (purchase.getSku().equals(AppConstants.PREMIUM_SKU)) {
playStoreSubscription = purchase;
}
}
}
if (hasNewsBlurSubscription || playStoreSubscription != null) {
binding.containerGoingPremium.setVisibility(View.GONE);
binding.containerGonePremium.setVisibility(View.VISIBLE);
long expirationTimeMs = PrefsUtils.getPremiumExpire(this);
String renewalString = null;
if (expirationTimeMs == 0) {
renewalString = getString(R.string.premium_subscription_no_expiration);
} else if (expirationTimeMs > 0) {
// date constructor expects ms
Date expirationDate = new Date(expirationTimeMs * 1000);
DateFormat dateFormat = new SimpleDateFormat("EEE, MMMM d, yyyy", Locale.getDefault());
dateFormat.setTimeZone(TimeZone.getDefault());
renewalString = getString(R.string.premium_subscription_renewal, dateFormat.format(expirationDate));
if (playStoreSubscription != null && !playStoreSubscription.isAutoRenewing()) {
renewalString = getString(R.string.premium_subscription_expiration, dateFormat.format(expirationDate));
}
}
if (!TextUtils.isEmpty(renewalString)) {
binding.textSubscriptionRenewal.setText(renewalString);
binding.textSubscriptionRenewal.setVisibility(View.VISIBLE);
}
showConfetti();
}
if (!hasNewsBlurSubscription && playStoreSubscription != null) {
purchasedSubscription = playStoreSubscription;
notifyNewsBlurOfSubscription();
}
}
private void retrievePlayStoreSubscriptions() {
List<String> skuList = new ArrayList<>(1);
// add sub SKUs from Play Store
skuList.add(AppConstants.PREMIUM_SKU);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(), (billingResult, skuDetailsList) -> {
Log.d(Premium.this.getLocalClassName(), "SkuDetailsResponse");
processSkuDetailsList(skuDetailsList);
});
}
private void processSkuDetailsList(@Nullable List<SkuDetails> skuDetailsList) {
if (skuDetailsList != null) {
for (SkuDetails skuDetails : skuDetailsList) {
if (skuDetails.getSku().equals(AppConstants.PREMIUM_SKU)) {
Log.d(Premium.this.getLocalClassName(), "Sku detail: " + skuDetails.getTitle() + " | " + skuDetails.getDescription() + " | " + skuDetails.getPrice() + " | " + skuDetails.getSku());
subscriptionDetails = skuDetails;
}
}
}
if (subscriptionDetails != null) {
showSubscriptionDetails();
} else {
showSubscriptionDetailsError();
}
}
private void showSubscriptionDetailsError() {
binding.textLoading.setText(R.string.premium_subscription_details_error);
binding.textLoading.setVisibility(View.VISIBLE);
binding.containerSub.setVisibility(View.GONE);
}
private void showSubscriptionDetails() {
// handling dynamic currency and pricing for 1Y subscriptions
String currencySymbol = subscriptionDetails.getPrice().substring(0, 1);
String priceString = subscriptionDetails.getPrice().substring(1);
double price = Double.parseDouble(priceString);
StringBuilder pricingText = new StringBuilder();
pricingText.append(subscriptionDetails.getPrice());
pricingText.append(" per year (");
pricingText.append(currencySymbol);
pricingText.append(String.format(Locale.getDefault(), "%.2f", price / 12));
pricingText.append("/month)");
binding.textSubTitle.setText(subscriptionDetails.getTitle());
binding.textSubPrice.setText(pricingText);
binding.textLoading.setVisibility(View.GONE);
binding.containerSub.setVisibility(View.VISIBLE);
binding.containerSub.setOnClickListener(view -> launchBillingFlow(subscriptionDetails));
}
private void launchBillingFlow(@NonNull SkuDetails skuDetails) {
Log.d(Premium.this.getLocalClassName(), "launchBillingFlow for sku: " + skuDetails.getSku());
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
billingClient.launchBillingFlow(this, billingFlowParams);
}
private void handlePurchase(Purchase purchase) {
Log.d(Premium.this.getLocalClassName(), "handlePurchase: " + purchase.getOrderId());
purchasedSubscription = purchase;
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged()) {
verifyUserSubscriptionStatus();
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) {
// need to acknowledge first time sub otherwise it will void
Log.d(Premium.this.getLocalClassName(), "acknowledge purchase: " + purchase.getOrderId());
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
}
private void showConfetti() {
binding.konfetti.build()
.addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA, Color.BLUE, Color.CYAN, Color.RED)
.setDirection(90)
.setFadeOutEnabled(true)
.setTimeToLive(1000L)
.addShapes(Shape.Square.INSTANCE, Shape.Circle.INSTANCE)
.addSizes(new Size(10, 5f))
.setPosition(0, binding.konfetti.getWidth() + 0f , -50f, -20f)
.streamFor(100, StreamEmitter.INDEFINITE);
}
private void notifyNewsBlurOfSubscription() {
if (purchasedSubscription != null) {
APIManager apiManager = new APIManager(this);
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.saveReceipt(purchasedSubscription.getOrderId(), purchasedSubscription.getSku());
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
super.onPostExecute(result);
if (!result.isError()) {
NBSyncService.forceFeedsFolders();
triggerSync();
}
finish();
}
}.execute();
}
}
}

View file

@ -2,9 +2,9 @@ package com.newsblur.activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;
import android.text.TextUtils;
import android.view.MenuItem;

View file

@ -8,12 +8,12 @@ import android.content.res.Configuration;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
@ -178,7 +178,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
transaction.commit();
}
getSupportLoaderManager().initLoader(0, null, this);
LoaderManager.getInstance(this).initLoader(0, null, this);
}
@Override
@ -436,7 +436,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
private void updateCursor() {
synchronized (STORIES_MUTEX) {
try {
getSupportLoaderManager().restartLoader(0, null, this);
LoaderManager.getInstance(this).restartLoader(0, null, this);
} catch (IllegalStateException ise) {
; // our heavy use of async can race loader calls, which it will gripe about, but this
// is only a refresh call, so dropping a refresh during creation is perfectly fine.

View file

@ -1,9 +1,9 @@
package com.newsblur.activity;
import android.support.v4.app.FragmentActivity;
import androidx.fragment.app.FragmentActivity;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.newsblur.R;
import com.newsblur.fragment.RegisterProgressFragment;

View file

@ -8,9 +8,10 @@ import java.util.Set;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import androidx.fragment.app.DialogFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -55,7 +56,7 @@ public class SearchForFeeds extends NbActivity implements LoaderCallbacks<Search
resultsList.setEmptyView(emptyView);
resultsList.setOnItemClickListener(this);
resultsList.setItemsCanFocus(false);
searchLoader = getSupportLoaderManager().initLoader(0, new Bundle(), this);
searchLoader = LoaderManager.getInstance(this).initLoader(0, new Bundle(), this);
onSearchRequested();
}
@ -70,6 +71,7 @@ public class SearchForFeeds extends NbActivity implements LoaderCallbacks<Search
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleIntent(intent);
}
@ -83,7 +85,7 @@ public class SearchForFeeds extends NbActivity implements LoaderCallbacks<Search
Bundle bundle = new Bundle();
bundle.putString(SearchAsyncTaskLoader.SEARCH_TERM, query);
searchLoader = getSupportLoaderManager().restartLoader(0, bundle, this);
searchLoader = LoaderManager.getInstance(this).restartLoader(0, bundle, this);
searchLoader.forceLoad();
}

View file

@ -1,6 +1,6 @@
package com.newsblur.activity;
import android.support.v4.app.FragmentActivity;
import androidx.fragment.app.FragmentActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.MenuItem;

View file

@ -1,10 +1,6 @@
package com.newsblur.activity;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -14,43 +10,18 @@ import com.newsblur.R;
import com.newsblur.databinding.ActivityWidgetConfigBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedOrderFilter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.FolderViewFilter;
import com.newsblur.util.ListOrderFilter;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.WidgetBackground;
import com.newsblur.widget.WidgetUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class WidgetConfig extends NbActivity {
public class WidgetConfig extends FeedChooser {
private WidgetConfigAdapter adapter;
private ArrayList<Feed> feeds;
private ArrayList<Folder> folders;
private Map<String, Feed> feedMap = new HashMap<>();
private ArrayList<String> folderNames = new ArrayList<>();
private ArrayList<ArrayList<Feed>> folderChildren = new ArrayList<>();
private ActivityWidgetConfigBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWidgetConfigBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getActionBar().setDisplayHomeAsUpEnabled(true);
setupList();
loadFeeds();
loadFolders();
}
@Override
protected void onPause() {
super.onPause();
@ -61,127 +32,46 @@ public class WidgetConfig extends NbActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_widget, menu);
inflater.inflate(R.menu.menu_feed_chooser, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
ListOrderFilter listOrderFilter = PrefsUtils.getWidgetConfigListOrder(this);
if (listOrderFilter == ListOrderFilter.ASCENDING) {
menu.findItem(R.id.menu_sort_order_ascending).setChecked(true);
} else if (listOrderFilter == ListOrderFilter.DESCENDING) {
menu.findItem(R.id.menu_sort_order_descending).setChecked(true);
}
FeedOrderFilter feedOrderFilter = PrefsUtils.getWidgetConfigFeedOrder(this);
if (feedOrderFilter == FeedOrderFilter.NAME) {
menu.findItem(R.id.menu_sort_by_name).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS) {
menu.findItem(R.id.menu_sort_by_subs).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH) {
menu.findItem(R.id.menu_sort_by_stories_month).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY) {
menu.findItem(R.id.menu_sort_by_recent_story).setChecked(true);
} else if (feedOrderFilter == FeedOrderFilter.OPENS) {
menu.findItem(R.id.menu_sort_by_number_opens).setChecked(true);
}
FolderViewFilter folderViewFilter = PrefsUtils.getWidgetConfigFolderView(this);
if (folderViewFilter == FolderViewFilter.NESTED) {
menu.findItem(R.id.menu_folder_view_nested).setChecked(true);
} else if (folderViewFilter == FolderViewFilter.FLAT) {
menu.findItem(R.id.menu_folder_view_flat).setChecked(true);
}
WidgetBackground widgetBackground = PrefsUtils.getWidgetBackground(this);
if (widgetBackground == WidgetBackground.DEFAULT) {
menu.findItem(R.id.menu_widget_background_default).setChecked(true);
} else if (widgetBackground == WidgetBackground.TRANSPARENT) {
menu.findItem(R.id.menu_widget_background_transparent).setChecked(true);
}
menu.findItem(R.id.menu_mute_all).setVisible(false);
menu.findItem(R.id.menu_mute_none).setVisible(false);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.menu_sort_order_ascending:
replaceListOrderFilter(ListOrderFilter.ASCENDING);
return true;
case R.id.menu_sort_order_descending:
replaceListOrderFilter(ListOrderFilter.DESCENDING);
return true;
case R.id.menu_sort_by_name:
replaceFeedOrderFilter(FeedOrderFilter.NAME);
return true;
case R.id.menu_sort_by_subs:
replaceFeedOrderFilter(FeedOrderFilter.SUBSCRIBERS);
return true;
case R.id.menu_sort_by_recent_story:
replaceFeedOrderFilter(FeedOrderFilter.RECENT_STORY);
return true;
case R.id.menu_sort_by_stories_month:
replaceFeedOrderFilter(FeedOrderFilter.STORIES_MONTH);
return true;
case R.id.menu_sort_by_number_opens:
replaceFeedOrderFilter(FeedOrderFilter.OPENS);
return true;
case R.id.menu_folder_view_nested:
replaceFolderView(FolderViewFilter.NESTED);
return true;
case R.id.menu_folder_view_flat:
replaceFolderView(FolderViewFilter.FLAT);
return true;
case R.id.menu_select_all:
selectAllFeeds();
return true;
case R.id.menu_select_none:
replaceWidgetFeedIds(Collections.<String>emptySet());
return true;
case R.id.menu_widget_background_default:
setWidgetBackground(WidgetBackground.DEFAULT);
return true;
case R.id.menu_widget_background_transparent:
setWidgetBackground(WidgetBackground.TRANSPARENT);
replaceWidgetFeedIds(Collections.emptySet());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void setupList() {
@Override
void bindLayout() {
binding = ActivityWidgetConfigBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
void setupList() {
adapter = new WidgetConfigAdapter(this);
binding.listView.setAdapter(adapter);
}
private void loadFeeds() {
Loader<Cursor> loader = FeedUtils.dbHelper.getFeedsLoader();
loader.registerListener(loader.getId(), new Loader.OnLoadCompleteListener<Cursor>() {
@Override
public void onLoadComplete(@NonNull Loader<Cursor> loader, @Nullable Cursor cursor) {
processFeeds(cursor);
}
});
loader.startLoading();
}
private void loadFolders() {
Loader<Cursor> loader = FeedUtils.dbHelper.getFoldersLoader();
loader.registerListener(loader.getId(), new Loader.OnLoadCompleteListener<Cursor>() {
@Override
public void onLoadComplete(@NonNull Loader<Cursor> loader, @Nullable Cursor cursor) {
processFolders(cursor);
}
});
loader.startLoading();
}
private void processFeeds(Cursor cursor) {
@Override
void processFeeds(Cursor cursor) {
ArrayList<Feed> feeds = new ArrayList<>();
while (cursor != null && cursor.moveToNext()) {
Feed feed = Feed.fromCursor(cursor);
@ -194,25 +84,25 @@ public class WidgetConfig extends NbActivity {
processData();
}
private void processFolders(Cursor cursor) {
ArrayList<Folder> folders = new ArrayList<>();
while (cursor != null && cursor.moveToNext()) {
Folder folder = Folder.fromCursor(cursor);
if (!folder.feedIds.isEmpty()) {
folders.add(folder);
@Override
public void setAdapterData() {
Set<String> feedIds = PrefsUtils.getWidgetFeedIds(this);
// by default select all feeds
if (feedIds == null) {
feedIds = new HashSet<>(this.feeds.size());
for (Feed feed : this.feeds) {
feedIds.add(feed.feedId);
}
}
this.folders = folders;
Collections.sort(this.folders, new Comparator<Folder>() {
@Override
public int compare(Folder o1, Folder o2) {
return Folder.compareFolderNames(o1.flatName(), o2.flatName());
}
});
processData();
adapter.setFeedIds(feedIds);
super.setAdapterData();
binding.listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE);
binding.textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE);
}
private void processData() {
@Override
void processData() {
if (folders != null && feeds != null) {
for (Folder folder : folders) {
ArrayList<Feed> activeFeeds = new ArrayList<>();
@ -226,7 +116,6 @@ public class WidgetConfig extends NbActivity {
folderChildren.add(activeFeeds);
}
setSelectedFeeds();
setAdapterData();
}
}
@ -243,44 +132,4 @@ public class WidgetConfig extends NbActivity {
PrefsUtils.setWidgetFeedIds(this, feedIds);
adapter.replaceFeedIds(feedIds);
}
private void replaceFeedOrderFilter(FeedOrderFilter feedOrderFilter) {
PrefsUtils.setWidgetConfigFeedOrder(this, feedOrderFilter);
adapter.replaceFeedOrder(feedOrderFilter);
}
private void replaceListOrderFilter(ListOrderFilter listOrderFilter) {
PrefsUtils.setWidgetConfigListOrder(this, listOrderFilter);
adapter.replaceListOrder(listOrderFilter);
}
private void replaceFolderView(FolderViewFilter folderViewFilter) {
PrefsUtils.setWidgetConfigFolderView(this, folderViewFilter);
adapter.replaceFolderView(folderViewFilter);
setAdapterData();
}
private void setWidgetBackground(WidgetBackground widgetBackground) {
PrefsUtils.setWidgetBackground(this, widgetBackground);
WidgetUtils.updateWidget(this);
}
private void setSelectedFeeds() {
Set<String> feedIds = PrefsUtils.getWidgetFeedIds(this);
// by default select all feeds
if (feedIds == null) {
feedIds = new HashSet<>(this.feeds.size());
for (Feed feed : this.feeds) {
feedIds.add(feed.feedId);
}
}
adapter.setFeedIds(feedIds);
}
private void setAdapterData() {
adapter.setData(this.folderNames, this.folderChildren, this.feeds);
binding.listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE);
binding.textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE);
}
}

View file

@ -1,296 +1,72 @@
package com.newsblur.activity;
import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedOrderFilter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.FolderViewFilter;
import com.newsblur.util.ListOrderFilter;
import com.newsblur.util.PrefsUtils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
public class WidgetConfigAdapter extends BaseExpandableListAdapter {
private final static int defaultTextSizeChild = 14;
private final static int defaultTextSizeGroup = 13;
private Set<String> feedIds = new HashSet<>();
private ArrayList<String> folderNames = new ArrayList<>();
private ArrayList<ArrayList<Feed>> folderChildren = new ArrayList<>();
private FolderViewFilter folderViewFilter;
private ListOrderFilter listOrderFilter;
private FeedOrderFilter feedOrderFilter;
private float textSize;
public class WidgetConfigAdapter extends FeedChooserAdapter {
WidgetConfigAdapter(Context context) {
folderViewFilter = PrefsUtils.getWidgetConfigFolderView(context);
listOrderFilter = PrefsUtils.getWidgetConfigListOrder(context);
feedOrderFilter = PrefsUtils.getWidgetConfigFeedOrder(context);
textSize = PrefsUtils.getListTextSize(context);
}
@Override
public int getGroupCount() {
return folderNames.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return folderChildren.get(groupPosition).size();
}
@Override
public String getGroup(int groupPosition) {
return folderNames.get(groupPosition);
}
@Override
public Feed getChild(int groupPosition, int childPosition) {
return folderChildren.get(groupPosition).get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return folderNames.get(groupPosition).hashCode();
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return folderChildren.get(groupPosition).get(childPosition).hashCode();
}
@Override
public boolean hasStableIds() {
return true;
super(context);
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, final ViewGroup parent) {
String folderName = folderNames.get(groupPosition);
if (folderName.equals(AppConstants.ROOT_FOLDER)) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_root_folder, parent, false);
} else {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_folder, parent, false);
TextView textName = convertView.findViewById(R.id.text_folder_name);
textName.setTextSize(textSize * defaultTextSizeGroup);
textName.setText(folderName);
}
View groupView = super.getGroupView(groupPosition, isExpanded, convertView, parent);
((ExpandableListView) parent).expandGroup(groupPosition);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ArrayList<Feed> folderChild = WidgetConfigAdapter.this.folderChildren.get(groupPosition);
// check all is selected
boolean allSelected = true;
for (Feed feed : folderChild) {
if (!feedIds.contains(feed.feedId)) {
allSelected = false;
break;
}
groupView.setOnClickListener(v -> {
ArrayList<Feed> folderChild = WidgetConfigAdapter.this.folderChildren.get(groupPosition);
// check all is selected
boolean allSelected = true;
for (Feed feed : folderChild) {
if (!feedIds.contains(feed.feedId)) {
allSelected = false;
break;
}
for (Feed feed : folderChild) {
if (allSelected) {
feedIds.remove(feed.feedId);
} else {
feedIds.add(feed.feedId);
}
}
setWidgetFeedIds(parent.getContext());
notifyDataChanged();
}
for (Feed feed : folderChild) {
if (allSelected) {
feedIds.remove(feed.feedId);
} else {
feedIds.add(feed.feedId);
}
}
setWidgetFeedIds(parent.getContext());
notifyDataChanged();
});
return convertView;
return groupView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_widget_config_feed, parent, false);
}
View childView = super.getChildView(groupPosition, childPosition, isLastChild, convertView, parent);
final Feed feed = folderChildren.get(groupPosition).get(childPosition);
TextView textTitle = convertView.findViewById(R.id.text_title);
TextView textDetails = convertView.findViewById(R.id.text_details);
final CheckBox checkBox = convertView.findViewById(R.id.check_box);
ImageView img = convertView.findViewById(R.id.img);
textTitle.setTextSize(textSize * defaultTextSizeChild);
textDetails.setTextSize(textSize * defaultTextSizeChild);
textTitle.setText(feed.title);
checkBox.setChecked(feedIds.contains(feed.feedId));
final CheckBox checkBox = childView.findViewById(R.id.check_box);
final ImageView imgToggle = childView.findViewById(R.id.img_toggle);
checkBox.setVisibility(View.VISIBLE);
imgToggle.setVisibility(View.GONE);
if (feedOrderFilter == FeedOrderFilter.NAME || feedOrderFilter == FeedOrderFilter.OPENS) {
textDetails.setText(parent.getContext().getString(R.string.feed_opens, feed.feedOpens));
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS) {
textDetails.setText(parent.getContext().getString(R.string.feed_subscribers, feed.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH) {
textDetails.setText(parent.getContext().getString(R.string.feed_stories_per_month, feed.storiesPerMonth));
} else {
// FeedOrderFilter.RECENT_STORY
try {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date dateTime = dateFormat.parse(feed.lastStoryDate);
CharSequence relativeTimeString = DateUtils.getRelativeTimeSpanString(dateTime.getTime(), System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
textDetails.setText(relativeTimeString);
} catch (Exception e) {
textDetails.setText(feed.lastStoryDate);
}
}
FeedUtils.iconLoader.displayImage(feed.faviconUrl, img, 0, false, img.getHeight(), true);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkBox.setChecked(!checkBox.isChecked());
if (checkBox.isChecked()) {
feedIds.add(feed.feedId);
} else {
feedIds.remove(feed.feedId);
}
setWidgetFeedIds(parent.getContext());
childView.setOnClickListener(v -> {
checkBox.setChecked(!checkBox.isChecked());
if (checkBox.isChecked()) {
feedIds.add(feed.feedId);
} else {
feedIds.remove(feed.feedId);
}
setWidgetFeedIds(parent.getContext());
});
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public boolean areAllItemsEnabled() {
return super.areAllItemsEnabled();
}
void setData(ArrayList<String> activeFoldersNames, ArrayList<ArrayList<Feed>> activeFolderChildren, ArrayList<Feed> feeds) {
if (folderViewFilter == FolderViewFilter.NESTED) {
this.folderNames = activeFoldersNames;
this.folderChildren = activeFolderChildren;
} else {
this.folderNames = new ArrayList<>(1);
this.folderNames.add(AppConstants.ROOT_FOLDER);
this.folderChildren = new ArrayList<>();
this.folderChildren.add(feeds);
}
this.notifyDataChanged();
}
void replaceFeedOrder(FeedOrderFilter feedOrderFilter) {
this.feedOrderFilter = feedOrderFilter;
notifyDataChanged();
}
void replaceListOrder(ListOrderFilter listOrderFilter) {
this.listOrderFilter = listOrderFilter;
notifyDataChanged();
}
void replaceFolderView(FolderViewFilter folderViewFilter) {
this.folderViewFilter = folderViewFilter;
}
private void notifyDataChanged() {
for (ArrayList<Feed> feedList : this.folderChildren) {
Collections.sort(feedList, getListComparator());
}
this.notifyDataSetChanged();
}
void setFeedIds(Set<String> feedIds) {
this.feedIds.clear();
this.feedIds.addAll(feedIds);
}
void replaceFeedIds(Set<String> feedIds) {
setFeedIds(feedIds);
this.notifyDataSetChanged();
return childView;
}
private void setWidgetFeedIds(Context context) {
PrefsUtils.setWidgetFeedIds(context, feedIds);
}
private Comparator<Feed> getListComparator() {
return new Comparator<Feed>() {
@Override
public int compare(Feed o1, Feed o2) {
if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.ASCENDING) {
return o1.title.compareTo(o2.title);
} else if (feedOrderFilter == FeedOrderFilter.NAME && listOrderFilter == ListOrderFilter.DESCENDING) {
return o2.title.compareTo(o1.title);
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.valueOf(o1.subscribers).compareTo(Integer.valueOf(o2.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.SUBSCRIBERS && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.valueOf(o2.subscribers).compareTo(Integer.valueOf(o1.subscribers));
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.feedOpens, o2.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.OPENS && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.compare(o2.feedOpens, o1.feedOpens);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.ASCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.RECENT_STORY && listOrderFilter == ListOrderFilter.DESCENDING) {
return compareLastStoryDateTimes(o1.lastStoryDate, o2.lastStoryDate, listOrderFilter);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.ASCENDING) {
return Integer.compare(o1.storiesPerMonth, o2.storiesPerMonth);
} else if (feedOrderFilter == FeedOrderFilter.STORIES_MONTH && listOrderFilter == ListOrderFilter.DESCENDING) {
return Integer.compare(o2.storiesPerMonth, o1.storiesPerMonth);
}
return o1.title.compareTo(o2.title);
}
};
}
private int compareLastStoryDateTimes(String firstDateTime, String secondDateTime, ListOrderFilter listOrderFilter) {
try {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// found null last story date times on feeds
if (TextUtils.isEmpty(firstDateTime)) {
firstDateTime = "2000-01-01 00:00:00";
}
if (TextUtils.isEmpty(secondDateTime)) {
secondDateTime = "2000-01-01 00:00:00";
}
Date firstDate = dateFormat.parse(firstDateTime);
Date secondDate = dateFormat.parse(secondDateTime);
if (listOrderFilter == ListOrderFilter.ASCENDING) {
return firstDate.compareTo(secondDate);
} else {
return secondDate.compareTo(firstDate);
}
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
}

View file

@ -6,9 +6,9 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.support.annotation.Nullable;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import androidx.annotation.Nullable;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
import android.text.TextUtils;
import android.util.Log;

View file

@ -75,6 +75,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
public int totalSocialNeutCount = 0;
/** Total positive unreads for all social feeds. */
public int totalSocialPosiCount = 0;
/** Total active feeds. */
public int totalActiveFeedCount = 0;
/** Feeds, indexed by feed ID. */
private Map<String,Feed> feeds = Collections.emptyMap();
@ -557,6 +559,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
feedPosCounts = new HashMap<String,Integer>();
totalNeutCount = 0;
totalPosCount = 0;
totalActiveFeedCount = 0;
while (cursor.moveToNext()) {
Feed f = Feed.fromCursor(cursor);
feeds.put(f.feedId, f);
@ -570,6 +573,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
feedNeutCounts.put(f.feedId, neut);
totalNeutCount += neut;
}
if (f.active) {
totalActiveFeedCount++;
}
}
recountFeeds();
notifyDataSetChanged();

View file

@ -4,7 +4,7 @@ import android.content.Context;
import android.database.Cursor;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.support.v4.content.AsyncTaskLoader;
import androidx.loader.content.AsyncTaskLoader;
import com.newsblur.util.AppConstants;

View file

@ -3,10 +3,10 @@ package com.newsblur.database;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
@ -178,7 +178,6 @@ public class ReadingAdapter extends PagerAdapter {
}
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
if (curTransaction == null) {
curTransaction = fm.beginTransaction();
}
@ -208,11 +207,9 @@ public class ReadingAdapter extends PagerAdapter {
if (fragment != lastActiveFragment) {
if (lastActiveFragment != null) {
lastActiveFragment.setMenuVisibility(false);
lastActiveFragment.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
lastActiveFragment = fragment;
}

View file

@ -4,8 +4,8 @@ import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Parcelable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.util.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DiffUtil;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.GestureDetector;
@ -126,7 +126,11 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
public int getStoryCount() {
return stories.size();
if (fs != null && UIUtils.needsPremiumAccess(context, fs)) {
return Math.min(3, stories.size());
} else {
return stories.size();
}
}
/**
@ -464,6 +468,9 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
case GEST_ACTION_UNSAVE:
FeedUtils.setStorySaved(story, false, context, null);
break;
case GEST_ACTION_STATISTICS:
FeedUtils.openStatistics(context, story.feedId);
break;
case GEST_ACTION_NONE:
default:
}

View file

@ -2,7 +2,7 @@ package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import java.util.Collection;

View file

@ -6,12 +6,12 @@ import android.app.Dialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View file

@ -5,7 +5,7 @@ import java.util.HashSet;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;

View file

@ -4,7 +4,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import com.newsblur.R;

View file

@ -10,7 +10,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -15,7 +15,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
public class DeleteFeedFragment extends DialogFragment {
private static final String FEED_TYPE = "feed_type";

View file

@ -4,9 +4,9 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.text.TextUtils;
import com.newsblur.R;

View file

@ -5,7 +5,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;

View file

@ -8,7 +8,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -1,7 +1,7 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;

View file

@ -8,9 +8,11 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import androidx.fragment.app.DialogFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@ -35,6 +37,7 @@ import com.newsblur.activity.GlobalSharedStoriesItemsList;
import com.newsblur.activity.InfrequentItemsList;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.Main;
import com.newsblur.activity.MuteConfig;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.ReadStoriesItemsList;
import com.newsblur.activity.SavedStoriesItemsList;
@ -139,6 +142,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
checkOpenFolderPreferences();
firstCursorSeenYet = true;
pushUnreadCounts();
checkAccountFeedsLimit();
break;
case SAVEDCOUNT_LOADER:
adapter.setStarredCountCursor(cursor);
@ -165,11 +169,11 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
if (isAdded()) {
com.newsblur.util.Log.d(this, "loading feeds in mode: " + currentState);
try {
getLoaderManager().restartLoader(SOCIALFEEDS_LOADER, null, this);
getLoaderManager().restartLoader(FOLDERS_LOADER, null, this);
getLoaderManager().restartLoader(FEEDS_LOADER, null, this);
getLoaderManager().restartLoader(SAVEDCOUNT_LOADER, null, this);
getLoaderManager().restartLoader(SAVED_SEARCH_LOADER, null, this);
LoaderManager.getInstance(this).restartLoader(SOCIALFEEDS_LOADER, null, this);
LoaderManager.getInstance(this).restartLoader(FOLDERS_LOADER, null, this);
LoaderManager.getInstance(this).restartLoader(FEEDS_LOADER, null, this);
LoaderManager.getInstance(this).restartLoader(SAVEDCOUNT_LOADER, null, this);
LoaderManager.getInstance(this).restartLoader(SAVED_SEARCH_LOADER, null, this);
} catch (Exception e) {
// on heavily loaded devices, the time between isAdded() going false
// and the loader subsystem shutting down can be nontrivial, causing
@ -180,13 +184,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
public synchronized void startLoaders() {
if (isAdded()) {
if (getLoaderManager().getLoader(FOLDERS_LOADER) == null) {
if (LoaderManager.getInstance(this).getLoader(FOLDERS_LOADER) == null) {
// if the loaders haven't yet been created, do so
getLoaderManager().initLoader(SOCIALFEEDS_LOADER, null, this);
getLoaderManager().initLoader(FOLDERS_LOADER, null, this);
getLoaderManager().initLoader(FEEDS_LOADER, null, this);
getLoaderManager().initLoader(SAVEDCOUNT_LOADER, null, this);
getLoaderManager().initLoader(SAVED_SEARCH_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(SOCIALFEEDS_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(FOLDERS_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(FEEDS_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(SAVEDCOUNT_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(SAVED_SEARCH_LOADER, null, this);
}
}
}
@ -364,7 +368,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
String folderName = adapter.getGroupFolderName(groupPosition);
deleteFeedFragment = DeleteFeedFragment.newInstance(adapter.getFeed(groupPosition, childPosition), folderName);
}
deleteFeedFragment.show(getFragmentManager(), "dialog");
deleteFeedFragment.show(getParentFragmentManager(), "dialog");
return true;
} else if (item.getItemId() == R.id.menu_mark_feed_as_read) {
FeedSet fs = adapter.getChild(groupPosition, childPosition);
@ -378,13 +382,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
Feed feed = adapter.getFeed(groupPosition, childPosition);
if (feed != null) {
DialogFragment chooseFoldersFragment = ChooseFoldersFragment.newInstance(feed);
chooseFoldersFragment.show(getFragmentManager(), "dialog");
chooseFoldersFragment.show(getParentFragmentManager(), "dialog");
}
} else if (item.getItemId() == R.id.menu_rename_feed) {
Feed feed = adapter.getFeed(groupPosition, childPosition);
if (feed != null) {
DialogFragment renameFeedFragment = RenameDialogFragment.newInstance(feed);
renameFeedFragment.show(getFragmentManager(), "dialog");
renameFeedFragment.show(getParentFragmentManager(), "dialog");
}
} else if (item.getItemId() == R.id.menu_mute_feed) {
Set<String> feedIds = new HashSet<String>();
@ -402,23 +406,23 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
FeedUtils.instaFetchFeed(getActivity(), adapter.getFeed(groupPosition, childPosition).feedId);
} else if (item.getItemId() == R.id.menu_intel) {
FeedIntelTrainerFragment intelFrag = FeedIntelTrainerFragment.newInstance(adapter.getFeed(groupPosition, childPosition), adapter.getChild(groupPosition, childPosition));
intelFrag.show(getFragmentManager(), FeedIntelTrainerFragment.class.getName());
intelFrag.show(getParentFragmentManager(), FeedIntelTrainerFragment.class.getName());
} else if (item.getItemId() == R.id.menu_delete_saved_search) {
SavedSearch savedSearch = adapter.getSavedSearch(childPosition);
if (savedSearch != null) {
DialogFragment deleteFeedFragment = DeleteFeedFragment.newInstance(savedSearch);
deleteFeedFragment.show(getFragmentManager(), "dialog");
deleteFeedFragment.show(getParentFragmentManager(), "dialog");
}
} else if (item.getItemId() == R.id.menu_delete_folder) {
Folder folder = adapter.getGroupFolder(groupPosition);
String folderParentName = folder.getFirstParentName();
DeleteFolderFragment deleteFolderFragment = DeleteFolderFragment.newInstance(folder.name, folderParentName);
deleteFolderFragment.show(getFragmentManager(), deleteFolderFragment.getTag());
deleteFolderFragment.show(getParentFragmentManager(), deleteFolderFragment.getTag());
} else if (item.getItemId() == R.id.menu_rename_folder) {
Folder folder = adapter.getGroupFolder(groupPosition);
String folderParentName = folder.getFirstParentName();
RenameDialogFragment renameDialogFragment = RenameDialogFragment.newInstance(folder.name, folderParentName);
renameDialogFragment.show(getFragmentManager(), renameDialogFragment.getTag());
renameDialogFragment.show(getParentFragmentManager(), renameDialogFragment.getTag());
}
return super.onContextItemSelected(item);
@ -581,6 +585,15 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
return true;
}
private void checkAccountFeedsLimit() {
new Handler().postDelayed(() -> {
if (adapter.totalActiveFeedCount > AppConstants.FREE_ACCOUNT_SITE_LIMIT && !PrefsUtils.getIsPremium(requireContext())) {
Intent intent = new Intent(requireActivity(), MuteConfig.class);
startActivity(intent);
}
}, 2000);
}
private void openSavedSearch(SavedSearch savedSearch) {
Intent intent = null;
FeedSet fs = null;

View file

@ -1,9 +1,9 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -5,23 +5,24 @@ import android.graphics.Typeface;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.FrameLayout;
import com.newsblur.R;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.StoryViewAdapter;
import com.newsblur.databinding.FragmentItemgridBinding;
import com.newsblur.databinding.RowFleuronBinding;
import com.newsblur.domain.Story;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.FeedSet;
@ -57,7 +58,6 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
// loading indicator for when stories are present and fresh (at bottom of list)
protected ProgressThrobber bottomProgressView;
private View fleuronFooter;
// the fleuron has padding that can't be calculated until after layout, but only changes
// rarely thereafter
private boolean fleuronResized = false;
@ -69,6 +69,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
public boolean fullFlingComplete = false;
private FragmentItemgridBinding binding;
private RowFleuronBinding fleuronBinding;
public static ItemSetFragment newInstance() {
ItemSetFragment fragment = new ItemSetFragment();
@ -80,7 +81,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(ITEMLIST_LOADER, null, this);
LoaderManager.getInstance(this).initLoader(ITEMLIST_LOADER, null, this);
}
@Override
@ -118,6 +119,8 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_itemgrid, null);
binding = FragmentItemgridBinding.bind(v);
View fleuronView = inflater.inflate(R.layout.row_fleuron, null);
fleuronBinding = RowFleuronBinding.bind(fleuronView);
// disable the throbbers if animations are going to have a zero time scale
boolean isDisableAnimations = ViewUtils.isPowerSaveMode(getActivity());
@ -136,8 +139,8 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
fleuronFooter = inflater.inflate(R.layout.row_fleuron, null);
fleuronFooter.setVisibility(View.INVISIBLE);
fleuronBinding.getRoot().setVisibility(View.INVISIBLE);
fleuronBinding.containerSubscribe.setOnClickListener(view -> UIUtils.startPremiumActivity(requireContext()));
binding.itemgridfragmentGrid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
@ -165,7 +168,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
adapter = new StoryViewAdapter(((NbActivity) getActivity()), this, getFeedSet(), listStyle);
adapter.addFooterView(footerView);
adapter.addFooterView(fleuronFooter);
adapter.addFooterView(fleuronBinding.getRoot());
binding.itemgridfragmentGrid.setAdapter(adapter);
// the layout manager needs to know that the footer rows span all the way across
@ -231,7 +234,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
public void hasUpdated() {
if (isAdded() && !getFeedSet().isMuted()) {
getLoaderManager().restartLoader(ITEMLIST_LOADER , null, this);
LoaderManager.getInstance(this).restartLoader(ITEMLIST_LOADER , null, this);
}
}
@ -293,9 +296,6 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
}
private void updateLoadingIndicators() {
// sanity check that we even have views yet
if (fleuronFooter == null) return;
calcFleuronPadding();
if (getFeedSet().isMuted()) {
@ -307,6 +307,15 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
return;
}
if (cursorSeenYet && adapter.getRawStoryCount() > 0 && UIUtils.needsPremiumAccess(requireContext(), getFeedSet())) {
fleuronBinding.getRoot().setVisibility(View.VISIBLE);
fleuronBinding.containerSubscribe.setVisibility(View.VISIBLE);
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
bottomProgressView.setVisibility(View.INVISIBLE);
fleuronResized = false;
return;
}
if ( (!cursorSeenYet) || NBSyncService.isFeedSetSyncing(getFeedSet(), getActivity()) ) {
binding.emptyViewText.setText(R.string.empty_list_view_loading);
binding.emptyViewText.setTypeface(null, Typeface.ITALIC);
@ -319,7 +328,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
binding.topLoadingThrob.setVisibility(View.VISIBLE);
bottomProgressView.setVisibility(View.GONE);
}
fleuronFooter.setVisibility(View.INVISIBLE);
fleuronBinding.getRoot().setVisibility(View.INVISIBLE);
} else {
ReadFilter readFilter = PrefsUtils.getReadFilter(getActivity(), getFeedSet());
if (readFilter == ReadFilter.UNREAD) {
@ -333,7 +342,8 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
bottomProgressView.setVisibility(View.INVISIBLE);
if (cursorSeenYet && NBSyncService.isFeedSetExhausted(getFeedSet()) && (adapter.getRawStoryCount() > 0)) {
fleuronFooter.setVisibility(View.VISIBLE);
fleuronBinding.containerSubscribe.setVisibility(View.GONE);
fleuronBinding.getRoot().setVisibility(View.VISIBLE);
}
}
}
@ -417,6 +427,9 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
// don't bother checking on scroll up
if (dy < 1) return;
// skip fetching more stories if premium access is required
if (UIUtils.needsPremiumAccess(requireContext(), getFeedSet()) && adapter.getItemCount() >= 3) return;
ensureSufficientStories();
// the list can be scrolled past the last item thanks to the offset footer, but don't fling
@ -500,21 +513,21 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
* be scrolled until the bottom most story reaches to top, for those who mark-by-scrolling.
*/
private void calcFleuronPadding() {
if (fleuronResized) return;
// sanity check that we even have views yet
if (fleuronResized || fleuronBinding.getRoot().getLayoutParams() == null) return;
int listHeight = binding.itemgridfragmentGrid.getMeasuredHeight();
View innerView = fleuronFooter.findViewById(R.id.fleuron);
ViewGroup.LayoutParams oldLayout = innerView.getLayoutParams();
ViewGroup.MarginLayoutParams newLayout = new LinearLayout.LayoutParams(oldLayout);
int marginPx_4dp = UIUtils.dp2px(getActivity(), 4);
int defaultPx_100dp = UIUtils.dp2px(getActivity(), 100);
int bufferPx_50dp = UIUtils.dp2px(getActivity(), 50);
ViewGroup.LayoutParams oldLayout = fleuronBinding.getRoot().getLayoutParams();
FrameLayout.LayoutParams newLayout = new FrameLayout.LayoutParams(oldLayout);
int marginPx_4dp = UIUtils.dp2px(requireContext(), 4);
int fleuronFooterHeightPx = fleuronBinding.getRoot().getMeasuredHeight();
if (listHeight > 1) {
newLayout.setMargins(0, marginPx_4dp, 0, listHeight-bufferPx_50dp);
newLayout.setMargins(0, marginPx_4dp, 0, listHeight-fleuronFooterHeightPx);
fleuronResized = true;
} else {
int defaultPx_100dp = UIUtils.dp2px(requireContext(), 100);
newLayout.setMargins(0, marginPx_4dp, 0, defaultPx_100dp);
}
innerView.setLayoutParams(newLayout);
fleuronBinding.getRoot().setLayoutParams(newLayout);
}
@Override

View file

@ -1,7 +1,7 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View file

@ -7,7 +7,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;

View file

@ -5,7 +5,7 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.graphics.Bitmap;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View file

@ -3,9 +3,9 @@ package com.newsblur.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -104,7 +104,7 @@ public class LoginRegisterFragment extends Fragment {
Intent i = new Intent(getActivity(), LoginProgress.class);
i.putExtra("username", binding.loginUsername.getText().toString());
i.putExtra("password", binding.loginUsername.getText().toString());
i.putExtra("password", binding.loginPassword.getText().toString());
startActivity(i);
}
}

View file

@ -4,7 +4,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import com.newsblur.R;
import com.newsblur.util.PrefsUtils;

View file

@ -1,7 +1,7 @@
package com.newsblur.fragment;
import android.app.Activity;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import com.newsblur.util.FeedUtils;

View file

@ -4,7 +4,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View file

@ -4,8 +4,8 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -138,7 +138,7 @@ public class ProfileDetailsFragment extends Fragment implements OnClickListener
followButton.setVisibility(View.GONE);
unfollowButton.setVisibility(View.VISIBLE);
} else {
FragmentManager fm = ProfileDetailsFragment.this.getFragmentManager();
FragmentManager fm = ProfileDetailsFragment.this.getParentFragmentManager();
AlertDialogFragment alertDialog = AlertDialogFragment.newAlertDialogFragment(getResources().getString(R.string.follow_error));
alertDialog.show(fm, "fragment_edit_name");
}
@ -164,7 +164,7 @@ public class ProfileDetailsFragment extends Fragment implements OnClickListener
unfollowButton.setVisibility(View.GONE);
followButton.setVisibility(View.VISIBLE);
} else {
FragmentManager fm = ProfileDetailsFragment.this.getFragmentManager();
FragmentManager fm = ProfileDetailsFragment.this.getParentFragmentManager();
AlertDialogFragment alertDialog = AlertDialogFragment.newAlertDialogFragment(getResources().getString(R.string.unfollow_error));
alertDialog.show(fm, "fragment_edit_name");
}

View file

@ -1,9 +1,9 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -7,7 +7,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
public class ReadingActionConfirmationFragment extends DialogFragment {

View file

@ -1,9 +1,9 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -14,9 +14,9 @@ import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
@ -415,7 +415,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
private void clickShare() {
DialogFragment newFragment = ShareDialogFragment.newInstance(story, sourceUserId);
newFragment.show(getFragmentManager(), "dialog");
newFragment.show(getParentFragmentManager(), "dialog");
}
private void updateShareButton() {
@ -483,7 +483,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onClick(View v) {
if (story.feedId.equals("0")) return; // cannot train on feedless stories
StoryIntelTrainerFragment intelFrag = StoryIntelTrainerFragment.newInstance(story, fs);
intelFrag.show(getFragmentManager(), StoryIntelTrainerFragment.class.getName());
intelFrag.show(getParentFragmentManager(), StoryIntelTrainerFragment.class.getName());
}
});
@ -492,17 +492,15 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onClick(View v) {
if (story.feedId.equals("0")) return; // cannot train on feedless stories
StoryIntelTrainerFragment intelFrag = StoryIntelTrainerFragment.newInstance(story, fs);
intelFrag.show(getFragmentManager(), StoryIntelTrainerFragment.class.getName());
intelFrag.show(getParentFragmentManager(), StoryIntelTrainerFragment.class.getName());
}
});
binding.readingItemTitle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW);
try {
i.setData(Uri.parse(story.permalink));
startActivity(i);
UIUtils.handleUri(requireContext(), Uri.parse(story.permalink));
} catch (Throwable t) {
// we don't actually know if the user will successfully be able to open whatever string
// was in the permalink or if the Intent could throw errors
@ -558,7 +556,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onClick(View view) {
if (story.feedId.equals("0")) return; // cannot train on feedless stories
StoryIntelTrainerFragment intelFrag = StoryIntelTrainerFragment.newInstance(story, fs);
intelFrag.show(getFragmentManager(), StoryIntelTrainerFragment.class.getName());
intelFrag.show(getParentFragmentManager(), StoryIntelTrainerFragment.class.getName());
}
});
}

View file

@ -3,9 +3,9 @@ package com.newsblur.fragment;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View file

@ -5,8 +5,8 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -5,7 +5,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;

View file

@ -4,8 +4,8 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.newsblur.R;
import com.newsblur.network.APIManager;

View file

@ -10,8 +10,8 @@ import java.util.Set;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@ -28,7 +28,6 @@ import com.newsblur.domain.Reply;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.domain.UserProfile;
import com.newsblur.fragment.ReplyDialogFragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -56,7 +55,7 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
public SetupCommentSectionTask(ReadingItemFragment fragment, View view, LayoutInflater inflater, Story story) {
this.fragment = fragment;
this.context = fragment.getActivity();
this.manager = fragment.getFragmentManager();
this.manager = fragment.getParentFragmentManager();
this.inflater = inflater;
this.story = story;
viewHolder = new WeakReference<View>(view);

View file

@ -5,7 +5,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import androidx.fragment.app.DialogFragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;

Some files were not shown because too many files have changed in this diff Show more