Merge branch 'django1.10' into django1.11

* django1.10: (102 commits)
  Beginning fix of rss_feeds unit tests.
  Fixing unit test for profile app and signup.
  Remove highlights count when it reaches zero.
  Android v10.1b1.
  Stubbing in profile tests.
  Adding nginx.local.conf
  Adding original text and original story to API docs.
  #1282 Adding feed to root folder
  #1319 In app and external browser options
  Adding a smarter wakeup for real-time to handle cases where a laptop is re-opened but real-time is not immediately reestablished.
  #1348 (scroll indicators theme)
  #1344 (search loses focus)
  #1335 Auto theme option for OS level dark mode
  Fixing signup flow.
  #1347 Show pager with stories after using the intel trainer and refreshing
  #1272 Load HTML in comments
  New icon for Infrequent Site Stories.
  Allowing selection in private notes.
  Autoresizing private notes field.
  For #1035: Adding private notes to saved stories.
  ...
This commit is contained in:
Samuel Clay 2020-08-10 17:52:24 -04:00
commit ac1471017d
145 changed files with 9224 additions and 1276 deletions

View file

@ -161,6 +161,7 @@ these after the installation below.
Then load up the database with empty NewsBlur tables and bootstrap the database:
./manage.py syncdb --all --noinput
./manage.py migrate --fake
./manage.py migrate
./manage.py loaddata config/fixtures/bootstrap.json

View file

@ -38,25 +38,6 @@
"user": 1,
"last_read_date": "2009-07-28 23:17:27"
}
},
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "conesus",
"first_name": "",
"last_name": "",
"is_active": 1,
"is_superuser": 1,
"is_staff": 1,
"last_login": "2009-04-07 19:22:24",
"groups": [],
"user_permissions": [],
"password": "sha1$7b94b$ac9e6cf08d0fa16a67e56e319c0935aeb26db2a2",
"email": "samuel@newsblur.com",
"date_joined": "2009-01-04 17:32:58"
}
}
]

View file

@ -139,9 +139,9 @@ class ClassifierTest(TestCase):
# user = User.objects.all()
# feed = Feed.objects.all()
management.call_command('loaddata', 'brownstoner.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'brownstoner.json', verbosity=0, commit=False, skip_checks=False)
management.call_command('refresh_feed', force=1, feed=1, single_threaded=True, daemonize=False, skip_checks=False)
management.call_command('loaddata', 'brownstoner2.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'brownstoner2.json', verbosity=0, commit=False, skip_checks=False)
management.call_command('refresh_feed', force=1, feed=1, single_threaded=True, daemonize=False, skip_checks=False)
stories = MStory.objects(story_feed_id=1)[:53]

View file

@ -10,5 +10,6 @@ urlpatterns = [
url(r'^add_site/?$', views.add_site_authed, name='api-add-site-authed'),
url(r'^check_share_on_site/(?P<token>\w+)', views.check_share_on_site, name='api-check-share-on-site'),
url(r'^share_story/(?P<token>\w+)', views.share_story, name='api-share-story'),
url(r'^save_story/(?P<token>\w+)', views.save_story, name='api-save-story'),
url(r'^share_story/?$', views.share_story),
]

View file

@ -12,7 +12,7 @@ from django.contrib.auth import logout as logout_user
from apps.reader.forms import SignupForm, LoginForm
from apps.profile.models import Profile
from apps.social.models import MSocialProfile, MSharedStory, MSocialSubscription
from apps.rss_feeds.models import Feed
from apps.rss_feeds.models import Feed, MStarredStoryCounts, MStarredStory
from apps.rss_feeds.text_importer import TextImporter
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory
from utils import json_functions as json
@ -80,8 +80,10 @@ def logout(request):
def add_site_load_script(request, token):
code = 0
usf = None
profile = None;
user_profile = None;
profile = None
user_profile = None
starred_counts = {}
def image_base64(image_name, path='icons/circular/'):
image_file = open(os.path.join(settings.MEDIA_ROOT, 'img/%s%s' % (path, image_name)))
return base64.b64encode(image_file.read())
@ -99,6 +101,7 @@ def add_site_load_script(request, token):
user=profile.user
)
user_profile = MSocialProfile.get_user(user_id=profile.user.pk)
starred_counts = MStarredStoryCounts.user_counts(profile.user.pk)
else:
code = -1
except Profile.DoesNotExist:
@ -112,6 +115,7 @@ def add_site_load_script(request, token):
'folders': (usf and usf.folders) or [],
'user': profile and profile.user or {},
'user_profile': user_profile and json.encode(user_profile.canonical()) or {},
'starred_counts': json.encode(starred_counts),
'accept_image': accept_image,
'error_image': error_image,
'add_image': add_image,
@ -301,7 +305,6 @@ def share_story(request, token=None):
else:
message = "Not authenticated, no token supplied and not authenticated."
if not profile:
return HttpResponse(json.encode({
'code': code,
@ -391,3 +394,106 @@ def share_story(request, token=None):
response['Access-Control-Allow-Methods'] = 'POST'
return response
@required_params('story_url', 'title')
def save_story(request, token=None):
code = 0
story_url = request.POST['story_url']
user_tags = request.POST.getlist('user_tags') or request.REQUEST.getlist('user_tags[]') or []
add_user_tag = request.POST.get('add_user_tag', None)
title = request.POST['title']
content = request.POST.get('content', None)
rss_url = request.POST.get('rss_url', None)
user_notes = request.POST.get('user_notes', None)
feed_id = request.POST.get('feed_id', None) or 0
feed = None
message = None
profile = None
if request.user.is_authenticated():
profile = request.user.profile
else:
try:
profile = Profile.objects.get(secret_token=token)
except Profile.DoesNotExist:
code = -1
if token:
message = "Not authenticated, couldn't find user by token."
else:
message = "Not authenticated, no token supplied and not authenticated."
if not profile:
return HttpResponse(json.encode({
'code': code,
'message': message,
'story': None,
}), mimetype='text/plain')
if feed_id:
feed = Feed.get_by_id(feed_id)
else:
if rss_url:
logging.user(request.user, "~FBFinding feed (save_story): %s" % rss_url)
feed = Feed.get_feed_from_url(rss_url, create=True, fetch=True)
if not feed:
logging.user(request.user, "~FBFinding feed (save_story): %s" % story_url)
feed = Feed.get_feed_from_url(story_url, create=True, fetch=True)
if feed:
feed_id = feed.pk
if content:
content = lxml.html.fromstring(content)
content.make_links_absolute(story_url)
content = lxml.html.tostring(content)
else:
importer = TextImporter(story=None, story_url=story_url, request=request, debug=settings.DEBUG)
document = importer.fetch(skip_save=True, return_document=True)
content = document['content']
if not title:
title = document['title']
if add_user_tag:
user_tags = user_tags + [tag for tag in add_user_tag.split(',')]
starred_story = MStarredStory.objects.filter(user_id=profile.user.pk,
story_feed_id=feed_id,
story_guid=story_url).limit(1).first()
if not starred_story:
story_db = {
"story_guid": story_url,
"story_permalink": story_url,
"story_title": title,
"story_feed_id": feed_id,
"story_content": content,
"story_date": datetime.datetime.now(),
"starred_date": datetime.datetime.now(),
"user_id": profile.user.pk,
"user_tags": user_tags,
"user_notes": user_notes,
}
starred_story = MStarredStory.objects.create(**story_db)
logging.user(profile.user, "~BM~FCStarring story from site: ~SB%s: %s" % (story_url, user_tags))
message = "Saving story from site: %s: %s" % (story_url, user_tags)
else:
starred_story.story_content = content
starred_story.story_title = title
starred_story.user_tags = user_tags
starred_story.story_permalink = story_url
starred_story.story_guid = story_url
starred_story.story_feed_id = feed_id
starred_story.user_notes = user_notes
starred_story.save()
logging.user(profile.user, "~BM~FC~SBUpdating~SN starred story from site: ~SB%s: %s" % (story_url, user_tags))
message = "Updating saved story from site: %s: %s" % (story_url, user_tags)
MStarredStoryCounts.schedule_count_tags_for_user(request.user.pk)
response = HttpResponse(json.encode({
'code': code,
'message': message,
'story': starred_story,
}), mimetype='text/plain')
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'POST'
return response

View file

@ -1,30 +1,4 @@
[
{
"pk": 2,
"model": "sites.site",
"fields": {
"domain": "testserver",
"name": "testserver"
}
},
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "conesus",
"first_name": "",
"last_name": "",
"is_active": 1,
"is_superuser": 1,
"is_staff": 1,
"last_login": "2009-04-07 19:22:24",
"groups": [],
"user_permissions": [],
"password": "sha1$7b94b$ac9e6cf08d0fa16a67e56e319c0935aeb26db2a2",
"email": "samuel@newsblur.com",
"date_joined": "2009-01-04 17:32:58"
}
},
{
"pk": 1,
"model": "reader.usersubscriptionfolders",

View file

@ -10,7 +10,7 @@ from django.db import models
from django.contrib.auth.models import User
from mongoengine.queryset import OperationError
import vendor.opml as opml
from apps.rss_feeds.models import Feed, DuplicateFeed, MStarredStory
from apps.rss_feeds.models import Feed, DuplicateFeed
from apps.reader.models import UserSubscription, UserSubscriptionFolders
from utils import json_functions as json, urlnorm
from utils import log as logging

View file

@ -2,7 +2,6 @@ from celery.task import Task
from django.contrib.auth.models import User
from apps.feed_import.models import UploadedOPML, OPMLImporter
from apps.reader.models import UserSubscription
from apps.rss_feeds.models import MStarredStory
from utils import log as logging

37
apps/profile/tests.py Normal file
View file

@ -0,0 +1,37 @@
from utils import json_functions as json
from django.test.client import Client
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.conf import settings
from mongoengine.connection import connect, disconnect
class ProfileTest(TestCase):
fixtures = [
'subscriptions.json',
'rss_feeds.json',
]
def setUp(self):
disconnect()
settings.MONGODB = connect('test_newsblur')
self.client = Client(HTTP_USER_AGENT='Mozilla/5.0')
def tearDown(self):
settings.MONGODB.drop_database('test_newsblur')
def test_create_account(self):
resp = self.client.get(reverse('load-feeds'))
response = json.decode(resp.content)
self.assertEquals(response['authenticated'], False)
response = self.client.post(reverse('welcome-signup'), {
'signup-username': 'test',
'signup-password': 'password',
'signup-email': 'test@newsblur.com',
})
self.assertEquals(response.status_code, 302)
resp = self.client.get(reverse('load-feeds'))
response = json.decode(resp.content)
self.assertEquals(response['authenticated'], True)

View file

@ -366,7 +366,7 @@ class PSHBUpdateTestCase(PSHBTestBase, TestCase):
self.assertEquals(self.requests[0][0],
'http://myhub.example.com/endpoint')
self.assertEquals(self.requests[0][1]['callback'],
'http://testserver/1/')
'http://test.nb.local.com/1/')
self.assert_((self.requests[0][1]['lease_seconds'] - 86400) < 5)
def test_update_with_changed_self(self):
@ -414,7 +414,7 @@ class PSHBUpdateTestCase(PSHBTestBase, TestCase):
self.assertEquals(self.requests[0][0],
'http://myhub.example.com/endpoint')
self.assertEquals(self.requests[0][1]['callback'],
'http://testserver/1/')
'http://test.nb.local.com/1/')
self.assert_((self.requests[0][1]['lease_seconds'] - 86400) < 5)
def test_update_with_changed_hub_and_self(self):
@ -463,5 +463,5 @@ class PSHBUpdateTestCase(PSHBTestBase, TestCase):
self.assertEquals(self.requests[0][0],
'http://myhub.example.com/endpoint')
self.assertEquals(self.requests[0][1]['callback'],
'http://testserver/1/')
'http://test.nb.local.com/1/')
self.assert_((self.requests[0][1]['lease_seconds'] - 86400) < 5)

View file

@ -1,18 +1,4 @@
[
{
"pk": 1,
"model": "reader.usersubscription",
"fields": {
"feed": 1,
"unread_count_updated": "2009-08-01 00:23:42",
"mark_read_date": "2009-07-28 23:17:27",
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"last_read_date": "2009-07-28 23:17:27"
}
},
{
"pk": 1,
"model": "reader.usersubscriptionfolders",
@ -26,7 +12,7 @@
"model": "reader.usersubscriptionfolders",
"fields": {
"folders": "[5299728, 644144, 1187026, {\"Brainiacs & Opinion\": [569, 38, 3581, 183139, 1186180, 15]}, {\"Science & Technology\": [731503, 140145, 1272495, 76, 161, 39, {\"Hacker\": [5985150, 3323431]}]}, {\"Humor\": [212379, 3530, 5994357]}, {\"Videos\": [3240, 5168]}]",
"user": 2
"user": 4
}
},
@ -40,7 +26,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -54,7 +40,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -68,7 +54,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -82,7 +68,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -96,7 +82,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -110,7 +96,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -124,7 +110,7 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
@ -138,22 +124,13 @@
"unread_count_neutral": 0,
"unread_count_positive": 0,
"unread_count_negative": 0,
"user": 1,
"user": 3,
"last_read_date": "2009-07-28 23:17:27"
}
},
{
"pk": 2,
"model": "sites.site",
"fields": {
"domain": "testserver",
"name": "testserver"
}
},
{
"pk": 1,
"pk": 3,
"model": "auth.user",
"fields": {
"username": "conesus",
@ -171,7 +148,7 @@
}
},
{
"pk": 2,
"pk": 4,
"model": "auth.user",
"fields": {
"username": "Dejal",
@ -198,7 +175,7 @@
"unread_count_positive": 0,
"mark_read_date": "2010-06-28 19:36:44",
"unread_count_negative": 3,
"user": 1,
"user": 3,
"last_read_date": "2010-06-15 19:30:35",
"unread_count_neutral": 0
}

View file

@ -626,7 +626,9 @@ class UserSubscription(models.Model):
if cutoff_date:
cutoff_date = cutoff_date + datetime.timedelta(seconds=1)
else:
latest_story = MStory.objects(story_feed_id=self.feed.pk)\
now = datetime.datetime.now()
latest_story = MStory.objects(story_feed_id=self.feed.pk,
story_date__lte=now)\
.order_by('-story_date').only('story_date').limit(1)
if latest_story and len(latest_story) >= 1:
cutoff_date = (latest_story[0]['story_date']

View file

@ -199,6 +199,8 @@ def signup(request):
url = "https://%s%s" % (Site.objects.get_current().domain,
reverse('stripe-form'))
return HttpResponseRedirect(url)
else:
return HttpResponseRedirect(reverse('index'))
return index(request)
@ -695,8 +697,7 @@ def load_single_feed(request, feed_id):
starred_stories = MStarredStory.objects(user_id=user.pk,
story_feed_id=feed.pk,
story_hash__in=story_hashes)\
.hint([('user_id', 1), ('story_hash', 1)])\
.only('story_hash', 'starred_date', 'user_tags')
.hint([('user_id', 1), ('story_hash', 1)])
shared_story_hashes = MSharedStory.check_shared_story_hashes(user.pk, story_hashes)
shared_stories = []
if shared_story_hashes:
@ -704,8 +705,7 @@ def load_single_feed(request, feed_id):
story_hash__in=shared_story_hashes)\
.hint([('story_hash', 1)])\
.only('story_hash', 'shared_date', 'comments')
starred_stories = dict([(story.story_hash, dict(starred_date=story.starred_date,
user_tags=story.user_tags))
starred_stories = dict([(story.story_hash, story)
for story in starred_stories])
shared_stories = dict([(story.story_hash, dict(shared_date=story.shared_date,
comments=story.comments))
@ -730,11 +730,14 @@ def load_single_feed(request, feed_id):
story['read_status'] = 0
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['story_hash']]['starred_date'],
starred_story = Feed.format_story(starred_stories[story['story_hash']])
starred_date = localtime_for_timezone(starred_story['starred_date'],
user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['starred_timestamp'] = starred_date.strftime('%s')
story['user_tags'] = starred_stories[story['story_hash']]['user_tags']
story['user_tags'] = starred_story['user_tags']
story['user_notes'] = starred_story['user_notes']
story['highlights'] = starred_story['highlights']
if story['story_hash'] in shared_stories:
story['shared'] = True
shared_date = localtime_for_timezone(shared_stories[story['story_hash']]['shared_date'],
@ -885,6 +888,7 @@ def load_starred_stories(request):
query = request.GET.get('query', '').strip()
order = request.GET.get('order', 'newest')
tag = request.GET.get('tag')
highlights = is_true(request.GET.get('highlights', False))
story_hashes = request.GET.getlist('h') or request.GET.getlist('h[]')
story_hashes = story_hashes[:100]
version = int(request.GET.get('v', 1))
@ -902,6 +906,17 @@ def load_starred_stories(request):
else:
stories = []
message = "You must be a premium subscriber to search."
elif highlights:
if user.profile.is_premium:
mstories = MStarredStory.objects(
user_id=user.pk,
highlights__exists=True,
__raw__={"$where": "this.highlights.length > 0"}
).order_by('%sstarred_date' % order_by)[offset:offset+limit]
stories = Feed.format_stories(mstories)
else:
stories = []
message = "You must be a premium subscriber to read through saved story highlights."
elif tag:
if user.profile.is_premium:
mstories = MStarredStory.objects(
@ -1048,6 +1063,12 @@ def starred_stories_rss_feed(request, user_id, secret_token, tag_slug):
starred_stories = MStarredStory.objects(
user_id=user.pk
).order_by('-starred_date').limit(25)
elif tag_counts.is_highlights:
starred_stories = MStarredStory.objects(
user_id=user.pk,
highlights__exists=True,
__raw__={"$where": "this.highlights.length > 0"}
).order_by('-starred_date').limit(25)
else:
starred_stories = MStarredStory.objects(
user_id=user.pk,
@ -1247,9 +1268,8 @@ def load_read_stories(request):
for story in shared_stories])
starred_stories = MStarredStory.objects(user_id=user.pk,
story_hash__in=story_hashes)\
.hint([('user_id', 1), ('story_hash', 1)])\
.only('story_hash', 'starred_date')
starred_stories = dict([(story.story_hash, story.starred_date)
.hint([('user_id', 1), ('story_hash', 1)])
starred_stories = dict([(story.story_hash, story)
for story in starred_stories])
nowtz = localtime_for_timezone(now, user.profile.timezone)
@ -1266,7 +1286,8 @@ def load_read_stories(request):
}
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['story_hash']],
starred_story = Feed.format_story(starred_stories[story['story_hash']])
starred_date = localtime_for_timezone(starred_story['starred_date'],
user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['starred_timestamp'] = starred_date.strftime('%s')
@ -1394,10 +1415,11 @@ def load_river_stories__redis(request):
story_hashes = [s['story_hash'] for s in stories]
starred_stories = MStarredStory.objects(
user_id=user.pk,
story_hash__in=story_hashes
).only('story_hash', 'starred_date', 'user_tags')
story_hash__in=story_hashes)
starred_stories = dict([(story.story_hash, dict(starred_date=story.starred_date,
user_tags=story.user_tags))
user_tags=story.user_tags,
highlights=story.highlights,
user_notes=story.user_notes))
for story in starred_stories])
else:
starred_stories = {}
@ -1445,6 +1467,8 @@ def load_river_stories__redis(request):
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['starred_timestamp'] = starred_date.strftime('%s')
story['user_tags'] = starred_stories[story['story_hash']]['user_tags']
story['user_notes'] = starred_stories[story['story_hash']]['user_notes']
story['highlights'] = starred_stories[story['story_hash']]['highlights']
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id']),
'author': apply_classifier_authors(classifier_authors, story),
@ -2393,6 +2417,8 @@ def _mark_story_as_starred(request):
story_id = request.POST.get('story_id', None)
story_hash = request.POST.get('story_hash', None)
user_tags = request.POST.getlist('user_tags') or request.POST.getlist('user_tags[]')
user_notes = request.POST.get('user_notes', None)
highlights = request.POST.getlist('highlights') or request.POST.getlist('highlights[]') or []
message = ""
if story_hash:
story, _ = MStory.find_story(story_hash=story_hash)
@ -2405,16 +2431,23 @@ def _mark_story_as_starred(request):
story_db = dict([(k, v) for k, v in story._data.items()
if k is not None and v is not None])
# Pop all existing user-specific fields because we don't want to reuse them from the found story
# in case MStory.find_story uses somebody else's saved/shared story (because the original is deleted)
story_db.pop('user_id', None)
story_db.pop('starred_date', None)
story_db.pop('id', None)
story_db.pop('user_tags', None)
story_db.pop('highlights', None)
story_db.pop('user_notes', None)
now = datetime.datetime.now()
story_values = dict(starred_date=now, user_tags=user_tags, **story_db)
story_values = dict(starred_date=now, user_tags=user_tags, highlights=highlights, user_notes=user_notes, **story_db)
params = dict(story_guid=story.story_guid, user_id=request.user.pk)
starred_story = MStarredStory.objects(**params).limit(1)
created = False
changed_user_notes = False
removed_user_tags = []
removed_highlights = []
if not starred_story:
params.update(story_values)
if params.has_key('story_latest_content_z'):
@ -2431,14 +2464,26 @@ def _mark_story_as_starred(request):
story_feed_id=feed_id,
story_id=starred_story.story_guid)
new_user_tags = user_tags
new_highlights = highlights
changed_user_notes = bool(user_notes)
MStarredStoryCounts.adjust_count(request.user.pk, feed_id=feed_id, amount=1)
else:
starred_story = starred_story[0]
new_user_tags = list(set(user_tags) - set(starred_story.user_tags or []))
removed_user_tags = list(set(starred_story.user_tags or []) - set(user_tags))
new_highlights = list(set(highlights) - set(starred_story.highlights or []))
removed_highlights = list(set(starred_story.highlights or []) - set(highlights))
changed_user_notes = bool(user_notes != starred_story.user_notes)
starred_story.user_tags = user_tags
starred_story.highlights = highlights
starred_story.user_notes = user_notes
starred_story.save()
if len(highlights) == 1 and len(new_highlights) == 1:
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=1)
elif len(highlights) == 0 and len(removed_highlights):
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=-1)
for tag in new_user_tags:
MStarredStoryCounts.adjust_count(request.user.pk, tag=tag, amount=1)
for tag in removed_user_tags:
@ -2451,13 +2496,14 @@ def _mark_story_as_starred(request):
if not starred_count and len(starred_counts):
starred_count = MStarredStory.objects(user_id=request.user.pk).count()
if not changed_user_notes:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(request.user.username, 'story:starred:%s' % story.story_hash)
if created:
logging.user(request, "~FCStarring: ~SB%s (~FM~SB%s~FC~SN)" % (story.story_title[:32], starred_story.user_tags))
else:
logging.user(request, "~FCUpdating starred:~SN~FC ~SB%s~SN (~FM~SB%s~FC~SN)" % (story.story_title[:32], starred_story.user_tags))
logging.user(request, "~FCUpdating starred:~SN~FC ~SB%s~SN (~FM~SB%s~FC~SN/~FM%s~FC)" % (story.story_title[:32], starred_story.user_tags, starred_story.user_notes))
return {'code': code, 'message': message, 'starred_count': starred_count, 'starred_counts': starred_counts}

View file

@ -1,27 +0,0 @@
[
{
"pk": 2,
"model": "sites.site",
"fields": {
"domain": "testserver",
"name": "testserver"
}
},
{
"model": "auth.user",
"fields": {
"username": "newsblur_test",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"last_login": "2011-07-18 00:23:49",
"groups": [],
"user_permissions": [],
"password": "sha1$d5473$d07ce4495b088ff0f41a62d5113d0189ce8f0096",
"email": "samuel@newsblur.com",
"date_joined": "2011-07-18 00:23:49"
}
}
]

View file

@ -1,22 +1,46 @@
[
{
"pk": 6,
"pk": 16,
"model": "rss_feeds.feed",
"fields": {
"premium_subscribers": -1,
"creation": "2011-08-27",
"exception_code": 0,
"last_load_time": 0,
"active_subscribers": 1,
"feed_address": "%(NEWSBLUR_DIR)s/apps/rss_feeds/fixtures/brokelyn.xml",
"days_to_trim": 90,
"feed_link": "%(NEWSBLUR_DIR)s/apps/rss_feeds/fixtures/brokelyn.html",
"hash_address_and_link": "16",
"feed_link_locked": true,
"num_subscribers": 0,
"creation": "2009-01-12",
"last_update": "2011-08-27 02:45:21",
"etag": null,
"average_stories_per_month": 0,
"feed_title": "Brokelyn",
"last_update": "2009-07-06 22:30:03",
"next_scheduled_update": "2009-07-06 22:30:03",
"last_story_date": "2009-07-06 22:30:03",
"min_to_decay": 1,
"etag": "",
"last_modified": "2009-07-06 22:30:03",
"active": 1
"last_modified": null,
"next_scheduled_update": "2011-08-28 14:33:50",
"favicon_color": null,
"stories_last_month": 0,
"active": true,
"favicon_not_found": false,
"has_page_exception": false,
"fetched_once": false,
"days_to_trim": 90,
"num_subscribers": 1,
"last_story_date": "2011-08-28 00:03:50",
"min_to_decay": 720,
"has_feed_exception": false
}
},
{
"pk": 16,
"model": "rss_feeds.feeddata",
"fields": {
"feed": 16,
"feed_classifier_counts": null,
"feed_tagline": "Visual feed reading with intelligence.",
"popular_tags": "[]",
"story_count_history": null,
"popular_authors": "[]"
}
}
]

View file

@ -1,6 +1,6 @@
[
{
"pk": 1,
"pk": 10,
"model": "rss_feeds.feed",
"fields": {
"premium_subscribers": -1,
@ -10,6 +10,7 @@
"active_subscribers": 1,
"feed_address": "%(NEWSBLUR_DIR)s/apps/rss_feeds/fixtures/gawker1.xml",
"feed_link": "%(NEWSBLUR_DIR)s/apps/rss_feeds/fixtures/gawker1.html",
"hash_address_and_link": "10",
"feed_link_locked": true,
"last_update": "2011-08-27 02:45:21",
"etag": null,

View file

@ -0,0 +1 @@
../../../config/fixtures/bootstrap.json

View file

@ -110,34 +110,6 @@
}
},
{
"pk": 2,
"model": "sites.site",
"fields": {
"domain": "testserver",
"name": "testserver"
}
},
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "conesus",
"first_name": "",
"last_name": "",
"is_active": 1,
"is_superuser": 1,
"is_staff": 1,
"last_login": "2009-07-06 22:30:03",
"groups": [],
"user_permissions": [],
"password": "sha1$7b94b$ac9e6cf08d0fa16a67e56e319c0935aeb26db2a2",
"email": "samuel@newsblur.com",
"date_joined": "2009-01-04 17:32:58"
}
},
{
"pk": 2,

View file

@ -1881,7 +1881,7 @@ class Feed(models.Model):
story_title = strip_tags(story_content)
if not story_title and story_db.story_permalink:
story_title = story_db.story_permalink
if len(story_title) > 80:
if story_title and len(story_title) > 80:
story_title = story_title[:80] + '...'
story = {}
@ -1912,6 +1912,10 @@ class Feed(models.Model):
story['starred_date'] = story_db.starred_date
if hasattr(story_db, 'user_tags'):
story['user_tags'] = story_db.user_tags
if hasattr(story_db, 'user_notes'):
story['user_notes'] = story_db.user_notes
if hasattr(story_db, 'highlights'):
story['highlights'] = story_db.highlights
if hasattr(story_db, 'shared_date'):
story['shared_date'] = story_db.shared_date
if hasattr(story_db, 'comments'):
@ -2844,7 +2848,9 @@ class MStarredStory(mongo.DynamicDocument):
story_guid = mongo.StringField()
story_hash = mongo.StringField()
story_tags = mongo.ListField(mongo.StringField(max_length=250))
user_notes = mongo.StringField()
user_tags = mongo.ListField(mongo.StringField(max_length=128))
highlights = mongo.ListField(mongo.StringField(max_length=1024))
image_urls = mongo.ListField(mongo.StringField(max_length=1024))
meta = {
@ -2856,6 +2862,16 @@ class MStarredStory(mongo.DynamicDocument):
'strict': False,
}
def __unicode__(self):
try:
user = User.objects.get(pk=self.user_id)
username = user.username
except User.DoesNotExist:
username = '[deleted]'
return "%s: %s (%s)" % (username,
self.story_title[:20],
self.story_feed_id)
def save(self, *args, **kwargs):
if self.story_content:
self.story_content_z = zlib.compress(self.story_content)
@ -2964,6 +2980,7 @@ class MStarredStoryCounts(mongo.Document):
user_id = mongo.IntField()
tag = mongo.StringField(max_length=128)
feed_id = mongo.IntField()
is_highlights = mongo.BooleanField()
slug = mongo.StringField(max_length=128)
count = mongo.IntField(default=0)
@ -2974,6 +2991,16 @@ class MStarredStoryCounts(mongo.Document):
'allow_inheritance': False,
}
def __unicode__(self):
if self.tag:
return "Tag: %s (%s)" % (self.tag, self.count)
elif self.feed_id:
return "Feed: %s (%s)" % (self.feed_id, self.count)
elif self.is_highlights:
return "Highlights: %s (%s)" % (self.is_highlights, self.count)
return "%s/%s/%s" % (self.tag, self.feed_id, self.is_highlights)
@property
def rss_url(self, secret_token=None):
if self.feed_id:
@ -2992,6 +3019,7 @@ class MStarredStoryCounts(mongo.Document):
counts = cls.objects.filter(user_id=user_id)
counts = sorted([{'tag': c.tag,
'count': c.count,
'is_highlights': c.is_highlights,
'feed_address': c.rss_url,
'feed_id': c.feed_id}
for c in counts],
@ -3000,7 +3028,7 @@ class MStarredStoryCounts(mongo.Document):
total = 0
feed_total = 0
for c in counts:
if not c['tag'] and not c['feed_id']:
if not c['tag'] and not c['feed_id'] and not c['is_highlights']:
total = c['count']
if c['feed_id']:
feed_total += c['count']
@ -3025,20 +3053,22 @@ class MStarredStoryCounts(mongo.Document):
def count_for_user(cls, user_id, total_only=False):
user_tags = []
user_feeds = []
highlights = 0
if not total_only:
cls.objects(user_id=user_id).delete()
try:
user_tags = cls.count_tags_for_user(user_id)
highlights = cls.count_highlights_for_user(user_id)
user_feeds = cls.count_feeds_for_user(user_id)
except pymongo.errors.OperationFailure, e:
logging.debug(" ---> ~FBOperationError on mongo: ~SB%s" % e)
total_stories_count = MStarredStory.objects(user_id=user_id).count()
cls.objects(user_id=user_id, tag=None, feed_id=None).update_one(set__count=total_stories_count,
cls.objects(user_id=user_id, tag=None, feed_id=None, is_highlights=None).update_one(set__count=total_stories_count,
upsert=True)
return dict(total=total_stories_count, tags=user_tags, feeds=user_feeds)
return dict(total=total_stories_count, tags=user_tags, feeds=user_feeds, highlights=highlights)
@classmethod
def count_tags_for_user(cls, user_id):
@ -3054,6 +3084,21 @@ class MStarredStoryCounts(mongo.Document):
return user_tags
@classmethod
def count_highlights_for_user(cls, user_id):
highlighted_count = MStarredStory.objects(user_id=user_id,
highlights__exists=True,
__raw__={"$where": "this.highlights.length > 0"}).count()
if highlighted_count > 0:
cls.objects(user_id=user_id,
is_highlights=True,
slug="highlights"
).update_one(set__count=highlighted_count, upsert=True)
else:
cls.objects(user_id=user_id, is_highlights=True, slug="highlights").delete()
return highlighted_count
@classmethod
def count_feeds_for_user(cls, user_id):
all_feeds = MStarredStory.objects(user_id=user_id).item_frequencies('story_feed_id')
@ -3079,12 +3124,14 @@ class MStarredStoryCounts(mongo.Document):
return user_feeds
@classmethod
def adjust_count(cls, user_id, feed_id=None, tag=None, amount=0):
def adjust_count(cls, user_id, feed_id=None, tag=None, highlights=None, amount=0):
params = dict(user_id=user_id)
if feed_id:
params['feed_id'] = feed_id
if tag:
params['tag'] = tag
if highlights:
params['is_highlights'] = True
cls.objects(**params).update_one(inc__count=amount, upsert=True)
try:

View file

@ -16,7 +16,7 @@ class FeedTest(TestCase):
disconnect()
settings.MONGODB = connect('test_newsblur')
settings.REDIS_STORY_HASH_POOL = redis.ConnectionPool(host=settings.REDIS_STORY['host'], port=6379, db=10)
settings.REDIS_FEED_READ_POOL = redis.ConnectionPool(host=settings.SESSION_REDIS_HOST, port=6379, db=10)
settings.REDIS_FEED_READ_POOL = redis.ConnectionPool(host=settings.REDIS_SESSIONS['host'], port=6379, db=10)
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
r.delete('RS:1')
@ -33,9 +33,9 @@ class FeedTest(TestCase):
def test_load_feeds__gawker(self):
self.client.login(username='conesus', password='test')
management.call_command('loaddata', 'gawker1.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'gawker1.json', verbosity=0, commit=False, skip_checks=False)
feed = Feed.objects.get(feed_link__contains='gawker')
feed = Feed.objects.get(pk=10)
stories = MStory.objects(story_feed_id=feed.pk)
self.assertEquals(stories.count(), 0)
@ -44,7 +44,7 @@ class FeedTest(TestCase):
stories = MStory.objects(story_feed_id=feed.pk)
self.assertEquals(stories.count(), 38)
management.call_command('loaddata', 'gawker2.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'gawker2.json', verbosity=0, commit=False, skip_checks=False)
feed.update(force=True)
@ -52,7 +52,7 @@ class FeedTest(TestCase):
stories = MStory.objects(story_feed_id=feed.pk)
self.assertEquals(stories.count(), 38)
url = reverse('load-single-feed', kwargs=dict(feed_id=1))
url = reverse('load-single-feed', kwargs=dict(feed_id=10))
response = self.client.get(url)
feed = json.decode(response.content)
self.assertEquals(len(feed['stories']), 6)
@ -60,7 +60,7 @@ class FeedTest(TestCase):
def test_load_feeds__gothamist(self):
self.client.login(username='conesus', password='test')
management.call_command('loaddata', 'gothamist_aug_2009_1.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'gothamist_aug_2009_1.json', verbosity=0, commit=False, skip_checks=False)
feed = Feed.objects.get(feed_link__contains='gothamist')
stories = MStory.objects(story_feed_id=feed.pk)
self.assertEquals(stories.count(), 0)
@ -75,7 +75,7 @@ class FeedTest(TestCase):
content = json.decode(response.content)
self.assertEquals(len(content['stories']), 6)
management.call_command('loaddata', 'gothamist_aug_2009_2.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'gothamist_aug_2009_2.json', verbosity=0, commit=False, skip_checks=False)
feed.update(force=True)
stories = MStory.objects(story_feed_id=feed.pk)
@ -93,7 +93,7 @@ class FeedTest(TestCase):
old_story_guid = "tag:google.com,2005:reader/item/4528442633bc7b2b"
management.call_command('loaddata', 'slashdot1.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'slashdot1.json', verbosity=0, commit=False, skip_checks=False)
feed = Feed.objects.get(feed_link__contains='slashdot')
stories = MStory.objects(story_feed_id=feed.pk)
@ -114,7 +114,7 @@ class FeedTest(TestCase):
content = json.decode(response.content)
self.assertEquals(content['feeds']['5']['nt'], 37)
management.call_command('loaddata', 'slashdot2.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'slashdot2.json', verbosity=0, commit=False, skip_checks=False)
management.call_command('refresh_feed', force=1, feed=5, single_threaded=True, daemonize=False, skip_checks=False)
stories = MStory.objects(story_feed_id=feed.pk)
@ -136,7 +136,7 @@ class FeedTest(TestCase):
def test_load_feeds__motherjones(self):
self.client.login(username='conesus', password='test')
management.call_command('loaddata', 'motherjones1.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'motherjones1.json', verbosity=0, commit=False, skip_checks=False)
feed = Feed.objects.get(feed_link__contains='motherjones')
stories = MStory.objects(story_feed_id=feed.pk)
@ -157,7 +157,7 @@ class FeedTest(TestCase):
content = json.decode(response.content)
self.assertEquals(content['feeds'][str(feed.pk)]['nt'], 9)
management.call_command('loaddata', 'motherjones2.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'motherjones2.json', verbosity=0, commit=False, skip_checks=False)
management.call_command('refresh_feed', force=1, feed=feed.pk, single_threaded=True, daemonize=False, skip_checks=False)
stories = MStory.objects(story_feed_id=feed.pk)
@ -225,19 +225,23 @@ class FeedTest(TestCase):
self.assertEquals(content['feeds']['766']['nt'], 19)
def test_load_feeds__brokelyn__invalid_xml(self):
BROKELYN_FEED_ID = 16
self.client.login(username='conesus', password='test')
management.call_command('loaddata', 'brokelyn.json', verbosity=0, commit=False)
self.assertEquals(Feed.objects.get(pk=BROKELYN_FEED_ID).pk, BROKELYN_FEED_ID)
management.call_command('refresh_feed', force=1, feed=BROKELYN_FEED_ID, single_threaded=True, daemonize=False)
management.call_command('loaddata', 'brokelyn.json', verbosity=0, skip_checks=False)
management.call_command('loaddata', 'brokelyn.json', verbosity=0, commit=False, skip_checks=False)
management.call_command('refresh_feed', force=1, feed=6, single_threaded=True, daemonize=False, skip_checks=False)
url = reverse('load-single-feed', kwargs=dict(feed_id=6))
url = reverse('load-single-feed', kwargs=dict(feed_id=BROKELYN_FEED_ID))
response = self.client.get(url)
# pprint([c['story_title'] for c in json.decode(response.content)])
feed = json.decode(response.content)
# Test: 1 changed char in title
self.assertEquals(len(feed['stories']), 6)
self.assertEquals(len(feed['stories']), 0)
def test_all_feeds(self):
pass

View file

@ -1255,8 +1255,10 @@ class MSocialSubscription(mongo.Document):
cutoff_date = cutoff_date + datetime.timedelta(seconds=1)
else:
# Use the latest story to get last read time.
now = datetime.datetime.now()
latest_shared_story = MSharedStory.objects(user_id=self.subscription_user_id,
shared_date__gte=user_profile.unread_cutoff
shared_date__gte=user_profile.unread_cutoff,
story_date__lte=now
).order_by('-shared_date').only('shared_date').first()
if latest_shared_story:
cutoff_date = latest_shared_story['shared_date'] + datetime.timedelta(seconds=1)

View file

@ -1304,6 +1304,7 @@ def shared_stories_rss_feed_noid(request):
return index
@ratelimit(minutes=1, requests=5)
def shared_stories_rss_feed(request, user_id, username):
try:
user = User.objects.get(pk=user_id)

View file

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="167"
android:versionName="10.0" >
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="28" />
package="com.newsblur">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -132,6 +126,9 @@
<activity
android:name=".activity.FolderReading"/>
<activity
android:name=".activity.InAppBrowser" />
<activity
android:name=".activity.SearchForFeeds" android:launchMode="singleTop" >
<intent-filter>

View file

@ -8,7 +8,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.android.tools.build:gradle:4.0.0'
}
}
@ -22,27 +22,29 @@ repositories {
apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
apply plugin: 'findbugs'
dependencies {
compile 'com.android.support:support-core-utils:27.1.1'
compile 'com.android.support:support-fragment:27.1.1'
compile 'com.android.support:support-core-ui:27.1.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.squareup.okhttp3:okhttp:3.8.1'
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.android.support:recyclerview-v7:27.1.1'
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 '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'
}
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdkVersion 28
defaultConfig {
applicationId "com.newsblur"
minSdkVersion 21
targetSdkVersion 28
versionCode 168
versionName "10.1"
}
compileOptions.with {
sourceCompatibility = JavaVersion.VERSION_1_7
}
// force old processing behaviour that butterknife 7 depends on, until we can afford to upgrade
android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true
viewBinding.enabled = true
sourceSets {
main {

View file

@ -11,16 +11,6 @@
-dontwarn okhttp3.**
-dontnote okhttp3.**
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
# these two seem to confuse ProGuard, so force keep them
-keep class com.newsblur.util.StateFilter { *; }
-keep class com.newsblur.view.StateToggleButton$StateChangedListener { *; }

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7B7B7B"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM19,14h-3v3h-2v-3h-3v-2h3L14,9h2v3h3v2z" />
</vector>

View file

@ -0,0 +1,20 @@
<?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">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-6dp"
android:max="100"
android:visibility="gone"/>
</FrameLayout>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
android:id="@+id/text_sync_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/status_overlay_background"
android:gravity="center"
android:padding="2dp"
android:text="@string/sync_status_feed_add"
android:textColor="@color/status_overlay_text"
android:textSize="14sp"
android:visibility="gone" />
<TextView
android:id="@+id/text_add_folder_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="14dp"
android:paddingEnd="16dp"
android:paddingBottom="14dp"
android:text="@string/add_new_folder"
android:textAllCaps="true" />
<LinearLayout
android:id="@+id/container_add_folder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:visibility="gone">
<EditText
android:id="@+id/input_folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:autofillHints="@null"
android:textSize="14sp"
android:hint="@string/new_folder_name_hint"
android:inputType="textCapCharacters"
android:maxLines="1" />
<ImageView
android:id="@+id/ic_create_folder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginEnd="12dp"
android:contentDescription="@string/description_add_new_folder_icon"
android:padding="4dp"
android:src="@drawable/ic_create_folder" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="bottom"
android:layout_marginStart="8dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="4dp"
android:indeterminate="true"
android:visibility="gone" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<android.support.v7.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" />
</LinearLayout>

View file

@ -1,17 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
android:paddingRight="10dp">
<EditText
android:id="@+id/feed_name_field"
android:id="@+id/input_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="5dp"
android:singleLine="true"
/>
android:layout_marginBottom="10dp"
android:autofillHints="@null"
android:hint="@string/new_folder_name_hint"
android:inputType="textCapSentences"
android:singleLine="true" />
</RelativeLayout>
</FrameLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_folder_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="42dp"
android:paddingStart="16dp"
android:paddingEnd="16dp" />

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="?selectorFeedBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/row_saved_search_icon"
android:layout_width="19dp"
android:layout_height="19dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:contentDescription="@string/description_row_folder_icon"
android:src="@drawable/ic_menu_search_gray55" />
<TextView
android:id="@+id/row_saved_search_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/row_saved_search_icon"
android:ellipsize="end"
android:lines="1"
android:paddingTop="9dp"
android:paddingEnd="9dp"
android:paddingBottom="9dp" />
<View
style="?rowBorderTop"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentTop="true" />
<View
style="?rowBorderBottom"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="?selectorFolderBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/row_folder_icon"
android:layout_width="19dp"
android:layout_height="19dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:contentDescription="@string/description_row_folder_icon"
android:src="@drawable/ic_menu_search_gray55" />
<TextView
android:id="@+id/row_foldername"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/row_folder_icon"
android:paddingTop="9dp"
android:paddingBottom="9dp"
android:text="@string/saved_searches_row_title"
android:textStyle="bold" />
<View
style="?rowBorderTop"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentTop="true" />
<View
style="?rowBorderBottom"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View file

@ -44,4 +44,7 @@
<item android:id="@+id/menu_intel"
android:title="@string/menu_intel" />
<item android:id="@+id/menu_delete_saved_search"
android:title="@string/menu_delete_saved_search" />
</menu>

View file

@ -10,4 +10,10 @@
<item android:id="@+id/menu_unmute_folder"
android:title="@string/menu_unmute_folder" />
<item android:id="@+id/menu_delete_folder"
android:title="@string/menu_delete_folder" />
<item android:id="@+id/menu_rename_folder"
android:title="@string/menu_rename_folder" />
</menu>

View file

@ -34,6 +34,8 @@
android:title="@string/menu_theme_choose" >
<menu>
<group android:checkableBehavior="single">
<item android:id="@+id/menu_theme_auto"
android:title="@string/auto" />
<item android:id="@+id/menu_theme_light"
android:title="@string/light" />
<item android:id="@+id/menu_theme_dark"
@ -72,4 +74,8 @@
<item android:id="@+id/menu_infrequent_cutoff"
android:title="@string/menu_infrequent_cutoff"
android:showAsAction="never" />
<item android:id="@+id/menu_save_search"
android:title="Save Search"
android:showAsAction="never"
android:visible="false"/>
</menu>

View file

@ -52,6 +52,8 @@
android:title="@string/menu_theme_choose" >
<menu>
<group android:checkableBehavior="single">
<item android:id="@+id/menu_theme_auto"
android:title="@string/auto" />
<item android:id="@+id/menu_theme_light"
android:title="@string/light" />
<item android:id="@+id/menu_theme_dark"

View file

@ -52,6 +52,8 @@
android:title="@string/menu_theme_choose" >
<menu>
<group android:checkableBehavior="single">
<item android:id="@+id/menu_theme_auto"
android:title="@string/auto" />
<item android:id="@+id/menu_theme_light"
android:title="@string/light" />
<item android:id="@+id/menu_theme_dark"

View file

@ -37,6 +37,7 @@
<string name="title_choose_folders">Choose Folders for Feed %s</string>
<string name="title_rename_feed">Rename Feed %s</string>
<string name="title_rename_folder">Rename Folder %s</string>
<string name="all_stories_row_title">ALL STORIES</string>
<string name="all_stories_title">All Stories</string>
@ -50,6 +51,7 @@
<string name="read_stories_title">Read Stories</string>
<string name="saved_stories_row_title">SAVED STORIES</string>
<string name="saved_stories_title">Saved Stories</string>
<string name="saved_searches_row_title">SAVED SEARCHES</string>
<string name="top_level">TOP LEVEL</string>
@ -63,6 +65,7 @@
<string name="unshare">DELETE SHARE</string>
<string name="update_shared">UPDATE COMMENT</string>
<string name="feed_name_save">RENAME FEED</string>
<string name="folder_name_save">RENAME FOLDER</string>
<string name="save_this">SAVE</string>
<string name="unsave_this">REMOVE FROM SAVED</string>
@ -132,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_delete_saved_search">Delete saved search</string>
<string name="menu_unfollow">Unfollow user</string>
<string name="menu_choose_folders">Choose folders</string>
<string name="menu_notifications_choose">Notifications…</string>
@ -156,6 +160,8 @@
<string name="menu_unmute_feed">Unmute feed</string>
<string name="menu_mute_folder">Mute folder</string>
<string name="menu_unmute_folder">Unmute folder</string>
<string name="menu_delete_folder">Delete folder</string>
<string name="menu_rename_folder">Rename folder</string>
<string name="menu_instafetch_feed">Insta-fetch stories</string>
<string name="menu_infrequent_cutoff">Infrequent stories per month</string>
<string name="menu_intel">Intelligence trainer</string>
@ -196,7 +202,11 @@
<string name="menu_add_feed">Add new feed</string>
<string name="menu_search">Search</string>
<string name="adding_feed_progress">Adding feed…</string>
<string name="add_folder_name">Please enter folder name</string>
<string name="add_new_folder">+ Add new folder</string>
<string name="new_folder_name_hint">Enter new folder name</string>
<string name="add_feed_error">Could not add feed</string>
<string name="add_folder_error">Could not add folder</string>
<string name="menu_mark_all_as_read">Mark all as read</string>
<string name="menu_logout">Log out</string>
<string name="menu_feedback">Send app feedback</string>
@ -204,6 +214,8 @@
<string name="menu_feedback_email">Email a bug report</string>
<string name="menu_theme_choose">Theme…</string>
<string name="description_add_new_folder_icon">Add new folder icon</string>
<string name="menu_loginas">Login as...</string>
<string name="loginas_title">Login As User</string>
@ -234,6 +246,9 @@
<string name="friends_shares_count">%d SHARES</string>
<string name="unknown_user">Unknown User</string>
<string name="delete_feed_message">Delete feed \&quot;%s\&quot;?</string>
<string name="delete_saved_search_message">Delete saved search %s?</string>
<string name="delete_folder_message">Delete folder \&quot;%s\&quot; and unsubscribe from all feeds inside?</string>
<string name="add_saved_search_message">Save search \&quot;%s\&quot;?</string>
<string name="unfollow_message">Unfollow \&quot;%s\&quot;?</string>
<string name="feed_subscribers">%s subscribers</string>
<string name="feed_opens">%d opens</string>
@ -387,6 +402,7 @@
<string name="feed_intel_author_header">AUTHORS</string>
<string name="intel_feed_header">EVERYTHING BY PUBLISHER</string>
<string name="auto">Auto</string>
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="black">Black</string>
@ -403,6 +419,7 @@
<string name="sync_status_text">Storing text for %s stories...</string>
<string name="sync_status_images">Storing %s images...</string>
<string name="sync_status_offline">Offline</string>
<string name="sync_status_feed_add">Adding feed …</string>
<string name="volume_key_navigation">Volume Key Navigation</string>
<string name="off">Off</string>
@ -477,6 +494,7 @@
</string-array>
<string name="rtl_gesture_action_value">GEST_ACTION_MARKUNREAD</string>
<string name="default_browser">Default browser</string>
<string name="font">Font</string>
<string name="default_font">Default</string>
<string name="whitney_font">Whitney</string>
@ -494,6 +512,25 @@
<string name="noto_sans_font_prefvalue">NOTO_SANS</string>
<string name="noto_serif_font_prefvalue">NOTO_SERIF</string>
<string name="open_sans_condensed_font_prefvalue">OPEN_SANS_CONDENSED</string>
<string name="system_default_prefvalue">SYSTEM_DEFAULT</string>
<string name="in_app_browser_prefvalue">IN_APP_BROWSER</string>
<string name="chrome_prefvalue">CHROME</string>
<string name="firefox_prefvalue">FIREFOX</string>
<string name="opera_mini_prefvalue">OPERA_MINI</string>
<string-array name="default_browser_entries">
<item>System default</item>
<item>In-app browser</item>
<item>Chrome</item>
<item>Firefox</item>
<item>Opera Mini</item>
</string-array>
<string-array name="default_browser_values">
<item>@string/system_default_prefvalue</item>
<item>@string/in_app_browser_prefvalue</item>
<item>@string/chrome_prefvalue</item>
<item>@string/firefox_prefvalue</item>
<item>@string/opera_mini_prefvalue</item>
</string-array>
<string-array name="default_font_entries">
<item>@string/anonymous_pro_font</item>
<item>@string/chronicle_font</item>
@ -515,6 +552,7 @@
<item>@string/whitney_font_prefvalue</item>
</string-array>
<string name="default_font_value">DEFAULT</string>
<string name="default_default_browser">@string/system_default_prefvalue</string>
<string name="story_notification_channel_id">story_notification_channel</string>
<string name="story_notification_channel_name">New Stories</string>

View file

@ -105,6 +105,13 @@
<PreferenceCategory
android:title="@string/settings_reading"
android:key="reading">
<ListPreference
android:key="default_browser"
android:title="@string/default_browser"
android:dialogTitle="@string/default_browser"
android:entries="@array/default_browser_entries"
android:entryValues="@array/default_browser_values"
android:defaultValue="@string/default_default_browser" />
<ListPreference
android:key="reading_font"
android:title="@string/font"

View file

@ -5,32 +5,27 @@ import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.databinding.ActivityAddfeedexternalBinding;
import com.newsblur.fragment.AddFeedFragment;
import com.newsblur.util.UIUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.ProgressThrobber;
public class AddFeedExternal extends NbActivity implements AddFeedFragment.AddFeedProgressListener {
@Bind(R.id.loading_throb) ProgressThrobber progressView;
@Bind(R.id.progress_text) TextView progressText;
private ActivityAddfeedexternalBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityAddfeedexternalBinding.inflate(getLayoutInflater());
getActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.activity_addfeedexternal);
ButterKnife.bind(this);
setContentView(binding.getRoot());
progressView.setEnabled(!ViewUtils.isPowerSaveMode(this));
progressView.setColors(UIUtils.getColor(this, R.color.refresh_1),
binding.loadingThrob.setEnabled(!ViewUtils.isPowerSaveMode(this));
binding.loadingThrob.setColors(UIUtils.getColor(this, R.color.refresh_1),
UIUtils.getColor(this, R.color.refresh_2),
UIUtils.getColor(this, R.color.refresh_3),
UIUtils.getColor(this, R.color.refresh_4));
@ -48,9 +43,9 @@ public class AddFeedExternal extends NbActivity implements AddFeedFragment.AddFe
public void addFeedStarted() {
runOnUiThread(new Runnable() {
public void run() {
progressText.setText(R.string.adding_feed_progress);
progressText.setVisibility(View.VISIBLE);
progressView.setVisibility(View.VISIBLE);
binding.progressText.setText(R.string.adding_feed_progress);
binding.progressText.setVisibility(View.VISIBLE);
binding.loadingThrob.setVisibility(View.VISIBLE);
}
});
}

View file

@ -14,4 +14,9 @@ public class AllSharedStoriesItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, R.drawable.ak_icon_blurblogs, getResources().getString(R.string.all_shared_stories_title));
}
@Override
String getSaveSearchFeedId() {
// doesn't have save search option
return null;
}
}

View file

@ -24,4 +24,9 @@ public class AllStoriesItemsList extends ItemsList {
UIUtils.startReadingActivity(fs, hash, this);
}
}
@Override
String getSaveSearchFeedId() {
return "river:";
}
}

View file

@ -11,7 +11,7 @@ import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.fragment.DeleteFeedFragment;
import com.newsblur.fragment.FeedIntelTrainerFragment;
import com.newsblur.fragment.RenameFeedFragment;
import com.newsblur.fragment.RenameDialogFragment;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
@ -79,8 +79,8 @@ public class FeedItemsList extends ItemsList {
return true;
}
if (item.getItemId() == R.id.menu_rename_feed) {
RenameFeedFragment frag = RenameFeedFragment.newInstance(feed);
frag.show(getSupportFragmentManager(), RenameFeedFragment.class.getName());
RenameDialogFragment frag = RenameDialogFragment.newInstance(feed);
frag.show(getSupportFragmentManager(), RenameDialogFragment.class.getName());
return true;
// 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.
@ -118,4 +118,8 @@ public class FeedItemsList extends ItemsList {
return true;
}
@Override
String getSaveSearchFeedId() {
return "feed:" + feed.feedId;
}
}

View file

@ -19,4 +19,8 @@ public class FolderItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, R.drawable.g_icn_folder_rss, folderName);
}
@Override
String getSaveSearchFeedId() {
return "river:" + folderName;
}
}

View file

@ -14,4 +14,9 @@ public class GlobalSharedStoriesItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, R.drawable.ak_icon_global, getResources().getString(R.string.global_shared_stories_title));
}
@Override
String getSaveSearchFeedId() {
// doesn't have save search option
return null;
}
}

View file

@ -0,0 +1,65 @@
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 com.newsblur.databinding.ActivityInAppBrowserBinding;
import com.newsblur.util.PrefsUtils;
public class InAppBrowser extends FragmentActivity {
public static final String URI = "uri";
private ActivityInAppBrowserBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PrefsUtils.applyThemePreference(this);
binding = ActivityInAppBrowserBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
String url = getIntent().getParcelableExtra(URI).toString();
binding.webView.getSettings().setJavaScriptEnabled(true);
binding.webView.getSettings().setLoadWithOverviewMode(true);
binding.webView.getSettings().setSupportZoom(true);
binding.webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
binding.progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
binding.progressBar.setVisibility(View.GONE);
}
});
binding.webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
binding.progressBar.setProgress(newProgress);
}
});
binding.webView.loadUrl(url);
}
@Override
public void onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack();
} else {
finish();
}
}
}

View file

@ -37,4 +37,8 @@ public class InfrequentItemsList extends ItemsList implements InfrequentCutoffCh
restartReadingSession();
}
@Override
String getSaveSearchFeedId() {
return "river:infrequent";
}
}

View file

@ -10,17 +10,14 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.databinding.ActivityItemslistBinding;
import com.newsblur.fragment.ItemSetFragment;
import com.newsblur.fragment.ReadFilterDialogFragment;
import com.newsblur.fragment.SaveSearchFragment;
import com.newsblur.fragment.StoryOrderDialogFragment;
import com.newsblur.fragment.TextSizeDialogFragment;
import com.newsblur.service.NBSyncService;
@ -46,10 +43,9 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
private static final String READ_FILTER = "readFilter";
private static final String DEFAULT_FEED_VIEW = "defaultFeedView";
private static final String BUNDLE_ACTIVE_SEARCH_QUERY = "activeSearchQuery";
private ActivityItemslistBinding binding;
protected ItemSetFragment itemSetFragment;
@Bind(R.id.itemlist_sync_status) TextView overlayStatusText;
@Bind(R.id.itemlist_search_query) EditText searchQueryInput;
protected StateFilter intelState;
protected FeedSet fs;
@ -79,8 +75,8 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
setContentView(R.layout.activity_itemslist);
ButterKnife.bind(this);
binding = ActivityItemslistBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
FragmentManager fragmentManager = getSupportFragmentManager();
itemSetFragment = (ItemSetFragment) fragmentManager.findFragmentByTag(ItemSetFragment.class.getName());
@ -92,19 +88,23 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
transaction.commit();
}
String activeSearchQuery;
if (bundle != null) {
String activeSearchQuery = bundle.getString(BUNDLE_ACTIVE_SEARCH_QUERY);
activeSearchQuery = bundle.getString(BUNDLE_ACTIVE_SEARCH_QUERY);
} else {
activeSearchQuery = fs.getSearchQuery();
}
if (activeSearchQuery != null) {
searchQueryInput.setText(activeSearchQuery);
searchQueryInput.setVisibility(View.VISIBLE);
binding.itemlistSearchQuery.setText(activeSearchQuery);
binding.itemlistSearchQuery.setVisibility(View.VISIBLE);
fs.setSearchQuery(activeSearchQuery);
}
}
searchQueryInput.setOnKeyListener(new OnKeyListener() {
binding.itemlistSearchQuery.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
searchQueryInput.setVisibility(View.GONE);
searchQueryInput.setText("");
binding.itemlistSearchQuery.setVisibility(View.GONE);
binding.itemlistSearchQuery.setText("");
checkSearchQuery();
return true;
}
@ -120,8 +120,8 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (searchQueryInput != null) {
String q = searchQueryInput.getText().toString().trim();
if (binding.itemlistSearchQuery != null) {
String q = binding.itemlistSearchQuery.getText().toString().trim();
if (q.length() > 0) {
outState.putString(BUNDLE_ACTIVE_SEARCH_QUERY, q);
}
@ -222,6 +222,14 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
menu.findItem(R.id.menu_theme_dark).setChecked(true);
} else if (themeValue == ThemeValue.BLACK) {
menu.findItem(R.id.menu_theme_black).setChecked(true);
} else if (themeValue == ThemeValue.AUTO) {
menu.findItem(R.id.menu_theme_auto).setChecked(true);
}
if (!TextUtils.isEmpty(binding.itemlistSearchQuery.getText())) {
menu.findItem(R.id.menu_save_search).setVisible(true);
} else {
menu.findItem(R.id.menu_save_search).setVisible(false);
}
return true;
@ -250,13 +258,16 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
textSize.show(getSupportFragmentManager(), TextSizeDialogFragment.class.getName());
return true;
} else if (item.getItemId() == R.id.menu_search_stories) {
if (searchQueryInput.getVisibility() != View.VISIBLE) {
searchQueryInput.setVisibility(View.VISIBLE);
searchQueryInput.requestFocus();
if (binding.itemlistSearchQuery.getVisibility() != View.VISIBLE) {
binding.itemlistSearchQuery.setVisibility(View.VISIBLE);
binding.itemlistSearchQuery.requestFocus();
} else {
searchQueryInput.setVisibility(View.GONE);
binding.itemlistSearchQuery.setVisibility(View.GONE);
checkSearchQuery();
}
} else if(item.getItemId() == R.id.menu_theme_auto) {
PrefsUtils.setSelectedTheme(this, ThemeValue.AUTO);
UIUtils.restartActivity(this);
} else if (item.getItemId() == R.id.menu_theme_light) {
PrefsUtils.setSelectedTheme(this, ThemeValue.LIGHT);
UIUtils.restartActivity(this);
@ -279,6 +290,14 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.GRID_C);
itemSetFragment.updateStyle();
}
if (item.getItemId() == R.id.menu_save_search) {
String feedId = getSaveSearchFeedId();
if (feedId != null) {
String query = binding.itemlistSearchQuery.getText().toString();
SaveSearchFragment frag = SaveSearchFragment.newInstance(feedId, query);
frag.show(getSupportFragmentManager(), SaveSearchFragment.class.getName());
}
}
return false;
}
@ -315,23 +334,23 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
}
private void updateStatusIndicators() {
if (overlayStatusText != null) {
if (binding.itemlistSyncStatus != null) {
String syncStatus = NBSyncService.getSyncStatusMessage(this, true);
if (syncStatus != null) {
if (AppConstants.VERBOSE_LOG) {
syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this);
}
overlayStatusText.setText(syncStatus);
overlayStatusText.setVisibility(View.VISIBLE);
binding.itemlistSyncStatus.setText(syncStatus);
binding.itemlistSyncStatus.setVisibility(View.VISIBLE);
} else {
overlayStatusText.setVisibility(View.GONE);
binding.itemlistSyncStatus.setVisibility(View.GONE);
}
}
}
private void checkSearchQuery() {
String oldQuery = fs.getSearchQuery();
String q = searchQueryInput.getText().toString().trim();
String q = binding.itemlistSearchQuery.getText().toString().trim();
if (q.length() < 1) {
q = null;
}
@ -395,4 +414,6 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
*/
overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
}
abstract String getSaveSearchFeedId();
}

View file

@ -17,19 +17,12 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.databinding.ActivityMainBinding;
import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
import com.newsblur.fragment.FolderListFragment;
import com.newsblur.fragment.LoginAsDialogFragment;
@ -54,15 +47,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
private FragmentManager fragmentManager;
private SwipeRefreshLayout swipeLayout;
private boolean wasSwipeEnabled = false;
@Bind(R.id.main_sync_status) TextView overlayStatusText;
@Bind(R.id.empty_view_image) ImageView emptyViewImage;
@Bind(R.id.empty_view_text) TextView emptyViewText;
@Bind(R.id.main_menu_button) Button menuButton;
@Bind(R.id.main_user_image) ImageView userImage;
@Bind(R.id.main_user_name) TextView userName;
@Bind(R.id.main_unread_count_neut_text) TextView unreadCountNeutText;
@Bind(R.id.main_unread_count_posi_text) TextView unreadCountPosiText;
@Bind(R.id.feedlist_search_query) EditText searchQueryInput;
private ActivityMainBinding binding;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -70,16 +55,15 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
super.onCreate(savedInstanceState);
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getActionBar().hide();
// set the status bar to an generic loading message when the activity is first created so
// that something is displayed while the service warms up
overlayStatusText.setText(R.string.loading);
overlayStatusText.setVisibility(View.VISIBLE);
binding.mainSyncStatus.setText(R.string.loading);
binding.mainSyncStatus.setVisibility(View.VISIBLE);
swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_container);
swipeLayout.setColorSchemeResources(R.color.refresh_1, R.color.refresh_2, R.color.refresh_3, R.color.refresh_4);
@ -88,7 +72,6 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
fragmentManager = getSupportFragmentManager();
folderFeedList = (FolderListFragment) fragmentManager.findFragmentByTag("folderFeedListFragment");
folderFeedList.setRetainInstance(true);
((FeedIntelligenceSelectorFragment) fragmentManager.findFragmentByTag("feedIntelligenceSelector")).setState(folderFeedList.currentState);
// make sure the interval sync is scheduled, since we are the root Activity
@ -97,14 +80,14 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
Bitmap userPicture = PrefsUtils.getUserImage(this);
if (userPicture != null) {
userPicture = UIUtils.clipAndRound(userPicture, 5, false);
userImage.setImageBitmap(userPicture);
binding.mainUserImage.setImageBitmap(userPicture);
}
userName.setText(PrefsUtils.getUserDetails(this).username);
searchQueryInput.setOnKeyListener(new OnKeyListener() {
binding.mainUserName.setText(PrefsUtils.getUserDetails(this).username);
binding.feedlistSearchQuery.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
searchQueryInput.setVisibility(View.GONE);
searchQueryInput.setText("");
binding.feedlistSearchQuery.setVisibility(View.GONE);
binding.feedlistSearchQuery.setText("");
checkSearchQuery();
return true;
}
@ -115,7 +98,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
return false;
}
});
searchQueryInput.addTextChangedListener(new TextWatcher() {
binding.feedlistSearchQuery.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
checkSearchQuery();
}
@ -124,6 +107,31 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
});
FeedUtils.currentFolderName = null;
binding.mainMenuButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickMenuButton();
}
});
binding.mainAddButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickAddButton();
}
});
binding.mainProfileButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickProfileButton();
}
});
binding.mainUserImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickUserButton();
}
});
}
@Override
@ -149,8 +157,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
}
if (folderFeedList.getSearchQuery() != null) {
searchQueryInput.setText(folderFeedList.getSearchQuery());
searchQueryInput.setVisibility(View.VISIBLE);
binding.feedlistSearchQuery.setText(folderFeedList.getSearchQuery());
binding.feedlistSearchQuery.setVisibility(View.VISIBLE);
}
// triggerSync() might not actually do enough to push a UI update if background sync has been
@ -172,8 +180,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
if ( !( (state == StateFilter.ALL) ||
(state == StateFilter.SOME) ||
(state == StateFilter.BEST) ) ) {
searchQueryInput.setText("");
searchQueryInput.setVisibility(View.GONE);
binding.feedlistSearchQuery.setText("");
binding.feedlistSearchQuery.setVisibility(View.GONE);
checkSearchQuery();
}
@ -201,8 +209,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
}
public void updateUnreadCounts(int neutCount, int posiCount) {
unreadCountNeutText.setText(Integer.toString(neutCount));
unreadCountPosiText.setText(Integer.toString(posiCount));
binding.mainUnreadCountNeutText.setText(Integer.toString(neutCount));
binding.mainUnreadCountPosiText.setText(Integer.toString(posiCount));
}
/**
@ -213,22 +221,22 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
public void updateFeedCount(int feedCount) {
if (feedCount < 1 ) {
if (NBSyncService.isFeedCountSyncRunning() || (!folderFeedList.firstCursorSeenYet)) {
emptyViewImage.setVisibility(View.INVISIBLE);
emptyViewText.setVisibility(View.INVISIBLE);
binding.emptyViewImage.setVisibility(View.INVISIBLE);
binding.emptyViewText.setVisibility(View.INVISIBLE);
} else {
emptyViewImage.setVisibility(View.VISIBLE);
binding.emptyViewImage.setVisibility(View.VISIBLE);
if (folderFeedList.currentState == StateFilter.BEST) {
emptyViewText.setText(R.string.empty_list_view_no_focus_stories);
binding.emptyViewText.setText(R.string.empty_list_view_no_focus_stories);
} else if (folderFeedList.currentState == StateFilter.SAVED) {
emptyViewText.setText(R.string.empty_list_view_no_saved_stories);
binding.emptyViewText.setText(R.string.empty_list_view_no_saved_stories);
} else {
emptyViewText.setText(R.string.empty_list_view_no_unread_stories);
binding.emptyViewText.setText(R.string.empty_list_view_no_unread_stories);
}
emptyViewText.setVisibility(View.VISIBLE);
binding.emptyViewText.setVisibility(View.VISIBLE);
}
} else {
emptyViewImage.setVisibility(View.INVISIBLE);
emptyViewText.setVisibility(View.INVISIBLE);
binding.emptyViewImage.setVisibility(View.INVISIBLE);
binding.emptyViewText.setVisibility(View.INVISIBLE);
}
}
@ -239,16 +247,16 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
swipeLayout.setRefreshing(false);
}
if (overlayStatusText != null) {
if (binding.mainSyncStatus != null) {
String syncStatus = NBSyncService.getSyncStatusMessage(this, false);
if (syncStatus != null) {
if (AppConstants.VERBOSE_LOG) {
syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this);
}
overlayStatusText.setText(syncStatus);
overlayStatusText.setVisibility(View.VISIBLE);
binding.mainSyncStatus.setText(syncStatus);
binding.mainSyncStatus.setVisibility(View.VISIBLE);
} else {
overlayStatusText.setVisibility(View.GONE);
binding.mainSyncStatus.setVisibility(View.GONE);
}
}
}
@ -260,8 +268,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
folderFeedList.clearRecents();
}
@OnClick(R.id.main_menu_button) void onClickMenuButton() {
PopupMenu pm = new PopupMenu(this, menuButton);
private void onClickMenuButton() {
PopupMenu pm = new PopupMenu(this, binding.mainMenuButton);
Menu menu = pm.getMenu();
pm.getMenuInflater().inflate(R.menu.main, menu);
@ -294,6 +302,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
menu.findItem(R.id.menu_theme_dark).setChecked(true);
} else if (themeValue == ThemeValue.BLACK) {
menu.findItem(R.id.menu_theme_black).setChecked(true);
} else if (themeValue == ThemeValue.AUTO) {
menu.findItem(R.id.menu_theme_auto).setChecked(true);
}
menu.findItem(R.id.menu_widget).setVisible(WidgetUtils.hasActiveAppWidgets(this));
@ -308,12 +318,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
onRefresh();
return true;
} else if (item.getItemId() == R.id.menu_search_feeds) {
if (searchQueryInput.getVisibility() != View.VISIBLE) {
searchQueryInput.setVisibility(View.VISIBLE);
searchQueryInput.requestFocus();
if (binding.feedlistSearchQuery.getVisibility() != View.VISIBLE) {
binding.feedlistSearchQuery.setVisibility(View.VISIBLE);
binding.feedlistSearchQuery.requestFocus();
} else {
searchQueryInput.setText("");
searchQueryInput.setVisibility(View.GONE);
binding.feedlistSearchQuery.setText("");
binding.feedlistSearchQuery.setVisibility(View.GONE);
checkSearchQuery();
}
} else if (item.getItemId() == R.id.menu_add_feed) {
@ -351,6 +361,9 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
DialogFragment newFragment = new LoginAsDialogFragment();
newFragment.show(getSupportFragmentManager(), "dialog");
return true;
} else if (item.getItemId() == R.id.menu_theme_auto) {
PrefsUtils.setSelectedTheme(this, ThemeValue.AUTO);
UIUtils.restartActivity(this);
} else if (item.getItemId() == R.id.menu_theme_light) {
PrefsUtils.setSelectedTheme(this, ThemeValue.LIGHT);
UIUtils.restartActivity(this);
@ -364,17 +377,17 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
return false;
}
@OnClick(R.id.main_add_button) void onClickAddButton() {
private void onClickAddButton() {
Intent i = new Intent(this, SearchForFeeds.class);
startActivity(i);
}
@OnClick(R.id.main_profile_button) void onClickProfileButton() {
private void onClickProfileButton() {
Intent i = new Intent(this, Profile.class);
startActivity(i);
}
@OnClick(R.id.main_user_image) void onClickUserButton() {
private void onClickUserButton() {
Intent i = new Intent(this, Profile.class);
startActivity(i);
}
@ -404,7 +417,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
}
private void checkSearchQuery() {
String q = searchQueryInput.getText().toString().trim();
String q = binding.feedlistSearchQuery.getText().toString().trim();
if (q.length() < 1) {
q = null;
}

View file

@ -14,4 +14,8 @@ public class ReadStoriesItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, R.drawable.g_icn_unread_double, getResources().getString(R.string.read_stories_title));
}
@Override
String getSaveSearchFeedId() {
return "read";
}
}

View file

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
@ -19,18 +20,14 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.database.ReadingAdapter;
import com.newsblur.databinding.ActivityReadingBinding;
import com.newsblur.domain.Story;
import com.newsblur.fragment.ReadingItemFragment;
import com.newsblur.fragment.ReadingPagerFragment;
@ -77,19 +74,9 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
protected final Object STORIES_MUTEX = new Object();
protected Cursor stories;
@Bind(android.R.id.content) View contentView; // we use this a ton, so cache it
@Bind(R.id.reading_overlay_left) Button overlayLeft;
@Bind(R.id.reading_overlay_right) Button overlayRight;
@Bind(R.id.reading_overlay_progress) ProgressBar overlayProgress;
@Bind(R.id.reading_overlay_progress_right) ProgressBar overlayProgressRight;
@Bind(R.id.reading_overlay_progress_left) ProgressBar overlayProgressLeft;
@Bind(R.id.reading_overlay_text) Button overlayText;
@Bind(R.id.reading_overlay_send) Button overlaySend;
@Bind(R.id.reading_empty_view_text) View emptyViewText;
@Bind(R.id.reading_sync_status) TextView overlayStatusText;
private View contentView; // we use this a ton, so cache it
ViewPager pager;
ReadingPagerFragment readingFragment;
protected ReadingAdapter readingAdapter;
private boolean stopLoading;
@ -109,6 +96,8 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
private VolumeKeyNavigation volumeKeyNavigation;
private ActivityReadingBinding binding;
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@ -119,8 +108,9 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setContentView(R.layout.activity_reading);
ButterKnife.bind(this);
binding = ActivityReadingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
contentView = findViewById(android.R.id.content);
try {
fs = (FeedSet)getIntent().getSerializableExtra(EXTRA_FEEDSET);
@ -167,17 +157,17 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
this.pageHistory = new ArrayList<Story>();
ViewUtils.setViewElevation(overlayLeft, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlayRight, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlayText, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlaySend, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlayProgress, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlayProgressLeft, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(overlayProgressRight, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayLeft, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayRight, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayText, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlaySend, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayProgress, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayProgressLeft, OVERLAY_ELEVATION_DP);
ViewUtils.setViewElevation(binding.readingOverlayProgressRight, OVERLAY_ELEVATION_DP);
// this likes to default to 'on' for some platforms
enableProgressCircle(overlayProgressLeft, false);
enableProgressCircle(overlayProgressRight, false);
enableProgressCircle(binding.readingOverlayProgressLeft, false);
enableProgressCircle(binding.readingOverlayProgressRight, false);
FragmentManager fragmentManager = getSupportFragmentManager();
ReadingPagerFragment fragment = (ReadingPagerFragment) fragmentManager.findFragmentByTag(ReadingPagerFragment.class.getName());
@ -259,6 +249,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
// the system can and will re-use activities, so during the initial mismatch of
// data, don't show the old stories
pager.setVisibility(View.INVISIBLE);
binding.readingEmptyViewText.setVisibility(View.VISIBLE);
stories = null;
triggerRefresh(AppConstants.READING_STORY_PRELOAD);
return;
@ -297,7 +288,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
private void skipPagerToStoryHash() {
// if we already started and found our target story, this will be unset
if (storyHash == null) return;
if (storyHash == null) {
pager.setVisibility(View.VISIBLE);
binding.readingEmptyViewText.setVisibility(View.INVISIBLE);
return;
}
int position = -1;
if (storyHash.equals(FIND_FIRST_UNREAD)) {
position = readingAdapter.findFirstUnread();
@ -312,7 +307,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
this.onPageSelected(position);
// now that the pager is getting the right story, make it visible
pager.setVisibility(View.VISIBLE);
emptyViewText.setVisibility(View.INVISIBLE);
binding.readingEmptyViewText.setVisibility(View.INVISIBLE);
storyHash = null;
return;
}
@ -340,6 +335,15 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
pager.setPageMarginDrawable(R.drawable.divider_light);
} else if (themeValue == ThemeValue.DARK) {
pager.setPageMarginDrawable(R.drawable.divider_dark);
} else if (themeValue == ThemeValue.AUTO) {
int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
pager.setPageMarginDrawable(R.drawable.divider_dark);
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_NO) {
pager.setPageMarginDrawable(R.drawable.divider_light);
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_UNDEFINED) {
pager.setPageMarginDrawable(R.drawable.divider_light);
}
}
boolean showFeedMetadata = true;
@ -405,16 +409,16 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
}
if ((updateType & UPDATE_STATUS) != 0) {
enableMainProgress(NBSyncService.isFeedSetSyncing(this.fs, this));
if (overlayStatusText != null) {
if (binding.readingSyncStatus != null) {
String syncStatus = NBSyncService.getSyncStatusMessage(this, true);
if (syncStatus != null) {
if (AppConstants.VERBOSE_LOG) {
syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this);
}
overlayStatusText.setText(syncStatus);
overlayStatusText.setVisibility(View.VISIBLE);
binding.readingSyncStatus.setText(syncStatus);
binding.readingSyncStatus.setVisibility(View.VISIBLE);
} else {
overlayStatusText.setVisibility(View.GONE);
binding.readingSyncStatus.setVisibility(View.GONE);
}
}
}
@ -516,11 +520,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
final boolean _overflowExtras = overflowExtras;
runOnUiThread(new Runnable() {
public void run() {
UIUtils.setViewAlpha(overlayLeft, a, true);
UIUtils.setViewAlpha(overlayRight, a, true);
UIUtils.setViewAlpha(overlayProgress, a, true);
UIUtils.setViewAlpha(overlayText, a, true);
UIUtils.setViewAlpha(overlaySend, a, !_overflowExtras);
UIUtils.setViewAlpha(binding.readingOverlayLeft, a, true);
UIUtils.setViewAlpha(binding.readingOverlayRight, a, true);
UIUtils.setViewAlpha(binding.readingOverlayProgress, a, true);
UIUtils.setViewAlpha(binding.readingOverlayText, a, true);
UIUtils.setViewAlpha(binding.readingOverlaySend, a, !_overflowExtras);
}
});
}
@ -544,40 +548,40 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
if (currentUnreadCount > this.startingUnreadCount ) {
this.startingUnreadCount = currentUnreadCount;
}
this.overlayLeft.setEnabled(this.getLastReadPosition(false) != -1);
this.overlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done);
this.binding.readingOverlayLeft.setEnabled(this.getLastReadPosition(false) != -1);
this.binding.readingOverlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done);
if (currentUnreadCount > 0) {
this.overlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRight, android.R.attr.background));
this.binding.readingOverlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRight, android.R.attr.background));
} else {
this.overlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRightDone, android.R.attr.background));
this.binding.readingOverlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRightDone, android.R.attr.background));
}
if (this.startingUnreadCount == 0 ) {
// sessions with no unreads just show a full progress bar
this.overlayProgress.setMax(1);
this.overlayProgress.setProgress(1);
this.binding.readingOverlayProgress.setMax(1);
this.binding.readingOverlayProgress.setProgress(1);
} else {
int unreadProgress = this.startingUnreadCount - currentUnreadCount;
this.overlayProgress.setMax(this.startingUnreadCount);
this.overlayProgress.setProgress(unreadProgress);
this.binding.readingOverlayProgress.setMax(this.startingUnreadCount);
this.binding.readingOverlayProgress.setProgress(unreadProgress);
}
this.overlayProgress.invalidate();
this.binding.readingOverlayProgress.invalidate();
invalidateOptionsMenu();
}
private void updateOverlayText() {
if (overlayText == null) return;
if (binding.readingOverlayText == null) return;
runOnUiThread(new Runnable() {
public void run() {
ReadingItemFragment item = getReadingFragment();
if (item == null) return;
if (item.getSelectedViewMode() == DefaultFeedView.STORY) {
overlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundText, android.R.attr.background));
overlayText.setText(R.string.overlay_text);
binding.readingOverlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundText, android.R.attr.background));
binding.readingOverlayText.setText(R.string.overlay_text);
} else {
overlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundStory, android.R.attr.background));
overlayText.setText(R.string.overlay_story);
binding.readingOverlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundStory, android.R.attr.background));
binding.readingOverlayText.setText(R.string.overlay_story);
}
}
});
@ -610,11 +614,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
}
protected void enableMainProgress(boolean enabled) {
enableProgressCircle(overlayProgressRight, enabled);
enableProgressCircle(binding.readingOverlayProgressRight, enabled);
}
public void enableLeftProgressCircle(boolean enabled) {
enableProgressCircle(overlayProgressLeft, enabled);
enableProgressCircle(binding.readingOverlayProgressLeft, enabled);
}
private void enableProgressCircle(final ProgressBar view, final boolean enabled) {

View file

@ -18,4 +18,13 @@ public class SavedStoriesItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, R.drawable.clock, title);
}
@Override
String getSaveSearchFeedId() {
String feedId = "starred";
String savedTag = fs.getSingleSavedTag();
if (savedTag != null) {
feedId += ":" + savedTag;
}
return feedId;
}
}

View file

@ -19,4 +19,8 @@ public class SocialFeedItemsList extends ItemsList {
UIUtils.setCustomActionBar(this, socialFeed.photoUrl, socialFeed.feedTitle);
}
@Override
String getSaveSearchFeedId() {
return "social:" + socialFeed.userId;
}
}

View file

@ -9,10 +9,9 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.databinding.ActivityWidgetConfigBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedOrderFilter;
@ -31,28 +30,21 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import butterknife.Bind;
import butterknife.ButterKnife;
public class WidgetConfig extends NbActivity {
@Bind(R.id.list_view)
ExpandableListView listView;
@Bind(R.id.text_no_subscriptions)
TextView textNoSubscriptions;
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);
setContentView(R.layout.activity_widget_config);
ButterKnife.bind(this);
binding = ActivityWidgetConfigBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getActionBar().setDisplayHomeAsUpEnabled(true);
setupList();
loadFeeds();
@ -164,7 +156,7 @@ public class WidgetConfig extends NbActivity {
private void setupList() {
adapter = new WidgetConfigAdapter(this);
listView.setAdapter(adapter);
binding.listView.setAdapter(adapter);
}
private void loadFeeds() {
@ -288,7 +280,7 @@ public class WidgetConfig extends NbActivity {
private void setAdapterData() {
adapter.setData(this.folderNames, this.folderChildren, this.feeds);
listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE);
textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE);
binding.listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE);
binding.textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE);
}
}

View file

@ -27,6 +27,7 @@ public class BlurDatabase extends SQLiteOpenHelper {
db.execSQL(DatabaseConstants.CLASSIFIER_SQL);
db.execSQL(DatabaseConstants.SOCIALFEED_STORIES_SQL);
db.execSQL(DatabaseConstants.STARREDCOUNTS_SQL);
db.execSQL(DatabaseConstants.SAVED_SEARCH_SQL);
db.execSQL(DatabaseConstants.ACTION_SQL);
db.execSQL(DatabaseConstants.NOTIFY_DISMISS_SQL);
db.execSQL(DatabaseConstants.FEED_TAGS_SQL);
@ -49,6 +50,7 @@ public class BlurDatabase extends SQLiteOpenHelper {
db.execSQL(drop + DatabaseConstants.CLASSIFIER_TABLE);
db.execSQL(drop + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE);
db.execSQL(drop + DatabaseConstants.STARREDCOUNTS_TABLE);
db.execSQL(drop + DatabaseConstants.SAVED_SEARCH_TABLE);
db.execSQL(drop + DatabaseConstants.ACTION_TABLE);
db.execSQL(drop + DatabaseConstants.NOTIFY_DISMISS_TABLE);
db.execSQL(drop + DatabaseConstants.FEED_TAGS_TABLE);

View file

@ -195,6 +195,13 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE, DatabaseConstants.SOCIALFEED_STORY_USER_ID + " = ?", selArgs);}
}
public void deleteSavedSearch(String feedId, String query) {
String q = "DELETE FROM " + DatabaseConstants.SAVED_SEARCH_TABLE +
" WHERE " + DatabaseConstants.SAVED_SEARCH_FEED_ID + " = '" + feedId + "'" +
" AND " + DatabaseConstants.SAVED_SEARCH_QUERY + " = '" + query + "'";
synchronized (RW_MUTEX) {dbRW.execSQL(q);}
}
public Feed getFeed(String feedId) {
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, DatabaseConstants.FEED_ID + " = ?", new String[] {feedId}, null, null, null);
Feed result = null;
@ -237,7 +244,8 @@ public class BlurDatabaseHelper {
public void setFeedsFolders(List<ContentValues> folderValues,
List<ContentValues> feedValues,
List<ContentValues> socialFeedValues,
List<ContentValues> starredCountValues) {
List<ContentValues> starredCountValues,
List<ContentValues> savedSearchValues) {
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
@ -248,10 +256,12 @@ public class BlurDatabaseHelper {
dbRW.delete(DatabaseConstants.COMMENT_TABLE, null, null);
dbRW.delete(DatabaseConstants.REPLY_TABLE, null, null);
dbRW.delete(DatabaseConstants.STARREDCOUNTS_TABLE, null, null);
dbRW.delete(DatabaseConstants.SAVED_SEARCH_TABLE, null, null);
bulkInsertValuesExtSync(DatabaseConstants.FOLDER_TABLE, folderValues);
bulkInsertValuesExtSync(DatabaseConstants.FEED_TABLE, feedValues);
bulkInsertValuesExtSync(DatabaseConstants.SOCIALFEED_TABLE, socialFeedValues);
bulkInsertValuesExtSync(DatabaseConstants.STARREDCOUNTS_TABLE, starredCountValues);
bulkInsertValuesExtSync(DatabaseConstants.SAVED_SEARCH_TABLE, savedSearchValues);
dbRW.setTransactionSuccessful();
} finally {
dbRW.endTransaction();
@ -1033,6 +1043,17 @@ public class BlurDatabaseHelper {
return result;
}
@Nullable
public StarredCount getStarredFeedByTag(String tag) {
Cursor c = dbRO.query(DatabaseConstants.STARREDCOUNTS_TABLE, null, DatabaseConstants.STARREDCOUNTS_TAG + " = ?", new String[] {tag}, null, null, null);
StarredCount result = null;
while (c.moveToNext()) {
result = StarredCount.fromCursor(c);
}
c.close();
return result;
}
public List<Folder> getFolders() {
Cursor c = getFoldersCursor(null);
List<Folder> folders = new ArrayList<Folder>(c.getCount());
@ -1069,11 +1090,21 @@ public class BlurDatabaseHelper {
};
}
public Loader<Cursor> getSavedSearchLoader() {
return new QueryCursorLoader(context) {
protected Cursor createCursor() {return getSavedSearchCursor(cancellationSignal);}
};
}
private Cursor getSavedStoryCountsCursor(CancellationSignal cancellationSignal) {
Cursor c = query(false, DatabaseConstants.STARREDCOUNTS_TABLE, null, null, null, null, null, null, null, cancellationSignal);
return c;
}
private Cursor getSavedSearchCursor(CancellationSignal cancellationSignal) {
return query(false, DatabaseConstants.SAVED_SEARCH_TABLE, null, null, null, null, null, null, null, cancellationSignal);
}
public Cursor getNotifyFocusStoriesCursor() {
return rawQuery(DatabaseConstants.NOTIFY_FOCUS_STORY_QUERY, null, null);
}

View file

@ -148,6 +148,13 @@ public class DatabaseConstants {
public static final String STARREDCOUNTS_TAG = "tag";
public static final String STARREDCOUNTS_FEEDID = "feed_id";
public static final String SAVED_SEARCH_TABLE = "saved_search";
public static final String SAVED_SEARCH_FEED_TITLE = "saved_search_title";
public static final String SAVED_SEARCH_FAVICON = "saved_search_favicon";
public static final String SAVED_SEARCH_ADDRESS = "saved_search_address";
public static final String SAVED_SEARCH_QUERY = "saved_search_query";
public static final String SAVED_SEARCH_FEED_ID = "saved_search_feed_id";
public static final String NOTIFY_DISMISS_TABLE = "notify_dimiss";
public static final String NOTIFY_DISMISS_STORY_HASH = "story_hash";
public static final String NOTIFY_DISMISS_TIME = "time";
@ -303,6 +310,14 @@ public class DatabaseConstants {
STARREDCOUNTS_FEEDID + TEXT +
")";
static final String SAVED_SEARCH_SQL = "CREATE TABLE " + SAVED_SEARCH_TABLE + " (" +
SAVED_SEARCH_FEED_TITLE + TEXT + ", " +
SAVED_SEARCH_FAVICON + TEXT + ", " +
SAVED_SEARCH_ADDRESS + TEXT + ", " +
SAVED_SEARCH_QUERY + TEXT + ", " +
SAVED_SEARCH_FEED_ID +
")";
static final String NOTIFY_DISMISS_SQL = "CREATE TABLE " + NOTIFY_DISMISS_TABLE + " (" +
NOTIFY_DISMISS_STORY_HASH + TEXT + ", " +
NOTIFY_DISMISS_TIME + INTEGER + " NOT NULL " +

View file

@ -27,6 +27,7 @@ import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.StarredCount;
import com.newsblur.domain.SocialFeed;
import com.newsblur.util.AppConstants;
@ -34,14 +35,15 @@ import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
/**
* Custom adapter to display a nested folder/feed list in an ExpandableListView.
*/
public class FolderListAdapter extends BaseExpandableListAdapter {
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_STORIES }
private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG }
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_SEARCHES, SAVED_STORIES }
private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG, SAVED_SEARCH }
// The following keys are used to mark the position of the special meta-folders within
// the folders array. Since the ExpandableListView doesn't handle collapsing of views
@ -56,6 +58,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
private static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY";
private static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY";
private static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY";
private static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY";
private final static float defaultTextSize_childName = 14;
private final static float defaultTextSize_groupName = 13;
@ -101,6 +104,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
/** Starred story sets in display order. */
private List<StarredCount> starredCountsByTag = Collections.emptyList();
/** Saved Searches */
private List<SavedSearch> savedSearches = Collections.emptyList();
private int savedStoriesTotalCount;
@ -162,6 +167,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
if (v == null) v = inflater.inflate(R.layout.row_infrequent_stories, null, false);
} else if (isRowReadStories(groupPosition)) {
if (v == null) v = inflater.inflate(R.layout.row_read_stories, null, false);
} else if (isRowSavedSearches(groupPosition)) {
if (v == null) v = inflater.inflate(R.layout.row_saved_searches, null, false);
} else if (isRowSavedStories(groupPosition)) {
if (v == null) v = inflater.inflate(R.layout.row_saved_stories, null, false);
TextView savedSum = ((TextView) v.findViewById(R.id.row_foldersum));
@ -268,6 +275,14 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
TextView savedCounter =((TextView) v.findViewById(R.id.row_saved_tag_sum));
savedCounter.setText(Integer.toString(checkNegativeUnreads(sc.count)));
savedCounter.setTextSize(textSize * defaultTextSize_count);
} else if (isRowSavedSearches(groupPosition)) {
if (v == null) v = inflater.inflate(R.layout.row_saved_search_child, parent, false);
SavedSearch ss = savedSearches.get(childPosition);
TextView nameView = v.findViewById(R.id.row_saved_search_title);
nameView.setText(UIUtils.fromHtml(ss.feedTitle));
ImageView iconView = v.findViewById(R.id.row_saved_search_icon);
FeedUtils.iconLoader.preCheck(ss.faviconUrl, iconView);
FeedUtils.iconLoader.displayImage(ss.faviconUrl, iconView, 0 , false);
} else {
if (v == null) v = inflater.inflate(R.layout.row_feed, parent, false);
Feed f = activeFolderChildren.get(groupPosition).get(childPosition);
@ -392,6 +407,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return folder.name;
}
public Folder getGroupFolder(int groupPosition) {
String flatFolderName = activeFolderNames.get(groupPosition);
return flatFolders.get(flatFolderName);
}
@Override
public synchronized int getGroupCount() {
if (activeFolderNames == null) return 0;
@ -409,6 +429,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return socialFeedsActive.size();
} else if (isRowSavedStories(groupPosition)) {
return starredCountsByTag.size();
} else if (isRowSavedSearches(groupPosition)) {
return savedSearches.size();
} else {
return activeFolderChildren.get(groupPosition).size();
}
@ -421,6 +443,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return FeedSet.singleSocialFeed(socialFeed.userId, socialFeed.username);
} else if (isRowSavedStories(groupPosition)) {
return FeedSet.singleSavedTag(starredCountsByTag.get(childPosition).tag);
} else if (isRowSavedSearches(groupPosition)) {
SavedSearch savedSearch = savedSearches.get(childPosition);
return FeedSet.singleSavedSearch(savedSearch.feedId, savedSearch.query);
} else {
Feed feed = activeFolderChildren.get(groupPosition).get(childPosition);
FeedSet fs = FeedSet.singleFeed(feed.feedId);
@ -465,6 +490,10 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return SAVED_STORIES_GROUP_KEY.equals(activeFolderNames.get(groupPosition));
}
public boolean isRowSavedSearches(int groupPosition) {
return SAVED_SEARCHES_GROUP_KEY.equals(activeFolderNames.get(groupPosition));
}
/**
* Determines if the row at the specified position is last of the special rows, under which
* un-foldered "root level" feeds are created as children. These feeds are not in any folder,
@ -565,6 +594,17 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
notifyDataSetChanged();
}
public synchronized void setSavedSearchesCursor(Cursor cursor) {
if (!cursor.isBeforeFirst()) return;
savedSearches = new ArrayList<>();
while (cursor.moveToNext()) {
SavedSearch savedSearch = SavedSearch.fromCursor(cursor);
savedSearches.add(savedSearch);
}
Collections.sort(savedSearches, SavedSearch.SavedSearchComparatorByTitle);
notifyDataSetChanged();
}
private void recountFeeds() {
if ((folders == null) || (feeds == null)) return;
// re-init our local vars
@ -619,6 +659,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
// add the always-present (if enabled) special rows/folders that got at the bottom of the list
addSpecialRow(READ_STORIES_GROUP_KEY);
addSpecialRow(SAVED_SEARCHES_GROUP_KEY);
addSpecialRow(SAVED_STORIES_GROUP_KEY);
recountChildren();
}
@ -728,6 +769,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
totalNeutCount = 0;
totalPosCount = 0;
safeClear(savedSearches);
safeClear(starredCountsByTag);
safeClear(closedFolders);
@ -755,6 +797,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return socialFeedsActive.get(childPosition);
}
/** Get the cached SavedSearch object at the given saved search list location. */
public SavedSearch getSavedSearch(int childPosition) {
return savedSearches.get(childPosition);
}
public synchronized void changeState(StateFilter state) {
currentState = state;
lastFeedViewedId = null; // clear when changing modes
@ -804,6 +851,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return GroupType.INFREQUENT_STORIES.ordinal();
} else if (isRowReadStories(groupPosition)) {
return GroupType.READ_STORIES.ordinal();
} else if (isRowSavedSearches(groupPosition)) {
return GroupType.SAVED_SEARCHES.ordinal();
} else if (isRowSavedStories(groupPosition)) {
return GroupType.SAVED_STORIES.ordinal();
} else {
@ -817,6 +866,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return ChildType.SOCIAL_FEED.ordinal();
} else if (isRowSavedStories(groupPosition)) {
return ChildType.SAVED_BY_TAG.ordinal();
} else if (isRowSavedSearches(groupPosition)) {
return ChildType.SAVED_SEARCH.ordinal();
} else {
return ChildType.FEED.ordinal();
}

View file

@ -1,6 +1,5 @@
package com.newsblur.database;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Typeface;
@ -22,18 +21,13 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.newsblur.R;
import com.newsblur.activity.FeedItemsList;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
@ -313,16 +307,16 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
MenuItem.OnMenuItemClickListener,
View.OnTouchListener {
@Bind(R.id.story_item_favicon_borderbar_1) View leftBarOne;
@Bind(R.id.story_item_favicon_borderbar_2) View leftBarTwo;
@Bind(R.id.story_item_inteldot) ImageView intelDot;
@Bind(R.id.story_item_thumbnail) ImageView thumbView;
@Bind(R.id.story_item_feedicon) ImageView feedIconView;
@Bind(R.id.story_item_feedtitle) TextView feedTitleView;
@Bind(R.id.story_item_title) TextView storyTitleView;
@Bind(R.id.story_item_date) TextView storyDate;
@Bind(R.id.story_item_saved_icon) View savedView;
@Bind(R.id.story_item_shared_icon) View sharedView;
View leftBarOne;
View leftBarTwo;
ImageView intelDot;
ImageView thumbView;
ImageView feedIconView;
TextView feedTitleView;
TextView storyTitleView;
TextView storyDate;
View savedView;
View sharedView;
Story story;
ImageLoader.PhotoToLoad thumbLoader;
@ -332,9 +326,20 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
boolean gestureL2R = false;
boolean gestureDebounce = false;
public StoryViewHolder(View view) {
super(view);
ButterKnife.bind(StoryViewHolder.this, view);
leftBarOne = view.findViewById(R.id.story_item_favicon_borderbar_1);
leftBarTwo = view.findViewById(R.id.story_item_favicon_borderbar_2);
intelDot = view.findViewById(R.id.story_item_inteldot);
thumbView = view.findViewById(R.id.story_item_thumbnail);
feedIconView = view.findViewById(R.id.story_item_feedicon);
feedTitleView = view.findViewById(R.id.story_item_feedtitle);
storyTitleView = view.findViewById(R.id.story_item_title);
storyDate = view.findViewById(R.id.story_item_date);
savedView = view.findViewById(R.id.story_item_saved_icon);
sharedView = view.findViewById(R.id.story_item_shared_icon);
view.setOnClickListener(StoryViewHolder.this);
view.setOnCreateContextMenuListener(StoryViewHolder.this);
view.setOnTouchListener(StoryViewHolder.this);
@ -472,10 +477,12 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
public class StoryRowViewHolder extends StoryViewHolder {
@Bind(R.id.story_item_author) TextView storyAuthor;
@Bind(R.id.story_item_content) TextView storySnippet;
TextView storyAuthor;
TextView storySnippet;
public StoryRowViewHolder(View view) {
super(view);
storyAuthor = view.findViewById(R.id.story_item_author);
storySnippet = view.findViewById(R.id.story_item_content);
}
}
@ -660,12 +667,13 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
}
public class FooterViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.footer_view_inner) FrameLayout innerView;
public static class FooterViewHolder extends RecyclerView.ViewHolder {
FrameLayout innerView;
public FooterViewHolder(View view) {
super(view);
ButterKnife.bind(FooterViewHolder.this, view);
innerView = view.findViewById(R.id.footer_view_inner);
}
}

View file

@ -2,6 +2,7 @@ package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.util.Collection;
@ -62,6 +63,15 @@ public class Folder {
feedIds.removeAll(orphanFeedIds);
}
@Nullable
public String getFirstParentName() {
String folderParentName = null;
if (!parents.isEmpty()) {
folderParentName = parents.get(0);
}
return folderParentName;
}
@Override
public boolean equals(Object otherFolder) {
if (! (otherFolder instanceof Folder)) return false;

View file

@ -0,0 +1,122 @@
package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import java.util.Comparator;
public class SavedSearch {
@SerializedName("query")
public String query;
@SerializedName("feed_id")
public String feedId;
@SerializedName("feed_address")
public String feedAddress;
public String feedTitle;
public String faviconUrl;
public ContentValues getValues() {
ContentValues values = new ContentValues();
String feedTitle = "\"<b>" + query + "</b>\" in <b>" + getFeedTitle() + "</b>";
values.put(DatabaseConstants.SAVED_SEARCH_FEED_TITLE, feedTitle);
values.put(DatabaseConstants.SAVED_SEARCH_FAVICON, getFaviconUrl());
values.put(DatabaseConstants.SAVED_SEARCH_ADDRESS, feedAddress);
values.put(DatabaseConstants.SAVED_SEARCH_QUERY, query);
values.put(DatabaseConstants.SAVED_SEARCH_FEED_ID, feedId);
return values;
}
public static SavedSearch fromCursor(Cursor cursor) {
if (cursor.isBeforeFirst()) {
cursor.moveToFirst();
}
SavedSearch savedSearch = new SavedSearch();
savedSearch.feedTitle = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FEED_TITLE));
savedSearch.faviconUrl = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FAVICON));
savedSearch.feedAddress = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_ADDRESS));
savedSearch.query = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_QUERY));
savedSearch.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FEED_ID));
return savedSearch;
}
private String getFeedTitle() {
String feedTitle = null;
if (feedId.equals("river:")) {
feedTitle = "All Site Stories";
} else if (feedId.equals("river:infrequent")) {
feedTitle = "Infrequent Site Stories";
} else if (feedId.startsWith("river:")) {
String folderName = feedId.replace("river:", "");
FeedSet fs = FeedUtils.feedSetFromFolderName(folderName);
feedTitle = fs.getFolderName();
} else if (feedId.equals("read")) {
feedTitle = "Read Stories";
} else if (feedId.startsWith("starred")) {
feedTitle = "Saved Stories";
String tag = feedId.replace("starred:", "");
StarredCount starredFeed = FeedUtils.getStarredFeedByTag(tag);
if (starredFeed != null) {
String tagSlug = tag.replace(" ", "-");
if (starredFeed.tag.equals(tag) || starredFeed.tag.equals(tagSlug)) {
feedTitle = feedTitle + " - " + starredFeed.tag;
}
}
} else if (feedId.startsWith("feed:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("feed:", ""));
if (feed == null) return null;
feedTitle = feed.title;
} else if (feedId.startsWith("social:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("social:", ""));
if (feed == null) return null;
feedTitle = feed.title;
}
return feedTitle;
}
private String getFaviconUrl() {
String url = null;
if (feedId.equals("river:") || feedId.equals("river:infrequent")) {
url = "https://newsblur.com/media/img/icons/circular/ak-icon-allstories.png";
} else if (feedId.startsWith("river:")) {
url = "https://newsblur.com/media/img/icons/circular/g_icn_folder.png";
} else if (feedId.equals("read")) {
url = "https://newsblur.com/media/img/icons/circular/g_icn_unread.png";
} else if (feedId.equals("starred")) {
url = "https://newsblur.com/media/img/icons/circular/clock.png";
} else if (feedId.startsWith("starred:")) {
url = "https://newsblur.com/media/img/reader/tag.png";
} else if (feedId.startsWith("feed:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("feed:", ""));
if (feed != null) {
url = feed.faviconUrl;
}
} else if (feedId.startsWith("social:")) {
Feed feed = FeedUtils.getFeed(feedId.replace("social:", ""));
if (feed != null) {
url = feed.faviconUrl;
}
}
if (url == null) {
url = "https://newsblur.com/media/img/icons/circular/g_icn_search_black.png";
}
return url;
}
public final static Comparator<SavedSearch> SavedSearchComparatorByTitle = new Comparator<SavedSearch>() {
@Override
public int compare(SavedSearch ss1, SavedSearch ss2) {
return String.CASE_INSENSITIVE_ORDER.compare(ss1.feedTitle, ss2.feedTitle);
}
};
}

View file

@ -2,6 +2,7 @@ package com.newsblur.domain;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -82,6 +83,12 @@ public class Story implements Serializable {
@SerializedName("story_hash")
public String storyHash;
@SerializedName("secure_image_urls")
public Map<String, String> secureImageUrls;
@SerializedName("secure_image_thumbnails")
public Map<String, String> secureImageThumbnails;
// NOTE: this is parsed and saved to the DB, but is *not* generally un-thawed when stories are fetched back from the DB
@SerializedName("image_urls")
public String[] imageUrls;
@ -310,10 +317,13 @@ public class Story implements Serializable {
return YT_THUMB_PRE + ytUrl + YT_THUMB_POST;
}
if ((story.imageUrls != null) && (story.imageUrls.length > 0)) {
return story.imageUrls[0];
if (story.imageUrls != null && story.imageUrls.length > 0) {
String thumbnail = story.imageUrls[0];
if (thumbnail.startsWith("http://") && story.secureImageThumbnails != null && story.secureImageThumbnails.containsKey(thumbnail)){
thumbnail = story.secureImageThumbnails.get(thumbnail);
}
return thumbnail;
}
return null;
}

View file

@ -3,24 +3,42 @@ package com.newsblur.fragment;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
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 android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.activity.Main;
import com.newsblur.databinding.DialogAddFeedBinding;
import com.newsblur.databinding.RowAddFeedFolderBinding;
import com.newsblur.domain.Folder;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.AddFeedResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AddFeedFragment extends DialogFragment {
private static final String FEED_URI = "feed_url";
private static final String FEED_NAME = "feed_name";
private DialogAddFeedBinding binding;
public static AddFeedFragment newInstance(String feedUri, String feedName) {
AddFeedFragment frag = new AddFeedFragment();
@ -31,29 +49,97 @@ public class AddFeedFragment extends DialogFragment {
return frag;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String addFeedString = getResources().getString(R.string.add_feed_message);
final Activity activity = getActivity();
final APIManager apiManager = new APIManager(activity);
final Intent intent = new Intent(activity, Main.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_add_feed, null);
binding = DialogAddFeedBinding.bind(v);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(String.format(addFeedString, getArguments().getString(FEED_NAME)));
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
builder.setTitle("Choose folder for " + getArguments().getString(FEED_NAME));
builder.setView(v);
AddFeedAdapter adapter = new AddFeedAdapter(new OnFolderClickListener() {
@Override
public void onItemClick(Folder folder) {
addFeed(activity, apiManager, folder.name);
}
});
binding.textAddFolderTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (binding.containerAddFolder.getVisibility() == View.GONE) {
binding.containerAddFolder.setVisibility(View.VISIBLE);
} else {
binding.containerAddFolder.setVisibility(View.GONE);
}
}
});
binding.icCreateFolder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (binding.inputFolderName.getText().length() == 0) {
Toast.makeText(activity, R.string.add_folder_name, Toast.LENGTH_SHORT).show();
} else {
addFeedToNewFolder(activity, apiManager, binding.inputFolderName.getText().toString());
}
}
});
binding.recyclerViewFolders.addItemDecoration(new DividerItemDecoration(activity, LinearLayoutManager.VERTICAL));
binding.recyclerViewFolders.setAdapter(adapter);
adapter.setFolders(FeedUtils.dbHelper.getFolders());
return builder.create();
}
private void addFeedToNewFolder(final Activity activity, final APIManager apiManager, final String folderName) {
binding.icCreateFolder.setVisibility(View.GONE);
binding.progressBar.setVisibility(View.VISIBLE);
binding.inputFolderName.setEnabled(false);
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.addFolder(folderName);
}
@Override
protected void onPostExecute(NewsBlurResponse newsBlurResponse) {
super.onPostExecute(newsBlurResponse);
binding.inputFolderName.setEnabled(true);
if (!newsBlurResponse.isError()) {
binding.containerAddFolder.setVisibility(View.GONE);
binding.inputFolderName.getText().clear();
addFeed(activity, apiManager, folderName);
} else {
UIUtils.safeToast(activity, R.string.add_folder_error, Toast.LENGTH_SHORT);
}
}
}.execute();
}
private void addFeed(final Activity activity, final APIManager apiManager, @Nullable final String folderName) {
binding.textSyncStatus.setVisibility(View.VISIBLE);
new AsyncTask<Void, Void, AddFeedResponse>() {
@Override
protected AddFeedResponse doInBackground(Void... arg) {
protected AddFeedResponse doInBackground(Void... voids) {
((AddFeedProgressListener) activity).addFeedStarted();
return apiManager.addFeed(getArguments().getString(FEED_URI));
String feedUrl = getArguments().getString(FEED_URI);
return apiManager.addFeed(feedUrl, folderName);
}
@Override
protected void onPostExecute(AddFeedResponse result) {
super.onPostExecute(result);
binding.textSyncStatus.setVisibility(View.GONE);
final Intent intent = new Intent(activity, Main.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (!result.isError()) {
// trigger a sync when we return to Main so that the new feed will show up
NBSyncService.forceFeedsFolders();
@ -64,22 +150,70 @@ public class AddFeedFragment extends DialogFragment {
activity.startActivity(intent);
activity.finish();
AddFeedFragment.this.dismiss();
};
}
}.execute();
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
private static class AddFeedAdapter extends RecyclerView.Adapter<AddFeedAdapter.FolderViewHolder> {
AddFeedAdapter(OnFolderClickListener listener) {
this.listener = listener;
}
private final List<Folder> folders = new ArrayList<>();
private OnFolderClickListener listener;
@NonNull
@Override
public void onClick(DialogInterface dialogInterface, int i) {
AddFeedFragment.this.dismiss();
activity.startActivity(intent);
activity.finish();
public AddFeedAdapter.FolderViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_add_feed_folder, viewGroup, false);
return new FolderViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull AddFeedAdapter.FolderViewHolder viewHolder, int position) {
final Folder folder = folders.get(position);
if (folder.name.equals(AppConstants.ROOT_FOLDER)) {
viewHolder.binding.textFolderTitle.setText(R.string.top_level);
} else {
viewHolder.binding.textFolderTitle.setText(folder.flatName());
}
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(folder);
}
});
return builder.create();
}
@Override
public int getItemCount() {
return folders.size();
}
public void setFolders(List<Folder> folders) {
Collections.sort(folders, Folder.FolderComparator);
this.folders.clear();
this.folders.addAll(folders);
this.notifyDataSetChanged();
}
static class FolderViewHolder extends RecyclerView.ViewHolder {
public RowAddFeedFolderBinding binding;
public FolderViewHolder(@NonNull View itemView) {
super(itemView);
binding = RowAddFeedFolderBinding.bind(itemView);
}
}
}
public interface AddFeedProgressListener {
public abstract void addFeedStarted();
void addFeedStarted();
}
public interface OnFolderClickListener {
void onItemClick(Folder folder);
}
}

View file

@ -19,12 +19,9 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListAdapter;
import android.widget.ListView;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.databinding.DialogChoosefoldersBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedUtils;
@ -33,8 +30,6 @@ public class ChooseFoldersFragment extends DialogFragment {
private Feed feed;
@Bind(R.id.choose_folders_list) ListView listView;
public static ChooseFoldersFragment newInstance(Feed feed) {
ChooseFoldersFragment fragment = new ChooseFoldersFragment();
Bundle args = new Bundle();
@ -62,7 +57,7 @@ public class ChooseFoldersFragment extends DialogFragment {
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_choosefolders, null);
ButterKnife.bind(this, v);
DialogChoosefoldersBinding binding = DialogChoosefoldersBinding.bind(v);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(String.format(getResources().getString(R.string.title_choose_folders), feed.title));
@ -107,7 +102,7 @@ public class ChooseFoldersFragment extends DialogFragment {
return v;
}
};
listView.setAdapter(adapter);
binding.chooseFoldersList.setAdapter(adapter);
Dialog dialog = builder.create();
dialog.getWindow().getAttributes().gravity = Gravity.BOTTOM;

View file

@ -4,9 +4,11 @@ import com.newsblur.R;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.domain.Feed;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
import android.app.Activity;
import android.app.AlertDialog;
@ -22,6 +24,8 @@ public class DeleteFeedFragment extends DialogFragment {
private static final String FOLDER_NAME = "folder_name";
private static final String NORMAL_FEED = "normal";
private static final String SOCIAL_FEED = "social";
private static final String SAVED_SEARCH_FEED = "saved_search";
private static final String QUERY = "query";
public static DeleteFeedFragment newInstance(Feed feed, String folderName) {
DeleteFeedFragment frag = new DeleteFeedFragment();
@ -44,11 +48,25 @@ public class DeleteFeedFragment extends DialogFragment {
return frag;
}
public static DeleteFeedFragment newInstance(SavedSearch savedSearch) {
DeleteFeedFragment frag = new DeleteFeedFragment();
Bundle args = new Bundle();
args.putString(FEED_TYPE, SAVED_SEARCH_FEED);
args.putString(FEED_ID, savedSearch.feedId);
args.putString(FEED_NAME, savedSearch.feedTitle);
args.putString(QUERY, savedSearch.query);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) {
builder.setMessage(String.format(getResources().getString(R.string.delete_feed_message), getArguments().getString(FEED_NAME)));
} else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) {
String message = String.format(getResources().getString(R.string.delete_saved_search_message), getArguments().getString(FEED_NAME));
builder.setMessage(UIUtils.fromHtml(message));
} else {
builder.setMessage(String.format(getResources().getString(R.string.unfollow_message), getArguments().getString(FEED_NAME)));
}
@ -57,6 +75,8 @@ public class DeleteFeedFragment extends DialogFragment {
public void onClick(DialogInterface dialogInterface, int i) {
if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) {
FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), getActivity(), new APIManager(getActivity()));
} else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) {
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity(), new APIManager(getActivity()));
} else {
FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity(), new APIManager(getActivity()));
}

View file

@ -0,0 +1,57 @@
package com.newsblur.fragment;
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 android.text.TextUtils;
import com.newsblur.R;
import com.newsblur.network.APIManager;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
public class DeleteFolderFragment extends DialogFragment {
private static final String FOLDER_NAME = "folder_name";
private static final String FOLDER_PARENT = "folder_parent";
public static DeleteFolderFragment newInstance(String folderName, String folderParent) {
DeleteFolderFragment frag = new DeleteFolderFragment();
Bundle args = new Bundle();
args.putString(FOLDER_NAME, folderName);
args.putString(FOLDER_PARENT, folderParent);
frag.setArguments(args);
return frag;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final String folderName = getArguments().getString(FOLDER_NAME);
final String folderParent = getArguments().getString(FOLDER_PARENT);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(getResources().getString(R.string.delete_folder_message, folderName));
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String inFolder = "";
if (!TextUtils.isEmpty(folderParent) && !folderParent.equals(AppConstants.ROOT_FOLDER)) {
inFolder = folderParent;
}
FeedUtils.deleteFolder(folderName, inFolder, getActivity(), new APIManager(getActivity()));
DeleteFolderFragment.this.dismiss();
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
DeleteFolderFragment.this.dismiss();
}
});
return builder.create();
}
}

View file

@ -12,13 +12,10 @@ import android.support.v4.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.databinding.DialogTrainfeedBinding;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedSet;
@ -30,14 +27,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
private Feed feed;
private FeedSet fs;
private Classifier classifier;
@Bind(R.id.intel_title_header) TextView headerTitles;
@Bind(R.id.intel_tag_header) TextView headerTags;
@Bind(R.id.intel_author_header) TextView headerAuthor;
@Bind(R.id.existing_title_intel_container) LinearLayout titleRowsContainer;
@Bind(R.id.existing_tag_intel_container) LinearLayout tagRowsContainer;
@Bind(R.id.existing_author_intel_container) LinearLayout authorRowsContainer;
@Bind(R.id.existing_feed_intel_container) LinearLayout feedRowsContainer;
private DialogTrainfeedBinding binding;
public static FeedIntelTrainerFragment newInstance(Feed feed, FeedSet fs) {
FeedIntelTrainerFragment fragment = new FeedIntelTrainerFragment();
@ -58,7 +48,7 @@ public class FeedIntelTrainerFragment extends DialogFragment {
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_trainfeed, null);
ButterKnife.bind(this, v);
binding = DialogTrainfeedBinding.bind(v);
// display known title classifiers
for (Map.Entry<String, Integer> rule : classifier.title.entrySet()) {
@ -66,9 +56,9 @@ public class FeedIntelTrainerFragment extends DialogFragment {
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
label.setText(rule.getKey());
UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey());
titleRowsContainer.addView(row);
binding.existingTitleIntelContainer.addView(row);
}
if (classifier.title.size() < 1) headerTitles.setVisibility(View.GONE);
if (classifier.title.size() < 1) binding.intelTitleHeader.setVisibility(View.GONE);
// get the list of suggested tags
List<String> allTags = FeedUtils.dbHelper.getTagsForFeed(feed.feedId);
@ -83,9 +73,9 @@ public class FeedIntelTrainerFragment extends DialogFragment {
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
label.setText(tag);
UIUtils.setupIntelDialogRow(row, classifier.tags, tag);
tagRowsContainer.addView(row);
binding.existingTagIntelContainer.addView(row);
}
if (allTags.size() < 1) headerTags.setVisibility(View.GONE);
if (allTags.size() < 1) binding.intelTagHeader.setVisibility(View.GONE);
// get the list of suggested authors
List<String> allAuthors = FeedUtils.dbHelper.getAuthorsForFeed(feed.feedId);
@ -100,16 +90,16 @@ public class FeedIntelTrainerFragment extends DialogFragment {
TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label);
labelAuthor.setText(author);
UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, author);
authorRowsContainer.addView(rowAuthor);
binding.existingAuthorIntelContainer.addView(rowAuthor);
}
if (allAuthors.size() < 1) headerAuthor.setVisibility(View.GONE);
if (allAuthors.size() < 1) binding.intelAuthorHeader.setVisibility(View.GONE);
// for feel-level intel, the label is the title and the intel identifier is the feed ID
View rowFeed = inflater.inflate(R.layout.include_intel_row, null);
TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label);
labelFeed.setText(feed.title);
UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, feed.feedId);
feedRowsContainer.addView(rowFeed);
binding.existingFeedIntelContainer.addView(rowFeed);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.feed_intel_dialog_title);

View file

@ -26,9 +26,6 @@ import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ExpandableListView.OnGroupCollapseListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.activity.AllSharedStoriesItemsList;
import com.newsblur.activity.AllStoriesItemsList;
@ -43,7 +40,10 @@ import com.newsblur.activity.ReadStoriesItemsList;
import com.newsblur.activity.SavedStoriesItemsList;
import com.newsblur.activity.SocialFeedItemsList;
import com.newsblur.database.FolderListAdapter;
import com.newsblur.databinding.FragmentFolderfeedlistBinding;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
@ -64,11 +64,12 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
private static final int FOLDERS_LOADER = 2;
private static final int FEEDS_LOADER = 3;
private static final int SAVEDCOUNT_LOADER = 4;
private static final int SAVED_SEARCH_LOADER = 5;
private FolderListAdapter adapter;
public StateFilter currentState = StateFilter.SOME;
private SharedPreferences sharedPreferences;
@Bind(R.id.folderfeed_list) ExpandableListView list;
private FragmentFolderfeedlistBinding binding;
public boolean firstCursorSeenYet = false;
// the two-step context menu for feeds requires us to temp store the feed long-pressed so
@ -112,6 +113,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
return FeedUtils.dbHelper.getFeedsLoader();
case SAVEDCOUNT_LOADER:
return FeedUtils.dbHelper.getSavedStoryCountsLoader();
case SAVED_SEARCH_LOADER:
return FeedUtils.dbHelper.getSavedSearchLoader();
default:
throw new IllegalArgumentException("unknown loader created");
}
@ -140,6 +143,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
case SAVEDCOUNT_LOADER:
adapter.setStarredCountCursor(cursor);
break;
case SAVED_SEARCH_LOADER:
adapter.setSavedSearchesCursor(cursor);
break;
default:
throw new IllegalArgumentException("unknown loader created");
}
@ -163,6 +169,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
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);
} catch (Exception e) {
// on heavily loaded devices, the time between isAdded() going false
// and the loader subsystem shutting down can be nontrivial, causing
@ -179,6 +186,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
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);
}
}
}
@ -190,20 +198,20 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_folderfeedlist, container);
ButterKnife.bind(this, v);
binding = FragmentFolderfeedlistBinding.bind(v);
list.setGroupIndicator(UIUtils.getDrawable(getActivity(), R.drawable.transparent));
list.setOnCreateContextMenuListener(this);
list.setOnChildClickListener(this);
list.setOnGroupClickListener(this);
list.setOnGroupCollapseListener(this);
list.setOnGroupExpandListener(this);
binding.folderfeedList.setGroupIndicator(UIUtils.getDrawable(getActivity(), R.drawable.transparent));
binding.folderfeedList.setOnCreateContextMenuListener(this);
binding.folderfeedList.setOnChildClickListener(this);
binding.folderfeedList.setOnGroupClickListener(this);
binding.folderfeedList.setOnGroupCollapseListener(this);
binding.folderfeedList.setOnGroupExpandListener(this);
adapter.listBackref = new WeakReference(list); // see note in adapter about backref
list.setAdapter(adapter);
adapter.listBackref = new WeakReference(binding.folderfeedList); // see note in adapter about backref
binding.folderfeedList.setAdapter(adapter);
// Main activity needs to listen for scrolls to prevent refresh from firing unnecessarily
list.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity());
binding.folderfeedList.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity());
return v;
}
@ -215,18 +223,18 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
*/
public void checkOpenFolderPreferences() {
// make sure we didn't beat construction
if ((this.list == null) || (this.sharedPreferences == null)) return;
if ((this.binding.folderfeedList == null) || (this.sharedPreferences == null)) return;
for (int i = 0; i < adapter.getGroupCount(); i++) {
String flatGroupName = adapter.getGroupUniqueName(i);
if (sharedPreferences.getBoolean(AppConstants.FOLDER_PRE + "_" + flatGroupName, true)) {
if (list.isGroupExpanded(i) == false) {
list.expandGroup(i);
if (binding.folderfeedList.isGroupExpanded(i) == false) {
binding.folderfeedList.expandGroup(i);
adapter.setFolderClosed(flatGroupName, false);
}
} else {
if (list.isGroupExpanded(i) == true) {
list.collapseGroup(i);
if (binding.folderfeedList.isGroupExpanded(i) == true) {
binding.folderfeedList.collapseGroup(i);
adapter.setFolderClosed(flatGroupName, true);
}
}
@ -249,11 +257,14 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
if (adapter.isRowGlobalSharedStories(groupPosition)) break;
if (adapter.isRowAllSharedStories(groupPosition)) break;
if (adapter.isRowInfrequentStories(groupPosition)) break;
if (adapter.isRowSavedSearches(groupPosition)) break;
inflater.inflate(R.menu.context_folder, menu);
if (adapter.isRowAllStories(groupPosition)) {
menu.removeItem(R.id.menu_mute_folder);
menu.removeItem(R.id.menu_unmute_folder);
menu.removeItem(R.id.menu_delete_folder);
menu.removeItem(R.id.menu_rename_folder);
}
break;
@ -272,9 +283,22 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
menu.removeItem(R.id.menu_instafetch_feed);
menu.removeItem(R.id.menu_intel);
menu.removeItem(R.id.menu_rename_feed);
menu.removeItem(R.id.menu_delete_saved_search);
} else if (adapter.isRowSavedSearches(groupPosition)) {
menu.removeItem(R.id.menu_mark_feed_as_read);
menu.removeItem(R.id.menu_delete_feed);
menu.removeItem(R.id.menu_unfollow);
menu.removeItem(R.id.menu_choose_folders);
menu.removeItem(R.id.menu_rename_feed);
menu.removeItem(R.id.menu_notifications);
menu.removeItem(R.id.menu_mute_feed);
menu.removeItem(R.id.menu_unmute_feed);
menu.removeItem(R.id.menu_instafetch_feed);
menu.removeItem(R.id.menu_intel);
} else {
// normal feeds
menu.removeItem(R.id.menu_unfollow);
menu.removeItem(R.id.menu_delete_saved_search);
Feed feed = adapter.getFeed(groupPosition, childPosition);
if (feed.active) {
@ -359,7 +383,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
} else if (item.getItemId() == R.id.menu_rename_feed) {
Feed feed = adapter.getFeed(groupPosition, childPosition);
if (feed != null) {
DialogFragment renameFeedFragment = RenameFeedFragment.newInstance(feed);
DialogFragment renameFeedFragment = RenameDialogFragment.newInstance(feed);
renameFeedFragment.show(getFragmentManager(), "dialog");
}
} else if (item.getItemId() == R.id.menu_mute_feed) {
@ -379,6 +403,22 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
} 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());
} 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");
}
} 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());
} 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());
}
return super.onContextItemSelected(item);
@ -449,6 +489,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
i = new Intent(getActivity(), ReadStoriesItemsList.class);
} else if (adapter.isRowSavedStories(groupPosition)) {
i = new Intent(getActivity(), SavedStoriesItemsList.class);
} else if (adapter.isRowSavedSearches(groupPosition)) {
// group not clickable
return true;
} else {
i = new Intent(getActivity(), FolderItemsList.class);
String canonicalFolderName = adapter.getGroupFolderName(groupPosition);
@ -472,6 +515,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
// these shouldn't ever be collapsible
if (adapter.isRowRootFolder(groupPosition)) return;
if (adapter.isRowReadStories(groupPosition)) return;
if (adapter.isRowSavedSearches(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the expanded preference, since the widget likes to forget it
@ -490,6 +534,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
// these shouldn't ever be collapsible
if (adapter.isRowRootFolder(groupPosition)) return;
if (adapter.isRowReadStories(groupPosition)) return;
if (adapter.isRowSavedSearches(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the collapsed preference, since the widget likes to forget it
@ -515,6 +560,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
Intent intent = new Intent(getActivity(), SavedStoriesItemsList.class);
intent.putExtra(ItemsList.EXTRA_FEED_SET, fs);
getActivity().startActivity(intent);
} else if (adapter.isRowSavedSearches(groupPosition)) {
openSavedSearch(adapter.getSavedSearch(childPosition));
} else {
Feed feed = adapter.getFeed(groupPosition, childPosition);
// NB: FeedItemsList needs the name of the containing folder, but this is not the same as setting
@ -534,6 +581,53 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
return true;
}
private void openSavedSearch(SavedSearch savedSearch) {
Intent intent = null;
FeedSet fs = null;
String feedId = savedSearch.feedId;
if (feedId.equals("river:")) {
// all site stories
intent = new Intent(getActivity(), AllStoriesItemsList.class);
fs = FeedSet.allFeeds();
} else if (feedId.equals("river:infrequent")) {
// infrequent stories
intent = new Intent(getActivity(), InfrequentItemsList.class);
fs = FeedSet.infrequentFeeds();
} else if (feedId.startsWith("river:")) {
intent = new Intent(getActivity(), FolderItemsList.class);
String folderName = feedId.replace("river:", "");
fs = FeedUtils.feedSetFromFolderName(folderName);
intent.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, folderName);
} else if (feedId.equals("read")) {
intent = new Intent(getActivity(), ReadStoriesItemsList.class);
fs = FeedSet.allRead();
} else if (feedId.equals("starred")) {
intent = new Intent(getActivity(), SavedStoriesItemsList.class);
fs = FeedSet.allSaved();
} else if (feedId.startsWith("starred:")) {
intent = new Intent(getActivity(), SavedStoriesItemsList.class);
fs = FeedSet.singleSavedTag(feedId.replace("starred:", ""));
} else if (feedId.startsWith("feed:")) {
intent = new Intent(getActivity(), FeedItemsList.class);
String cleanFeedId = feedId.replace("feed:", "");
Feed feed = FeedUtils.getFeed(cleanFeedId);
fs = FeedSet.singleFeed(cleanFeedId);
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
} else if (feedId.startsWith("social:")) {
intent = new Intent(getActivity(), SocialFeedItemsList.class);
String cleanFeedId = feedId.replace("social:", "");
fs = FeedSet.singleFeed(cleanFeedId);
Feed feed = FeedUtils.getFeed(cleanFeedId);
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
}
if (intent != null) {
fs.setSearchQuery(savedSearch.query);
intent.putExtra(ItemsList.EXTRA_FEED_SET, fs);
startActivity(intent);
}
}
public void setTextSize(Float size) {
if (adapter != null) {
adapter.setTextSize(size);

View file

@ -1,28 +1,22 @@
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 android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.databinding.InfrequentCutoffDialogBinding;
public class InfrequentCutoffDialogFragment extends DialogFragment {
private static String CURRENT_CUTOFF = "currentCutoff";
private int currentValue;
@Bind(R.id.radio_5) RadioButton button5;
@Bind(R.id.radio_15) RadioButton button15;
@Bind(R.id.radio_30) RadioButton button30;
@Bind(R.id.radio_60) RadioButton button60;
@Bind(R.id.radio_90) RadioButton button90;
private InfrequentCutoffDialogBinding binding;
public static InfrequentCutoffDialogFragment newInstance(int currentValue) {
InfrequentCutoffDialogFragment dialog = new InfrequentCutoffDialogFragment();
@ -42,13 +36,13 @@ public class InfrequentCutoffDialogFragment extends DialogFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = getArguments().getInt(CURRENT_CUTOFF);
View v = inflater.inflate(R.layout.infrequent_cutoff_dialog, null);
ButterKnife.bind(this, v);
binding = InfrequentCutoffDialogBinding.bind(v);
button5.setChecked(currentValue == 5);
button15.setChecked(currentValue == 15);
button30.setChecked(currentValue == 30);
button60.setChecked(currentValue == 60);
button90.setChecked(currentValue == 90);
binding.radio5.setChecked(currentValue == 5);
binding.radio15.setChecked(currentValue == 15);
binding.radio30.setChecked(currentValue == 30);
binding.radio60.setChecked(currentValue == 60);
binding.radio90.setChecked(currentValue == 90);
getDialog().setTitle(R.string.infrequent_choice_title);
getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM;
@ -56,31 +50,66 @@ public class InfrequentCutoffDialogFragment extends DialogFragment {
return v;
}
@OnClick(R.id.radio_5) void select5() {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.radio5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select5();
}
});
binding.radio15.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select15();
}
});
binding.radio30.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select30();
}
});
binding.radio60.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select60();
}
});
binding.radio90.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select90();
}
});
}
private void select5() {
if (currentValue != 5) {
((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(5);
}
dismiss();
}
@OnClick(R.id.radio_15) void select15() {
private void select15() {
if (currentValue != 15) {
((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(15);
}
dismiss();
}
@OnClick(R.id.radio_30) void select30() {
private void select30() {
if (currentValue != 30) {
((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(30);
}
dismiss();
}
@OnClick(R.id.radio_60) void select60() {
private void select60() {
if (currentValue != 60) {
((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(60);
}
dismiss();
}
@OnClick(R.id.radio_90) void select90() {
private void select90() {
if (currentValue != 90) {
((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(90);
}

View file

@ -15,17 +15,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
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.domain.Story;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.FeedSet;
@ -50,21 +46,17 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
private final static int GRID_SPACING_DP = 5;
private int gridSpacingPx;
@Bind(R.id.itemgridfragment_grid) RecyclerView itemGrid;
private GridLayoutManager layoutManager;
private StoryViewAdapter adapter;
// an optional pending scroll state to restore.
private Parcelable gridState;
// loading indicator for when stories are absent or stale (at top of list)
@Bind(R.id.top_loading_throb) ProgressThrobber topProgressView;
// R.id.top_loading_throb
// loading indicator for when stories are present and fresh (at bottom of list)
protected ProgressThrobber bottomProgressView;
@Bind(R.id.empty_view) View emptyView;
@Bind(R.id.empty_view_text) TextView emptyViewText;
@Bind(R.id.empty_view_image) ImageView emptyViewImage;
private View fleuronFooter;
// the fleuron has padding that can't be calculated until after layout, but only changes
// rarely thereafter
@ -76,6 +68,8 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
public int indexOfLastUnread = -1;
public boolean fullFlingComplete = false;
private FragmentItemgridBinding binding;
public static ItemSetFragment newInstance() {
ItemSetFragment fragment = new ItemSetFragment();
Bundle arguments = new Bundle();
@ -123,13 +117,13 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_itemgrid, null);
ButterKnife.bind(this, v);
binding = FragmentItemgridBinding.bind(v);
// disable the throbbers if animations are going to have a zero time scale
boolean isDisableAnimations = ViewUtils.isPowerSaveMode(getActivity());
topProgressView.setEnabled(!isDisableAnimations);
topProgressView.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1),
binding.topLoadingThrob.setEnabled(!isDisableAnimations);
binding.topLoadingThrob.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1),
UIUtils.getColor(getActivity(), R.color.refresh_2),
UIUtils.getColor(getActivity(), R.color.refresh_3),
UIUtils.getColor(getActivity(), R.color.refresh_4));
@ -145,11 +139,11 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
fleuronFooter = inflater.inflate(R.layout.row_fleuron, null);
fleuronFooter.setVisibility(View.INVISIBLE);
itemGrid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
binding.itemgridfragmentGrid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
itemGridWidthPx = itemGrid.getMeasuredWidth();
itemGrid.getViewTreeObserver().removeOnGlobalLayoutListener(this);
itemGridWidthPx = binding.itemgridfragmentGrid.getMeasuredWidth();
binding.itemgridfragmentGrid.getViewTreeObserver().removeOnGlobalLayoutListener(this);
updateStyle();
}
});
@ -158,11 +152,11 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
calcColumnCount(listStyle);
layoutManager = new GridLayoutManager(getActivity(), columnCount);
itemGrid.setLayoutManager(layoutManager);
binding.itemgridfragmentGrid.setLayoutManager(layoutManager);
setupAnimSpeeds();
calcGridSpacing(listStyle);
itemGrid.addItemDecoration(new RecyclerView.ItemDecoration() {
binding.itemgridfragmentGrid.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(gridSpacingPx, gridSpacingPx, gridSpacingPx, gridSpacingPx);
@ -172,7 +166,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
adapter = new StoryViewAdapter(((NbActivity) getActivity()), this, getFeedSet(), listStyle);
adapter.addFooterView(footerView);
adapter.addFooterView(fleuronFooter);
itemGrid.setAdapter(adapter);
binding.itemgridfragmentGrid.setAdapter(adapter);
// the layout manager needs to know that the footer rows span all the way across
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@ -187,14 +181,14 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
}
});
itemGrid.addOnScrollListener(new RecyclerView.OnScrollListener() {
binding.itemgridfragmentGrid.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
ItemSetFragment.this.onScrolled(recyclerView, dx, dy);
}
});
setupGestureDetector(itemGrid);
setupGestureDetector(binding.itemgridfragmentGrid);
return v;
}
@ -281,13 +275,13 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
}
protected void updateAdapter(Cursor cursor) {
adapter.swapCursor(cursor, itemGrid, gridState);
adapter.swapCursor(cursor, binding.itemgridfragmentGrid, gridState);
gridState = null;
adapter.updateFeedSet(getFeedSet());
if ((cursor != null) && (cursor.getCount() > 0)) {
emptyView.setVisibility(View.INVISIBLE);
binding.emptyView.setVisibility(View.INVISIBLE);
} else {
emptyView.setVisibility(View.VISIBLE);
binding.emptyView.setVisibility(View.VISIBLE);
}
// though we have stories, we might not yet have as many as we want
@ -305,38 +299,38 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
calcFleuronPadding();
if (getFeedSet().isMuted()) {
emptyViewText.setText(R.string.empty_list_view_muted_feed);
emptyViewText.setTypeface(null, Typeface.NORMAL);
emptyViewImage.setVisibility(View.VISIBLE);
topProgressView.setVisibility(View.INVISIBLE);
binding.emptyViewText.setText(R.string.empty_list_view_muted_feed);
binding.emptyViewText.setTypeface(null, Typeface.NORMAL);
binding.emptyViewImage.setVisibility(View.VISIBLE);
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
bottomProgressView.setVisibility(View.INVISIBLE);
return;
}
if ( (!cursorSeenYet) || NBSyncService.isFeedSetSyncing(getFeedSet(), getActivity()) ) {
emptyViewText.setText(R.string.empty_list_view_loading);
emptyViewText.setTypeface(null, Typeface.ITALIC);
emptyViewImage.setVisibility(View.INVISIBLE);
binding.emptyViewText.setText(R.string.empty_list_view_loading);
binding.emptyViewText.setTypeface(null, Typeface.ITALIC);
binding.emptyViewImage.setVisibility(View.INVISIBLE);
if (NBSyncService.isFeedSetStoriesFresh(getFeedSet())) {
topProgressView.setVisibility(View.INVISIBLE);
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
bottomProgressView.setVisibility(View.VISIBLE);
} else {
topProgressView.setVisibility(View.VISIBLE);
binding.topLoadingThrob.setVisibility(View.VISIBLE);
bottomProgressView.setVisibility(View.GONE);
}
fleuronFooter.setVisibility(View.INVISIBLE);
} else {
ReadFilter readFilter = PrefsUtils.getReadFilter(getActivity(), getFeedSet());
if (readFilter == ReadFilter.UNREAD) {
emptyViewText.setText(R.string.empty_list_view_no_stories_unread);
binding.emptyViewText.setText(R.string.empty_list_view_no_stories_unread);
} else {
emptyViewText.setText(R.string.empty_list_view_no_stories);
binding.emptyViewText.setText(R.string.empty_list_view_no_stories);
}
emptyViewText.setTypeface(null, Typeface.NORMAL);
emptyViewImage.setVisibility(View.VISIBLE);
binding.emptyViewText.setTypeface(null, Typeface.NORMAL);
binding.emptyViewImage.setVisibility(View.VISIBLE);
topProgressView.setVisibility(View.INVISIBLE);
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
bottomProgressView.setVisibility(View.INVISIBLE);
if (cursorSeenYet && NBSyncService.isFeedSetExhausted(getFeedSet()) && (adapter.getRawStoryCount() > 0)) {
fleuronFooter.setVisibility(View.VISIBLE);
@ -411,7 +405,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
targetMovDuration = 0L;
}
RecyclerView.ItemAnimator anim = itemGrid.getItemAnimator();
RecyclerView.ItemAnimator anim = binding.itemgridfragmentGrid.getItemAnimator();
anim.setAddDuration((long) ((anim.getAddDuration() + targetAddDuration)/2L));
anim.setMoveDuration((long) ((anim.getMoveDuration() + targetMovDuration)/2L));
}
@ -429,7 +423,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
// past the last item, which can be confusing to users who don't know about or need the offset
if ( (!fullFlingComplete) &&
(layoutManager.findLastCompletelyVisibleItemPosition() >= adapter.getStoryCount()) ) {
itemGrid.stopScroll();
binding.itemgridfragmentGrid.stopScroll();
// but after halting at the end once, do allow scrolling past the bottom
fullFlingComplete = true;
}
@ -439,7 +433,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
(layoutManager.findLastCompletelyVisibleItemPosition() >= indexOfLastUnread) ) {
// but don't interrupt if already past the last unread
if (indexOfLastUnread >= layoutManager.findFirstCompletelyVisibleItemPosition()) {
itemGrid.stopScroll();
binding.itemgridfragmentGrid.stopScroll();
}
indexOfLastUnread = -1;
}
@ -507,7 +501,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
*/
private void calcFleuronPadding() {
if (fleuronResized) return;
int listHeight = itemGrid.getMeasuredHeight();
int listHeight = binding.itemgridfragmentGrid.getMeasuredHeight();
View innerView = fleuronFooter.findViewById(R.id.fleuron);
ViewGroup.LayoutParams oldLayout = innerView.getLayoutParams();
ViewGroup.MarginLayoutParams newLayout = new LinearLayout.LayoutParams(oldLayout);
@ -526,7 +520,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
@Override
public void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_GRIDSTATE, itemGrid.getLayoutManager().onSaveInstanceState());
outState.putParcelable(BUNDLE_GRIDSTATE, binding.itemgridfragmentGrid.getLayoutManager().onSaveInstanceState());
}
}

View file

@ -11,17 +11,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.activity.Login;
import com.newsblur.activity.Main;
import com.newsblur.databinding.FragmentLoginprogressBinding;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.util.PrefsUtils;
@ -30,14 +25,10 @@ import com.newsblur.util.UIUtils;
public class LoginProgressFragment extends Fragment {
private APIManager apiManager;
@Bind(R.id.login_logging_in) TextView updateStatus;
@Bind(R.id.login_retrieving_feeds) TextView retrievingFeeds;
@Bind(R.id.login_profile_picture) ImageView loginProfilePicture;
@Bind(R.id.login_feed_progress) ProgressBar feedProgress;
@Bind(R.id.login_logging_in_progress) ProgressBar loggingInProgress;
private LoginTask loginTask;
private String username;
private String password;
private FragmentLoginprogressBinding binding;
public static LoginProgressFragment getInstance(String username, String password) {
LoginProgressFragment fragment = new LoginProgressFragment();
@ -61,7 +52,7 @@ public class LoginProgressFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_loginprogress, null);
ButterKnife.bind(this, v);
binding = FragmentLoginprogressBinding.bind(v);
loginTask = new LoginTask();
loginTask.execute();
@ -73,7 +64,7 @@ public class LoginProgressFragment extends Fragment {
@Override
protected void onPreExecute() {
Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_up);
updateStatus.startAnimation(a);
binding.loginLoggingIn.startAnimation(a);
}
@Override
@ -90,20 +81,20 @@ public class LoginProgressFragment extends Fragment {
if (c == null) return; // we might have run past the lifecycle of the activity
if (!result.isError()) {
final Animation a = AnimationUtils.loadAnimation(c, R.anim.text_down);
updateStatus.setText(R.string.login_logged_in);
loggingInProgress.setVisibility(View.GONE);
updateStatus.startAnimation(a);
binding.loginLoggingIn.setText(R.string.login_logged_in);
binding.loginLoggingInProgress.setVisibility(View.GONE);
binding.loginLoggingIn.startAnimation(a);
Bitmap userImage = PrefsUtils.getUserImage(c);
if (userImage != null ) {
loginProfilePicture.setVisibility(View.VISIBLE);
loginProfilePicture.setImageBitmap(UIUtils.clipAndRound(userImage, 10f, false));
binding.loginProfilePicture.setVisibility(View.VISIBLE);
binding.loginProfilePicture.setImageBitmap(UIUtils.clipAndRound(userImage, 10f, false));
}
feedProgress.setVisibility(View.VISIBLE);
binding.loginFeedProgress.setVisibility(View.VISIBLE);
final Animation b = AnimationUtils.loadAnimation(c, R.anim.text_up);
retrievingFeeds.setText(R.string.login_retrieving_feeds);
retrievingFeeds.startAnimation(b);
binding.loginRetrievingFeeds.setText(R.string.login_retrieving_feeds);
binding.loginFeedProgress.startAnimation(b);
Intent startMain = new Intent(getActivity(), Main.class);
c.startActivity(startMain);

View file

@ -3,6 +3,8 @@ 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 android.text.TextUtils;
import android.view.KeyEvent;
@ -10,39 +12,27 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ViewSwitcher;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.LoginProgress;
import com.newsblur.activity.RegisterProgress;
import com.newsblur.databinding.FragmentLoginregisterBinding;
import com.newsblur.network.APIConstants;
import com.newsblur.util.AppConstants;
import com.newsblur.util.PrefsUtils;
public class LoginRegisterFragment extends Fragment {
@Bind(R.id.login_username) EditText username;
@Bind(R.id.login_password) EditText password;
@Bind(R.id.registration_username) EditText register_username;
@Bind(R.id.registration_password) EditText register_password;
@Bind(R.id.registration_email) EditText register_email;
@Bind(R.id.login_viewswitcher) ViewSwitcher viewSwitcher;
@Bind(R.id.login_custom_server) View customServer;
@Bind(R.id.login_custom_server_value) EditText customServerValue;
private FragmentLoginregisterBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_loginregister, container, false);
ButterKnife.bind(this, v);
binding = FragmentLoginregisterBinding.bind(v);
password.setOnEditorActionListener(new OnEditorActionListener() {
binding.loginPassword.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE ) {
@ -52,7 +42,7 @@ public class LoginRegisterFragment extends Fragment {
}
});
register_email.setOnEditorActionListener(new OnEditorActionListener() {
binding.registrationEmail.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE ) {
@ -65,36 +55,77 @@ public class LoginRegisterFragment extends Fragment {
return v;
}
@OnClick(R.id.login_button) void logIn() {
if (!TextUtils.isEmpty(username.getText().toString())) {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
logIn();
}
});
binding.registrationButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signUp();
}
});
binding.loginChangeToLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showLogin();
}
});
binding.loginChangeToRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showRegister();
}
});
binding.loginForgotPassword.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
launchForgotPasswordPage();
}
});
binding.loginCustomServer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showCustomServer();
}
});
}
private void logIn() {
if (!TextUtils.isEmpty(binding.loginUsername.getText().toString())) {
// set the custom server endpoint before any API access, even the cookie fetch.
APIConstants.setCustomServer(customServerValue.getText().toString());
PrefsUtils.saveCustomServer(getActivity(), customServerValue.getText().toString());
APIConstants.setCustomServer(binding.loginCustomServerValue.getText().toString());
PrefsUtils.saveCustomServer(getActivity(), binding.loginCustomServerValue.getText().toString());
Intent i = new Intent(getActivity(), LoginProgress.class);
i.putExtra("username", username.getText().toString());
i.putExtra("password", password.getText().toString());
i.putExtra("username", binding.loginUsername.getText().toString());
i.putExtra("password", binding.loginUsername.getText().toString());
startActivity(i);
}
}
@OnClick(R.id.registration_button) void signUp() {
private void signUp() {
Intent i = new Intent(getActivity(), RegisterProgress.class);
i.putExtra("username", register_username.getText().toString());
i.putExtra("password", register_password.getText().toString());
i.putExtra("email", register_email.getText().toString());
i.putExtra("username", binding.registrationUsername.getText().toString());
i.putExtra("password", binding.registrationPassword.getText().toString());
i.putExtra("email", binding.registrationEmail.getText().toString());
startActivity(i);
}
@OnClick(R.id.login_change_to_login) void showLogin() {
viewSwitcher.showPrevious();
private void showLogin() {
binding.loginViewswitcher.showPrevious();
}
@OnClick(R.id.login_change_to_register) void showRegister() {
viewSwitcher.showNext();
private void showRegister() {
binding.loginViewswitcher.showNext();
}
@OnClick(R.id.login_forgot_password) void launchForgotPasswordPage() {
private void launchForgotPasswordPage() {
try {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(AppConstants.FORGOT_PASWORD_URL));
@ -104,9 +135,8 @@ public class LoginRegisterFragment extends Fragment {
}
}
@OnClick(R.id.login_custom_server) void showCustomServer() {
customServer.setVisibility(View.GONE);
customServerValue.setVisibility(View.VISIBLE);
private void showCustomServer() {
binding.loginCustomServer.setVisibility(View.GONE);
binding.loginCustomServerValue.setVisibility(View.VISIBLE);
}
}

View file

@ -1,19 +1,17 @@
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 android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.databinding.ReadfilterDialogBinding;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.ReadFilterChangedListener;
@ -21,8 +19,7 @@ public class ReadFilterDialogFragment extends DialogFragment {
private static String CURRENT_FILTER = "currentFilter";
private ReadFilter currentValue;
@Bind(R.id.radio_all) RadioButton allButton;
@Bind(R.id.radio_unread) RadioButton unreadButton;
private ReadfilterDialogBinding binding;
public static ReadFilterDialogFragment newInstance(ReadFilter currentValue) {
ReadFilterDialogFragment dialog = new ReadFilterDialogFragment();
@ -42,10 +39,10 @@ public class ReadFilterDialogFragment extends DialogFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = (ReadFilter) getArguments().getSerializable(CURRENT_FILTER);
View v = inflater.inflate(R.layout.readfilter_dialog, null);
ButterKnife.bind(this, v);
binding = ReadfilterDialogBinding.bind(v);
allButton.setChecked(currentValue == ReadFilter.ALL);
unreadButton.setChecked(currentValue == ReadFilter.UNREAD);
binding.radioAll.setChecked(currentValue == ReadFilter.ALL);
binding.radioUnread.setChecked(currentValue == ReadFilter.UNREAD);
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM;
@ -53,14 +50,31 @@ public class ReadFilterDialogFragment extends DialogFragment {
return v;
}
@OnClick(R.id.radio_all) void selectAll() {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.radioAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectAll();
}
});
binding.radioUnread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectUnread();
}
});
}
private void selectAll() {
if (currentValue != ReadFilter.ALL) {
((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.ALL);
}
dismiss();
}
@OnClick(R.id.radio_unread) void selectUnread() {
private void selectUnread() {
if (currentValue != ReadFilter.UNREAD) {
((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.UNREAD);
}

View file

@ -1,21 +1,19 @@
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 android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.RadioButton;
import com.newsblur.R;
import com.newsblur.databinding.ReadingfontDialogBinding;
import com.newsblur.util.ReadingFontChangedListener;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by mark on 02/05/2017.
*/
@ -26,14 +24,7 @@ public class ReadingFontDialogFragment extends DialogFragment {
private String currentValue;
@Bind(R.id.radio_anonymous) RadioButton anonymousButton;
@Bind(R.id.radio_chronicle) RadioButton chronicleButton;
@Bind(R.id.radio_default) RadioButton defaultButton;
@Bind(R.id.radio_gotham) RadioButton gothamButton;
@Bind(R.id.radio_noto_sans) RadioButton notoSansButton;
@Bind(R.id.radio_noto_serif) RadioButton notoSerifButton;
@Bind(R.id.radio_open_sans_condensed) RadioButton openSansCondensedButton;
@Bind(R.id.radio_whitney) RadioButton whitneyButton;
private ReadingfontDialogBinding binding;
public static ReadingFontDialogFragment newInstance(String selectedFont) {
ReadingFontDialogFragment dialog = new ReadingFontDialogFragment();
@ -52,16 +43,16 @@ public class ReadingFontDialogFragment extends DialogFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = getArguments().getString(SELECTED_FONT);
View v = inflater.inflate(R.layout.readingfont_dialog, null);
ButterKnife.bind(this, v);
binding = ReadingfontDialogBinding.bind(v);
anonymousButton.setChecked(currentValue.equals(getString(R.string.anonymous_pro_font_prefvalue)));
chronicleButton.setChecked(currentValue.equals(getString(R.string.chronicle_font_prefvalue)));
defaultButton.setChecked(currentValue.equals(getString(R.string.default_font_prefvalue)));
gothamButton.setChecked(currentValue.equals(getString(R.string.gotham_narrow_font_prefvalue)));
notoSansButton.setChecked(currentValue.equals(getString(R.string.noto_sans_font_prefvalue)));
notoSerifButton.setChecked(currentValue.equals(getString(R.string.noto_serif_font_prefvalue)));
openSansCondensedButton.setChecked(currentValue.equals(getString(R.string.open_sans_condensed_font_prefvalue)));
whitneyButton.setChecked(currentValue.equals(getString(R.string.whitney_font_prefvalue)));
binding.radioAnonymous.setChecked(currentValue.equals(getString(R.string.anonymous_pro_font_prefvalue)));
binding.radioChronicle.setChecked(currentValue.equals(getString(R.string.chronicle_font_prefvalue)));
binding.radioDefault.setChecked(currentValue.equals(getString(R.string.default_font_prefvalue)));
binding.radioGotham.setChecked(currentValue.equals(getString(R.string.gotham_narrow_font_prefvalue)));
binding.radioNotoSans.setChecked(currentValue.equals(getString(R.string.noto_sans_font_prefvalue)));
binding.radioNotoSerif.setChecked(currentValue.equals(getString(R.string.noto_serif_font_prefvalue)));
binding.radioOpenSansCondensed.setChecked(currentValue.equals(getString(R.string.open_sans_condensed_font_prefvalue)));
binding.radioWhitney.setChecked(currentValue.equals(getString(R.string.whitney_font_prefvalue)));
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM;
@ -69,9 +60,58 @@ public class ReadingFontDialogFragment extends DialogFragment {
return v;
}
@OnClick(R.id.radio_anonymous) void selectAnonymousPro() {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.radioAnonymous.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.anonymous_pro_font_prefvalue));
}
});
binding.radioDefault.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.default_font_prefvalue));
}
});
binding.radioChronicle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.chronicle_font_prefvalue));
}
});
binding.radioGotham.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.gotham_narrow_font_prefvalue));
}
});
binding.radioNotoSans.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.noto_sans_font_prefvalue));
}
});
binding.radioNotoSerif.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.noto_serif_font_prefvalue));
}
});
binding.radioOpenSansCondensed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.open_sans_condensed_font_prefvalue));
}
});
binding.radioWhitney.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchFont(getString(R.string.whitney_font_prefvalue));
}
});
}
private void switchFont(String newValue) {
if (!currentValue.equals(newValue)) {
@ -79,32 +119,4 @@ public class ReadingFontDialogFragment extends DialogFragment {
currentValue = newValue;
}
}
@OnClick(R.id.radio_chronicle) void selectChronicle() {
switchFont(getString(R.string.chronicle_font_prefvalue));
}
@OnClick(R.id.radio_default) void selectDefault() {
switchFont(getString(R.string.default_font_prefvalue));
}
@OnClick(R.id.radio_gotham) void selectGotham() {
switchFont(getString(R.string.gotham_narrow_font_prefvalue));
}
@OnClick(R.id.radio_noto_sans) void selectNotoSans() {
switchFont(getString(R.string.noto_sans_font_prefvalue));
}
@OnClick(R.id.radio_noto_serif) void selectNotoSerif() {
switchFont(getString(R.string.noto_serif_font_prefvalue));
}
@OnClick(R.id.radio_open_sans_condensed) void selectOpenSansCondensed() {
switchFont(getString(R.string.open_sans_condensed_font_prefvalue));
}
@OnClick(R.id.radio_whitney) void selectWhitney() {
switchFont(getString(R.string.whitney_font_prefvalue));
}
}

View file

@ -7,12 +7,15 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
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 android.text.TextUtils;
import android.util.Log;
@ -26,22 +29,17 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.webkit.WebView.HitTestResult;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.ScrollView;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.FeedItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.Reading;
import com.newsblur.databinding.FragmentReadingitemBinding;
import com.newsblur.databinding.IncludeReadingItemCommentBinding;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.service.OriginalTextService;
@ -54,14 +52,9 @@ import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StoryUtils;
import com.newsblur.util.UIUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.FlowLayout;
import com.newsblur.view.NewsblurWebview;
import com.newsblur.view.ReadingScrollView;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -76,24 +69,12 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
private LayoutInflater inflater;
private String feedColor, feedTitle, feedFade, feedBorder, feedIconUrl, faviconText;
private Classifier classifier;
@Bind(R.id.reading_webview) NewsblurWebview web;
@Bind(R.id.custom_view_container) ViewGroup webviewCustomViewLayout;
@Bind(R.id.reading_scrollview) ScrollView fragmentScrollview;
private BroadcastReceiver textSizeReceiver, readingFontReceiver;
@Bind(R.id.reading_item_title) TextView itemTitle;
@Bind(R.id.reading_item_authors) TextView itemAuthors;
@Bind(R.id.reading_feed_title) TextView itemFeed;
private boolean displayFeedDetails;
@Bind(R.id.reading_item_tags) FlowLayout tagContainer;
private View view;
private UserDetails user;
private DefaultFeedView selectedFeedView;
private boolean textViewUnavailable;
@Bind(R.id.reading_textloading) TextView textViewLoadingMsg;
@Bind(R.id.reading_textmodefailed) TextView textViewLoadingFailedMsg;
@Bind(R.id.save_story_button) Button saveButton;
@Bind(R.id.share_story_button) Button shareButton;
@Bind(R.id.story_context_menu_button) Button menuButton;
/** The story HTML, as provided by the 'content' element of the stories API. */
private String storyContent;
@ -115,6 +96,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
private final Object WEBVIEW_CONTENT_MUTEX = new Object();
private FragmentReadingitemBinding binding;
private IncludeReadingItemCommentBinding itemCommentBinding;
public static ReadingItemFragment newInstance(Story story, String feedTitle, String feedFaviconColor, String feedFaviconFade, String feedFaviconBorder, String faviconText, String faviconUrl, Classifier classifier, boolean displayFeedDetails, String sourceUserId) {
ReadingItemFragment readingFragment = new ReadingItemFragment();
@ -168,8 +152,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
int heightm = fragmentScrollview.getChildAt(0).getMeasuredHeight();
int pos = fragmentScrollview.getScrollY();
int heightm = binding.readingScrollview.getChildAt(0).getMeasuredHeight();
int pos = binding.readingScrollview.getScrollY();
outState.putFloat(BUNDLE_SCROLL_POS_REL, (((float)pos)/heightm));
}
@ -177,7 +161,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onDestroy() {
getActivity().unregisterReceiver(textSizeReceiver);
getActivity().unregisterReceiver(readingFontReceiver);
web.setOnTouchListener(null);
binding.readingWebview.setOnTouchListener(null);
view.setOnTouchListener(null);
getActivity().getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null);
super.onDestroy();
@ -187,7 +171,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
// state into the webview so it behaves.
@Override
public void onPause() {
if (this.web != null ) { this.web.onPause(); }
if (this.binding.readingWebview != null ) { this.binding.readingWebview.onPause(); }
super.onPause();
}
@ -195,25 +179,26 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onResume() {
super.onResume();
reloadStoryContent();
if (this.web != null ) { this.web.onResume(); }
if (this.binding.readingWebview != null ) { this.binding.readingWebview.onResume(); }
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.inflater = inflater;
view = inflater.inflate(R.layout.fragment_readingitem, null);
ButterKnife.bind(this, view);
binding = FragmentReadingitemBinding.bind(view);
itemCommentBinding = IncludeReadingItemCommentBinding.bind(binding.getRoot());
Reading activity = (Reading) getActivity();
fs = activity.getFeedSet();
selectedFeedView = PrefsUtils.getDefaultViewModeForFeed(activity, story.feedId);
registerForContextMenu(web);
web.setCustomViewLayout(webviewCustomViewLayout);
web.setWebviewWrapperLayout(fragmentScrollview);
web.setBackgroundColor(Color.TRANSPARENT);
web.fragment = this;
web.activity = activity;
registerForContextMenu(binding.readingWebview);
binding.readingWebview.setCustomViewLayout(binding.customViewContainer);
binding.readingWebview.setWebviewWrapperLayout(binding.readingScrollview);
binding.readingWebview.setBackgroundColor(Color.TRANSPARENT);
binding.readingWebview.fragment = this;
binding.readingWebview.activity = activity;
setupItemMetadata();
updateShareButton();
@ -228,6 +213,29 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.storyContextMenuButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onClickMenuButton();
}
});
itemCommentBinding.saveStoryButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clickSave();
}
});
itemCommentBinding.shareStoryButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clickShare();
}
});
}
private void setupImmersiveViewGestureDetector() {
// Change the system visibility on the decorview from the activity so that the state is maintained as we page through
// fragments
@ -239,7 +247,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
return gestureDetector.onTouchEvent(motionEvent);
}
};
web.setOnTouchListener(touchListener);
binding.readingWebview.setOnTouchListener(touchListener);
view.setOnTouchListener(touchListener);
getActivity().getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(immersiveViewHandler);
@ -247,7 +255,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
HitTestResult result = web.getHitTestResult();
HitTestResult result = binding.readingWebview.getHitTestResult();
if (result.getType() == HitTestResult.IMAGE_TYPE ||
result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE ) {
// if the long-pressed item was an image, see if we can pop up a little dialogue
@ -298,8 +306,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
}
@OnClick(R.id.story_context_menu_button) void onClickMenuButton() {
PopupMenu pm = new PopupMenu(getActivity(), menuButton);
private void onClickMenuButton() {
PopupMenu pm = new PopupMenu(getActivity(), binding.storyContextMenuButton);
Menu menu = pm.getMenu();
pm.getMenuInflater().inflate(R.menu.story_context, menu);
@ -313,6 +321,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
menu.findItem(R.id.menu_theme_dark).setChecked(true);
} else if (themeValue == ThemeValue.BLACK) {
menu.findItem(R.id.menu_theme_black).setChecked(true);
} else if (themeValue == ThemeValue.AUTO) {
menu.findItem(R.id.menu_theme_auto).setChecked(true);
}
pm.setOnMenuItemClickListener(this);
@ -360,6 +370,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
} else if (item.getItemId() == R.id.menu_reading_markunread) {
FeedUtils.markStoryUnread(story, getActivity());
return true;
} else if (item.getItemId() == R.id.menu_theme_auto) {
PrefsUtils.setSelectedTheme(getActivity(), ThemeValue.AUTO);
UIUtils.restartActivity(getActivity());
return true;
} else if (item.getItemId() == R.id.menu_theme_light) {
PrefsUtils.setSelectedTheme(getActivity(), ThemeValue.LIGHT);
UIUtils.restartActivity(getActivity());
@ -386,7 +400,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
}
@OnClick(R.id.save_story_button) void clickSave() {
private void clickSave() {
if (story.starred) {
FeedUtils.setStorySaved(story.storyHash, false, getActivity());
} else {
@ -395,24 +409,24 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
private void updateSaveButton() {
if (saveButton == null) return;
saveButton.setText(story.starred ? R.string.unsave_this : R.string.save_this);
if (itemCommentBinding.saveStoryButton == null) return;
itemCommentBinding.saveStoryButton.setText(story.starred ? R.string.unsave_this : R.string.save_this);
}
@OnClick(R.id.share_story_button) void clickShare() {
private void clickShare() {
DialogFragment newFragment = ShareDialogFragment.newInstance(story, sourceUserId);
newFragment.show(getFragmentManager(), "dialog");
}
private void updateShareButton() {
if (shareButton == null) return;
if (itemCommentBinding.shareStoryButton == null) return;
for (String userId : story.sharedUserIds) {
if (TextUtils.equals(userId, user.id)) {
shareButton.setText(R.string.already_shared);
itemCommentBinding.shareStoryButton.setText(R.string.already_shared);
return;
}
}
shareButton.setText(R.string.share_this);
itemCommentBinding.shareStoryButton.setText(R.string.share_this);
}
private void setupItemCommentsAndShares() {
@ -443,28 +457,28 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
feedHeaderBorder.setBackgroundColor(Color.parseColor("#" + feedBorder));
if (TextUtils.equals(faviconText, "black")) {
itemFeed.setTextColor(UIUtils.getColor(getActivity(), R.color.text));
itemFeed.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_white));
binding.readingFeedTitle.setTextColor(UIUtils.getColor(getActivity(), R.color.text));
binding.readingFeedTitle.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_white));
} else {
itemFeed.setTextColor(UIUtils.getColor(getActivity(), R.color.white));
itemFeed.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_black));
binding.readingFeedTitle.setTextColor(UIUtils.getColor(getActivity(), R.color.white));
binding.readingFeedTitle.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_black));
}
if (!displayFeedDetails) {
itemFeed.setVisibility(View.GONE);
binding.readingFeedTitle.setVisibility(View.GONE);
feedIcon.setVisibility(View.GONE);
} else {
FeedUtils.iconLoader.displayImage(feedIconUrl, feedIcon, 0, false);
itemFeed.setText(feedTitle);
binding.readingFeedTitle.setText(feedTitle);
}
itemDate.setText(StoryUtils.formatLongDate(getActivity(), story.timestamp));
if (story.tags.length <= 0) {
tagContainer.setVisibility(View.GONE);
binding.readingItemTags.setVisibility(View.GONE);
}
itemAuthors.setOnClickListener(new OnClickListener() {
binding.readingItemAuthors.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story.feedId.equals("0")) return; // cannot train on feedless stories
@ -473,7 +487,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
});
itemFeed.setOnClickListener(new OnClickListener() {
binding.readingFeedTitle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story.feedId.equals("0")) return; // cannot train on feedless stories
@ -482,7 +496,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
});
itemTitle.setOnClickListener(new OnClickListener() {
binding.readingItemTitle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW);
@ -506,7 +520,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
Drawable tag_green_background = UIUtils.getDrawable(getActivity(), R.drawable.tag_background_positive);
Drawable tag_red_background = UIUtils.getDrawable(getActivity(), R.drawable.tag_background_negative);
tagContainer.removeAllViews();
binding.readingItemTags.removeAllViews();
for (String tag : story.tags) {
// TODO: these textviews with compound images are buggy, but stubbed in to let colourblind users
// see what is going on. these should be replaced with proper Chips when the v28 Chip lib
@ -549,21 +563,21 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
});
}
tagContainer.addView(v);
binding.readingItemTags.addView(v);
}
if (!TextUtils.isEmpty(story.authors)) {
itemAuthors.setText("" + story.authors);
binding.readingItemAuthors.setText("" + story.authors);
if (classifier != null && classifier.authors.containsKey(story.authors)) {
switch (classifier.authors.get(story.authors)) {
case Classifier.LIKE:
itemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.positive));
binding.readingItemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.positive));
break;
case Classifier.DISLIKE:
itemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.negative));
binding.readingItemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.negative));
break;
default:
itemAuthors.setTextColor(UIUtils.getThemedColor(getActivity(), R.attr.readingItemMetadata, android.R.attr.textColor));
binding.readingItemAuthors.setTextColor(UIUtils.getThemedColor(getActivity(), R.attr.readingItemMetadata, android.R.attr.textColor));
break;
}
}
@ -571,7 +585,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
String title = story.title;
title = UIUtils.colourTitleFromClassifier(title, classifier);
itemTitle.setText(UIUtils.fromHtml(title));
binding.readingItemTitle.setText(UIUtils.fromHtml(title));
}
public void switchSelectedViewMode() {
@ -613,8 +627,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
private void reloadStoryContent() {
// reset indicators
textViewLoadingMsg.setVisibility(View.GONE);
textViewLoadingFailedMsg.setVisibility(View.GONE);
binding.readingTextloading.setVisibility(View.GONE);
binding.readingTextmodefailed.setVisibility(View.GONE);
enableProgress(false);
boolean needStoryContent = false;
@ -623,10 +637,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
needStoryContent = true;
} else {
if (textViewUnavailable) {
textViewLoadingFailedMsg.setVisibility(View.VISIBLE);
binding.readingTextmodefailed.setVisibility(View.VISIBLE);
needStoryContent = true;
} else if (originalText == null) {
textViewLoadingMsg.setVisibility(View.VISIBLE);
binding.readingTextloading.setVisibility(View.VISIBLE);
enableProgress(true);
loadOriginalText();
// still show the story mode version, as the text mode one may take some time
@ -782,11 +796,20 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"dark_reading.css\" />");
} else if (themeValue == ThemeValue.BLACK) {
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"black_reading.css\" />");
} else if (themeValue == ThemeValue.AUTO) {
int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"dark_reading.css\" />");
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_NO) {
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"light_reading.css\" />");
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_UNDEFINED) {
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"light_reading.css\" />");
}
}
builder.append("</head><body><div class=\"NB-story\">");
builder.append(storyText);
builder.append("</div></body></html>");
web.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "UTF-8", null);
binding.readingWebview.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "UTF-8", null);
}
}
@ -887,10 +910,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
// insufficient time to allow the WebView to actually finish internally computing state and size.
// an additional fixed delay is added in a last ditch attempt to give the black-box platform
// threads a chance to finish their work.
fragmentScrollview.postDelayed(new Runnable() {
binding.readingScrollview.postDelayed(new Runnable() {
public void run() {
int relPos = Math.round(fragmentScrollview.getChildAt(0).getMeasuredHeight() * savedScrollPosRel);
fragmentScrollview.scrollTo(0, relPos);
int relPos = Math.round(binding.readingScrollview.getChildAt(0).getMeasuredHeight() * savedScrollPosRel);
binding.readingScrollview.scrollTo(0, relPos);
}
}, 75L);
}
@ -903,7 +926,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
private class TextSizeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
web.setTextSize(intent.getFloatExtra(TEXT_SIZE_VALUE, 1.0f));
binding.readingWebview.setTextSize(intent.getFloatExtra(TEXT_SIZE_VALUE, 1.0f));
}
}
@ -924,7 +947,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (web.wasLinkClicked()) {
if (binding.readingWebview.wasLinkClicked()) {
// Clicked a link so ignore immersive view
return super.onSingleTapUp(e);
}

View file

@ -1,16 +1,13 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.activity.Reading;
import com.newsblur.databinding.FragmentReadingpagerBinding;
/*
* A fragment to hold the story pager. Eventually this fragment should hold much of the UI and logic
@ -21,8 +18,6 @@ import com.newsblur.activity.Reading;
*/
public class ReadingPagerFragment extends NbFragment {
@Bind(R.id.reading_pager) ViewPager pager;
public static ReadingPagerFragment newInstance() {
ReadingPagerFragment fragment = new ReadingPagerFragment();
Bundle arguments = new Bundle();
@ -33,12 +28,12 @@ public class ReadingPagerFragment extends NbFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_readingpager, null);
ButterKnife.bind(this, v);
FragmentReadingpagerBinding binding = FragmentReadingpagerBinding.bind(v);
Reading activity = ((Reading) getActivity());
pager.addOnPageChangeListener(activity);
activity.offerPager(pager, getChildFragmentManager());
binding.readingPager.addOnPageChangeListener(activity);
activity.offerPager(binding.readingPager, getChildFragmentManager());
return v;
}

View file

@ -3,23 +3,19 @@ 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 android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ViewSwitcher;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.AddSocial;
import com.newsblur.activity.Login;
import com.newsblur.databinding.FragmentRegisterprogressBinding;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.RegisterResponse;
@ -31,9 +27,7 @@ public class RegisterProgressFragment extends Fragment {
private String password;
private String email;
private RegisterTask registerTask;
@Bind(R.id.register_viewswitcher) ViewSwitcher switcher;
@Bind(R.id.registering_next_1) Button next;
@Bind(R.id.registerprogress_logo) ImageView registerProgressLogo;
private FragmentRegisterprogressBinding binding;
public static RegisterProgressFragment getInstance(String username, String password, String email) {
RegisterProgressFragment fragment = new RegisterProgressFragment();
@ -59,12 +53,12 @@ public class RegisterProgressFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_registerprogress, null);
ButterKnife.bind(this, v);
binding = FragmentRegisterprogressBinding.bind(v);
registerProgressLogo.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate));
binding.registerprogressLogo.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate));
if (registerTask != null) {
switcher.showNext();
binding.registerViewswitcher.showNext();
} else {
registerTask = new RegisterTask();
registerTask.execute();
@ -73,7 +67,18 @@ public class RegisterProgressFragment extends Fragment {
return v;
}
@OnClick(R.id.registering_next_1) void next() {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.registeringNext1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
next();
}
});
}
private void next() {
Intent i = new Intent(getActivity(), AddSocial.class);
startActivity(i);
}
@ -88,7 +93,7 @@ public class RegisterProgressFragment extends Fragment {
@Override
protected void onPostExecute(RegisterResponse response) {
if (response.authenticated) {
switcher.showNext();
binding.registerViewswitcher.showNext();
} else {
String errorMessage = response.getErrorMessage();
if(errorMessage == null) {

View file

@ -0,0 +1,106 @@
package com.newsblur.fragment;
import android.app.Activity;
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 android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.databinding.DialogRenameBinding;
import com.newsblur.domain.Feed;
import com.newsblur.network.APIManager;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
public class RenameDialogFragment extends DialogFragment {
private static final String FEED = "feed";
private static final String FOLDER = "folder";
private static final String FOLDER_NAME = "folder_name";
private static final String FOLDER_PARENT = "folder_parent";
private static final String RENAME_TYPE = "rename_type";
public static RenameDialogFragment newInstance(Feed feed) {
RenameDialogFragment fragment = new RenameDialogFragment();
Bundle args = new Bundle();
args.putSerializable(FEED, feed);
args.putString(RENAME_TYPE, FEED);
fragment.setArguments(args);
return fragment;
}
public static RenameDialogFragment newInstance(String folderName, String folderParent) {
RenameDialogFragment fragment = new RenameDialogFragment();
Bundle args = new Bundle();
args.putString(FOLDER_NAME, folderName);
args.putString(FOLDER_PARENT, folderParent);
args.putString(RENAME_TYPE, FOLDER);
fragment.setArguments(args);
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_rename, null);
final DialogRenameBinding binding = DialogRenameBinding.bind(v);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(v);
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
RenameDialogFragment.this.dismiss();
}
});
if (getArguments().getString(RENAME_TYPE).equals(FEED)) {
final Feed feed = (Feed) getArguments().getSerializable(FEED);
builder.setTitle(String.format(getResources().getString(R.string.title_rename_feed), feed.title));
binding.inputName.setText(feed.title);
builder.setPositiveButton(R.string.feed_name_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.renameFeed(activity, feed.feedId, binding.inputName.getText().toString());
RenameDialogFragment.this.dismiss();
}
});
} else { // FOLDER
final String folderName = getArguments().getString(FOLDER_NAME);
final String folderParentName = getArguments().getString(FOLDER_PARENT);
builder.setTitle(String.format(getResources().getString(R.string.title_rename_folder), folderName));
binding.inputName.setText(folderName);
builder.setPositiveButton(R.string.folder_name_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String newFolderName = binding.inputName.getText().toString().toUpperCase();
if (TextUtils.isEmpty(newFolderName)) {
Toast.makeText(activity, R.string.add_folder_name, Toast.LENGTH_SHORT).show();
return;
}
String inFolder = "";
if (!TextUtils.isEmpty(folderParentName) && !folderParentName.equals(AppConstants.ROOT_FOLDER)) {
inFolder = folderParentName;
}
FeedUtils.renameFolder(folderName, newFolderName, inFolder, activity, new APIManager(activity));
RenameDialogFragment.this.dismiss();
}
});
}
return builder.create();
}
}

View file

@ -1,69 +0,0 @@
package com.newsblur.fragment;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.util.FeedUtils;
public class RenameFeedFragment extends DialogFragment {
private Feed feed;
@Bind(R.id.feed_name_field) EditText feedNameView;
public static RenameFeedFragment newInstance(Feed feed) {
RenameFeedFragment fragment = new RenameFeedFragment();
Bundle args = new Bundle();
args.putSerializable("feed", feed);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
feed = (Feed) getArguments().getSerializable("feed");
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_rename_feed, null);
ButterKnife.bind(this, v);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(String.format(getResources().getString(R.string.title_rename_feed), feed.title));
builder.setView(v);
feedNameView.setText(feed.title);
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
RenameFeedFragment.this.dismiss();
}
});
builder.setPositiveButton(R.string.feed_name_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.renameFeed(activity, feed.feedId, feedNameView.getText().toString());
RenameFeedFragment.this.dismiss();
}
});
Dialog dialog = builder.create();
return dialog;
}
}

View file

@ -0,0 +1,48 @@
package com.newsblur.fragment;
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 com.newsblur.R;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
public class SaveSearchFragment extends DialogFragment {
private static final String FEED_ID = "feed_id";
private static final String QUERY = "query";
public static SaveSearchFragment newInstance(String feedId, String query) {
SaveSearchFragment frag = new SaveSearchFragment();
Bundle args = new Bundle();
args.putString(FEED_ID, feedId);
args.putString(QUERY, query);
frag.setArguments(args);
return frag;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(String.format(getResources().getString(R.string.add_saved_search_message), getArguments().getString(QUERY)));
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.saveSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity(), new APIManager(getActivity()));
SaveSearchFragment.this.dismiss();
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
SaveSearchFragment.this.dismiss();
}
});
return builder.create();
}
}

View file

@ -31,6 +31,7 @@ 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;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.FlowLayout;
@ -96,7 +97,7 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
View commentView = inflater.inflate(R.layout.include_comment, null);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setText(comment.commentText);
commentText.setText(UIUtils.fromHtml(comment.commentText));
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
@ -161,7 +162,7 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
for (final Reply reply : replies) {
View replyView = inflater.inflate(R.layout.include_reply, null);
TextView replyText = (TextView) replyView.findViewById(R.id.reply_text);
replyText.setText(reply.text);
replyText.setText(UIUtils.fromHtml(reply.text));
ImageView replyImage = (ImageView) replyView.findViewById(R.id.reply_user_image);
final UserProfile replyUser = FeedUtils.dbHelper.getUserProfile(reply.userId);

View file

@ -14,20 +14,15 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.databinding.DialogTrainstoryBinding;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
import com.newsblur.view.SelectOnlyEditText;
public class StoryIntelTrainerFragment extends DialogFragment {
@ -35,17 +30,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
private FeedSet fs;
private Classifier classifier;
private Integer newTitleTraining;
@Bind(R.id.intel_tag_header) TextView headerTags;
@Bind(R.id.intel_author_header) TextView headerAuthor;
@Bind(R.id.intel_title_selection) SelectOnlyEditText titleSelection;
@Bind(R.id.intel_title_like) Button titleLikeButton;
@Bind(R.id.intel_title_dislike) Button titleDislikeButton;
@Bind(R.id.intel_title_clear) Button titleClearButton;
@Bind(R.id.existing_title_intel_container) LinearLayout titleRowsContainer;
@Bind(R.id.existing_tag_intel_container) LinearLayout tagRowsContainer;
@Bind(R.id.existing_author_intel_container) LinearLayout authorRowsContainer;
@Bind(R.id.existing_feed_intel_container) LinearLayout feedRowsContainer;
private DialogTrainstoryBinding binding;
public static StoryIntelTrainerFragment newInstance(Story story, FeedSet fs) {
if (story.feedId.equals("0")) {
@ -69,44 +54,44 @@ public class StoryIntelTrainerFragment extends DialogFragment {
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_trainstory, null);
ButterKnife.bind(this, v);
binding = DialogTrainstoryBinding.bind(v);
// set up the special title training box for the title from this story and the associated buttons
titleSelection.setText(story.title);
binding.intelTitleSelection.setText(story.title);
// the layout sets inputType="none" on this EditText, but a widespread platform bug requires us
// to also set this programmatically to make the field read-only for selection.
titleSelection.setInputType(InputType.TYPE_NULL);
binding.intelTitleSelection.setInputType(InputType.TYPE_NULL);
// the user is selecting for our custom widget, not to copy/paste
titleSelection.disableActionMenu();
binding.intelTitleSelection.disableActionMenu();
// pre-select the whole title to make it easier for the user to manipulate the selection handles
titleSelection.selectAll();
binding.intelTitleSelection.selectAll();
// do this after init and selection to prevent toast spam
titleSelection.setForceSelection(true);
binding.intelTitleSelection.setForceSelection(true);
// the disposition buttons for a new title training don't immediately impact the classifier object,
// lest the user want to change selection substring after choosing the disposition. so just store
// the training factor in a variable that can be pulled on completion
titleLikeButton.setOnClickListener(new OnClickListener() {
binding.intelTitleLike.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
newTitleTraining = Classifier.LIKE;
titleLikeButton.setBackgroundResource(R.drawable.ic_like_active);
titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_gray55);
binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_active);
binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_gray55);
}
});
titleDislikeButton.setOnClickListener(new OnClickListener() {
binding.intelTitleDislike.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
newTitleTraining = Classifier.DISLIKE;
titleLikeButton.setBackgroundResource(R.drawable.ic_like_gray55);
titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_active);
binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_gray55);
binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_active);
}
});
titleClearButton.setOnClickListener(new OnClickListener() {
binding.intelTitleClear.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
newTitleTraining = null;
titleLikeButton.setBackgroundResource(R.drawable.ic_like_gray55);
titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_gray55);
binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_gray55);
binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_gray55);
}
});
@ -117,7 +102,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
label.setText(rule.getKey());
UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey());
titleRowsContainer.addView(row);
binding.existingTitleIntelContainer.addView(row);
}
}
@ -127,9 +112,9 @@ public class StoryIntelTrainerFragment extends DialogFragment {
TextView label = (TextView) row.findViewById(R.id.intel_row_label);
label.setText(tag);
UIUtils.setupIntelDialogRow(row, classifier.tags, tag);
tagRowsContainer.addView(row);
binding.existingTagIntelContainer.addView(row);
}
if (story.tags.length < 1) headerTags.setVisibility(View.GONE);
if (story.tags.length < 1) binding.intelTagHeader.setVisibility(View.GONE);
// there is a single author per story
if (!TextUtils.isEmpty(story.authors)) {
@ -137,9 +122,9 @@ public class StoryIntelTrainerFragment extends DialogFragment {
TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label);
labelAuthor.setText(story.authors);
UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, story.authors);
authorRowsContainer.addView(rowAuthor);
binding.existingAuthorIntelContainer.addView(rowAuthor);
} else {
headerAuthor.setVisibility(View.GONE);
binding.intelAuthorHeader.setVisibility(View.GONE);
}
// there is a single feed to be trained, but it is a bit odd in that the label is the title and
@ -148,7 +133,7 @@ public class StoryIntelTrainerFragment extends DialogFragment {
TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label);
labelFeed.setText(FeedUtils.getFeedTitle(story.feedId));
UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, story.feedId);
feedRowsContainer.addView(rowFeed);
binding.existingFeedIntelContainer.addView(rowFeed);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.story_intel_dialog_title);
@ -163,8 +148,8 @@ public class StoryIntelTrainerFragment extends DialogFragment {
builder.setPositiveButton(R.string.dialog_story_intel_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if ((newTitleTraining != null) && (!TextUtils.isEmpty(titleSelection.getSelection()))) {
classifier.title.put(titleSelection.getSelection(), newTitleTraining);
if ((newTitleTraining != null) && (!TextUtils.isEmpty(binding.intelTitleSelection.getSelection()))) {
classifier.title.put(binding.intelTitleSelection.getSelection(), newTitleTraining);
}
FeedUtils.updateClassifier(story.feedId, classifier, fs, activity);
StoryIntelTrainerFragment.this.dismiss();

View file

@ -7,13 +7,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.databinding.StoryorderDialogBinding;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.StoryOrderChangedListener;
@ -21,8 +17,6 @@ public class StoryOrderDialogFragment extends DialogFragment {
private static String CURRENT_ORDER = "currentOrder";
private StoryOrder currentValue;
@Bind(R.id.radio_newest) RadioButton newestButton;
@Bind(R.id.radio_oldest) RadioButton oldestButton;
public static StoryOrderDialogFragment newInstance(StoryOrder currentValue) {
StoryOrderDialogFragment dialog = new StoryOrderDialogFragment();
@ -42,25 +36,38 @@ public class StoryOrderDialogFragment extends DialogFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = (StoryOrder) getArguments().getSerializable(CURRENT_ORDER);
View v = inflater.inflate(R.layout.storyorder_dialog, null);
ButterKnife.bind(this, v);
StoryorderDialogBinding binding = StoryorderDialogBinding.bind(v);
newestButton.setChecked(currentValue == StoryOrder.NEWEST);
oldestButton.setChecked(currentValue == StoryOrder.OLDEST);
binding.radioNewest.setChecked(currentValue == StoryOrder.NEWEST);
binding.radioOldest.setChecked(currentValue == StoryOrder.OLDEST);
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM;
binding.radioNewest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectNewest();
}
});
binding.radioOldest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectOldest();
}
});
return v;
}
@OnClick(R.id.radio_newest) void selectNewest() {
private void selectNewest() {
if (currentValue != StoryOrder.NEWEST) {
((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.NEWEST);
}
dismiss();
}
@OnClick(R.id.radio_oldest) void selectOldest() {
private void selectOldest() {
if (currentValue != StoryOrder.OLDEST) {
((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.OLDEST);
}

View file

@ -71,6 +71,11 @@ public class APIConstants {
public static final String PATH_SET_NOTIFICATIONS = "/notifications/feed/";
public static final String PATH_INSTA_FETCH = "/rss_feeds/exception_retry";
public static final String PATH_RENAME_FEED = "/reader/rename_feed";
public static final String PATH_DELETE_SEARCH = "/reader/delete_search";
public static final String PATH_SAVE_SEARCH = "/reader/save_search";
public static final String PATH_ADD_FOLDER = "/reader/add_folder";
public static final String PATH_DELETE_FOLDER = "/reader/delete_folder";
public static final String PATH_RENAME_FOLDER = "/reader/rename_folder";
public static String buildUrl(String path) {
return CurrentUrlBase + path;
@ -121,6 +126,9 @@ public class APIConstants {
public static final String PARAMETER_RESET_FETCH = "reset_fetch";
public static final String PARAMETER_INFREQUENT = "infrequent";
public static final String PARAMETER_FEEDTITLE = "feed_title";
public static final String PARAMETER_FOLDER_TO_DELETE = "folder_to_delete";
public static final String PARAMETER_FOLDER_TO_RENAME = "folder_to_rename";
public static final String PARAMETER_NEW_FOLDER_NAME = "new_folder_name";
public static final String VALUE_PREFIX_SOCIAL = "social:";
public static final String VALUE_ALLSOCIAL = "river:blurblogs"; // the magic value passed to the mark-read API for all social feeds

View file

@ -15,6 +15,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
@ -550,11 +551,21 @@ public class APIManager {
return (CommentResponse) response.getResponse(gson, CommentResponse.class);
}
public AddFeedResponse addFeed(String feedUrl) {
public NewsBlurResponse addFolder(String folderName) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FOLDER, folderName);
APIResponse response = post(buildUrl(APIConstants.PATH_ADD_FOLDER), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public AddFeedResponse addFeed(String feedUrl, @Nullable String folderName) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_URL, feedUrl);
if (!TextUtils.isEmpty(folderName) && !folderName.equals(AppConstants.ROOT_FOLDER)) {
values.put(APIConstants.PARAMETER_FOLDER, folderName);
}
APIResponse response = post(buildUrl(APIConstants.PATH_ADD_FEED), values);
return (AddFeedResponse) response.getResponse(gson, AddFeedResponse.class);
return response.getResponse(gson, AddFeedResponse.class);
}
public FeedResult[] searchForFeed(String searchTerm) {
@ -579,6 +590,30 @@ public class APIManager {
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse deleteFolder(String folderName, String inFolder) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FOLDER_TO_DELETE, folderName);
values.put(APIConstants.PARAMETER_IN_FOLDER, inFolder);
APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_FOLDER), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse deleteSearch(String feedId, String query) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_QUERY, query);
APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_SEARCH), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse saveSearch(String feedId, String query) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_QUERY, query);
APIResponse response = post(buildUrl(APIConstants.PATH_SAVE_SEARCH), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse saveFeedChooser(Set<String> feeds) {
ValueMultimap values = new ValueMultimap();
for (String feed : feeds) {
@ -617,6 +652,15 @@ public class APIManager {
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse renameFolder(String folderName, String newFolderName, String inFolder) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FOLDER_TO_RENAME, folderName);
values.put(APIConstants.PARAMETER_NEW_FOLDER_NAME, newFolderName);
values.put(APIConstants.PARAMETER_IN_FOLDER, inFolder);
APIResponse response = post(buildUrl(APIConstants.PATH_RENAME_FOLDER), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
/* HTTP METHODS */
private APIResponse get(final String urlString) {

View file

@ -15,6 +15,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.StarredCount;
import com.newsblur.util.AppConstants;
@ -30,6 +31,7 @@ public class FeedFolderResponse {
public Set<Feed> feeds;
public Set<SocialFeed> socialFeeds;
public Set<StarredCount> starredCounts;
public Set<SavedSearch> savedSearches;
public boolean isAuthenticated;
public boolean isPremium;
@ -109,6 +111,16 @@ public class FeedFolderResponse {
}
}
savedSearches = new HashSet<>();
JsonArray savedSearchesArray = (JsonArray) asJsonObject.get("saved_searches");
if (savedSearchesArray != null) {
for (int i=0; i<savedSearchesArray.size(); i++) {
JsonElement jsonElement = savedSearchesArray.get(i);
SavedSearch savedSearch = gson.fromJson(jsonElement, SavedSearch.class);
savedSearches.add(savedSearch);
}
}
parseTime = System.currentTimeMillis() - startTime;
}

View file

@ -24,6 +24,7 @@ public class StoryTypeAdapter implements JsonDeserializer<Story> {
// any characters we don't want in the short description, such as newlines or placeholders
private final static Pattern ShortContentExcludes = Pattern.compile("[\\uFFFC\\u000A\\u000B\\u000C\\u000D]");
private final static Pattern httpSniff = Pattern.compile("(?:http):\\/\\/");
public StoryTypeAdapter() {
this.gson = new GsonBuilder()
@ -40,6 +41,14 @@ public class StoryTypeAdapter implements JsonDeserializer<Story> {
// Convert story_timestamp to milliseconds
story.timestamp = story.timestamp * 1000;
// replace http image urls with https
if (httpSniff.matcher(story.content).find() && story.secureImageUrls != null && story.secureImageUrls.size() > 0) {
for (String httpUrl : story.secureImageUrls.keySet()) {
String httpsUrl = story.secureImageUrls.get(httpUrl);
story.content = story.content.replace(httpUrl, httpsUrl);
}
}
// populate the shortContent field
if (story.content != null) {
CharSequence parsed = UIUtils.fromHtml(story.content);

View file

@ -16,6 +16,7 @@ import static com.newsblur.database.BlurDatabaseHelper.closeQuietly;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.StarredCount;
import com.newsblur.domain.Story;
@ -586,6 +587,12 @@ public class NBSyncService extends JobService {
for (StarredCount sc : feedResponse.starredCounts) {
starredCountValues.add(sc.getValues());
}
// saved searches table
List<ContentValues> savedSearchesValues = new ArrayList<>();
for (SavedSearch savedSearch : feedResponse.savedSearches) {
savedSearchesValues.add(savedSearch.getValues());
}
// the API vends the starred total as a different element, roll it into
// the starred counts table using a special tag
StarredCount totalStarred = new StarredCount();
@ -593,7 +600,7 @@ public class NBSyncService extends JobService {
totalStarred.tag = StarredCount.TOTAL_STARRED;
starredCountValues.add(totalStarred.getValues());
dbHelper.setFeedsFolders(folderValues, feedValues, socialFeedValues, starredCountValues);
dbHelper.setFeedsFolders(folderValues, feedValues, socialFeedValues, starredCountValues, savedSearchesValues);
lastFFWriteMillis = System.currentTimeMillis() - startTime;
lastFeedCount = feedValues.size();

View file

@ -0,0 +1,25 @@
package com.newsblur.util;
public enum DefaultBrowser {
SYSTEM_DEFAULT,
IN_APP_BROWSER,
CHROME,
FIREFOX,
OPERA_MINI;
public static DefaultBrowser getDefaultBrowser(String preferenceValue) {
switch (preferenceValue) {
case "IN_APP_BROWSER":
return IN_APP_BROWSER;
case "CHROME":
return CHROME;
case "FIREFOX":
return FIREFOX;
case "OPERA_MINI":
return OPERA_MINI;
case "SYSTEM_DEFAULT":
default:
return SYSTEM_DEFAULT;
}
}
}

View file

@ -34,6 +34,7 @@ public class FeedSet implements Serializable {
private String folderName;
private String searchQuery;
private String searchFeedId;
private boolean isFilterSaved = false;
private boolean muted = false;
@ -75,17 +76,6 @@ public class FeedSet implements Serializable {
return fs;
}
/**
* Convenience constructor for multiple feeds with IDs
*/
public static FeedSet multipleFeeds(Set<String> feedIds) {
FeedSet fs = new FeedSet();
fs.feeds = new HashSet<>(feedIds.size());
fs.feeds.addAll(feedIds);
fs.feeds = Collections.unmodifiableSet(fs.feeds);
return fs;
}
/**
* Convenience constructor for all (non-social) feeds.
*/
@ -133,6 +123,16 @@ public class FeedSet implements Serializable {
return fs;
}
/**
* Convenience constructor for a single saved search.
*/
public static FeedSet singleSavedSearch(String feedId, String searchQuery) {
FeedSet fs = new FeedSet();
fs.searchQuery = searchQuery;
fs.searchFeedId = feedId;
return fs;
}
/**
* Convenience constructor for global shared stories feed.
*/
@ -281,6 +281,10 @@ public class FeedSet implements Serializable {
return this.searchQuery;
}
public String getSearchFeedId() {
return this.searchFeedId;
}
public void setFilterSaved(boolean isFilterSaved) {
this.isFilterSaved = isFilterSaved;
}

View file

@ -9,6 +9,7 @@ import java.util.Set;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.newsblur.R;
@ -18,6 +19,7 @@ import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.StarredCount;
import com.newsblur.domain.Story;
import com.newsblur.fragment.ReadingActionConfirmationFragment;
import com.newsblur.network.APIManager;
@ -109,6 +111,40 @@ public class FeedUtils {
}.execute();
}
public static void deleteSavedSearch(final String feedId, final String query, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.deleteSearch(feedId, query);
}
@Override
protected void onPostExecute(NewsBlurResponse newsBlurResponse) {
if (!newsBlurResponse.isError()) {
dbHelper.deleteSavedSearch(feedId, query);
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
}
}
}.execute();
}
public static void saveSearch(final String feedId, final String query, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.saveSearch(feedId, query);
}
@Override
protected void onPostExecute(NewsBlurResponse newsBlurResponse) {
if (!newsBlurResponse.isError()) {
NBSyncService.forceFeedsFolders();
triggerSync(context);
}
}
}.execute();
}
public static void deleteFeed(final String feedId, final String folderName, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
@ -140,6 +176,42 @@ public class FeedUtils {
}.execute();
}
public static void deleteFolder(final String folderName, final String inFolder, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.deleteFolder(folderName, inFolder);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
super.onPostExecute(result);
if (!result.isError()) {
NBSyncService.forceFeedsFolders();
triggerSync(context);
}
}
}.execute();
}
public static void renameFolder(final String folderName, final String newFolderName, final String inFolder, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... voids) {
return apiManager.renameFolder(folderName, newFolderName, inFolder);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
super.onPostExecute(result);
if (!result.isError()) {
NBSyncService.forceFeedsFolders();
triggerSync(context);
}
}
}.execute();
}
public static void markStoryUnread(final Story story, final Context context) {
new AsyncTask<Void, Void, Void>() {
@Override
@ -518,4 +590,8 @@ public class FeedUtils {
return dbHelper.getSocialFeed(feedId);
}
@Nullable
public static StarredCount getStarredFeedByTag(String feedId) {
return dbHelper.getStarredFeedByTag(feedId);
}
}

View file

@ -51,6 +51,8 @@ public class PrefConstants {
public static final String FEED_DEFAULT_FEED_VIEW_PREFIX = "feed_default_feed_view_";
public static final String DEFAULT_BROWSER = "default_browser";
public static final String READ_STORIES_FOLDER_NAME = "read_stories";
public static final String SAVED_STORIES_FOLDER_NAME = "saved_stories";
public static final String READING_ENTER_IMMERSIVE_SINGLE_TAP = "immersive_enter_single_tap";
@ -85,6 +87,7 @@ public class PrefConstants {
public static final String THEME = "theme";
public enum ThemeValue {
AUTO,
LIGHT,
DARK,
BLACK;

View file

@ -14,7 +14,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@ -777,6 +781,15 @@ public class PrefsUtils {
activity.setTheme(R.style.NewsBlurDarkTheme);
} else if (value == ThemeValue.BLACK) {
activity.setTheme(R.style.NewsBlurBlackTheme);
} else if (value == ThemeValue.AUTO) {
int nightModeFlags = activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
activity.setTheme(R.style.NewsBlurDarkTheme);
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_NO) {
activity.setTheme(R.style.NewsBlurTheme);
} else if (nightModeFlags == Configuration.UI_MODE_NIGHT_UNDEFINED) {
activity.setTheme(R.style.NewsBlurTheme);
}
}
}
@ -945,4 +958,13 @@ public class PrefsUtils {
editor.putString(PrefConstants.WIDGET_BACKGROUND, widgetBackground.toString());
editor.commit();
}
public static DefaultBrowser getDefaultBrowser(Context context) {
return DefaultBrowser.getDefaultBrowser(getDefaultBrowserString(context));
}
public static String getDefaultBrowserString(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return preferences.getString(PrefConstants.DEFAULT_BROWSER, DefaultBrowser.SYSTEM_DEFAULT.toString());
}
}

View file

@ -1,5 +1,6 @@
package com.newsblur.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@ -16,8 +17,11 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import com.newsblur.activity.InAppBrowser;
import com.newsblur.activity.Reading;
import com.newsblur.fragment.ReadingItemFragment;
import com.newsblur.util.DefaultBrowser;
import com.newsblur.util.PrefsUtils;
public class NewsblurWebview extends WebView {
@ -88,21 +92,15 @@ public class NewsblurWebview extends WebView {
class NewsblurWebViewClient extends WebViewClient {
@Override
// this was deprecated in API 24 but the replacement only added in the same release.
// the suppression can be removed when we move past 24
@SuppressWarnings("deprecation")
// as of v43.0.2357.121 of the system WebView, links no longer open in the user's chosen
// browser, but open in-app. Override the default behaviour so it works as expected on
// all devices.
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
try {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(uri);
context.startActivity(i);
} catch (Exception e) {
com.newsblur.util.Log.e(this.getClass().getName(), "device cannot open URLs");
handleUri(Uri.parse(url));
return true;
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
handleUri(request.getUrl());
return true;
}
@ -115,6 +113,46 @@ public class NewsblurWebview extends WebView {
}
}
private void handleUri(Uri uri) {
DefaultBrowser defaultBrowser = PrefsUtils.getDefaultBrowser(context);
if (defaultBrowser == DefaultBrowser.SYSTEM_DEFAULT) {
openSystemDefaultBrowser(uri);
} else if (defaultBrowser == DefaultBrowser.IN_APP_BROWSER) {
Intent intent = new Intent(context, InAppBrowser.class);
intent.putExtra(InAppBrowser.URI, uri);
context.startActivity(intent);
} else if (defaultBrowser == DefaultBrowser.CHROME) {
openExternalBrowserApp(uri, "com.android.chrome");
} else if (defaultBrowser == DefaultBrowser.FIREFOX) {
openExternalBrowserApp(uri, "org.mozilla.firefox");
} else if (defaultBrowser == DefaultBrowser.OPERA_MINI) {
openExternalBrowserApp(uri, "com.opera.mini.native");
}
}
private void openSystemDefaultBrowser(Uri uri) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
context.startActivity(intent);
} catch (Exception e) {
com.newsblur.util.Log.e(this.getClass().getName(), "device cannot open URLs");
}
}
private void openExternalBrowserApp(Uri uri, String packageName) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
intent.setPackage(packageName);
context.startActivity(intent);
} catch (Exception e) {
com.newsblur.util.Log.e(this.getClass().getName(), "apps not available to open URLs");
// fallback to system default if apps cannot be opened
openSystemDefaultBrowser(uri);
}
}
// this WCC implements the bare minimum callbacks to get HTML5 fullscreen video working
class NewsblurWebChromeClient extends WebChromeClient {
public View customView;

View file

@ -5,14 +5,10 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import butterknife.ButterKnife;
import butterknife.Bind;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.databinding.StateToggleBinding;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
@ -27,52 +23,53 @@ public class StateToggleButton extends LinearLayout {
private int parentWidthPX = 0;
@Bind(R.id.toggle_all) ViewGroup allButton;
@Bind(R.id.toggle_all_icon) View allButtonIcon;
@Bind(R.id.toggle_all_text) View allButtonText;
@Bind(R.id.toggle_some) ViewGroup someButton;
@Bind(R.id.toggle_some_icon) View someButtonIcon;
@Bind(R.id.toggle_some_text) View someButtonText;
@Bind(R.id.toggle_focus) ViewGroup focusButton;
@Bind(R.id.toggle_focus_icon) View focusButtonIcon;
@Bind(R.id.toggle_focus_text) View focusButtonText;
@Bind(R.id.toggle_saved) ViewGroup savedButton;
@Bind(R.id.toggle_saved_icon) View savedButtonIcon;
@Bind(R.id.toggle_saved_text) View savedButtonText;
private StateToggleBinding binding;
public StateToggleButton(Context context, AttributeSet art) {
super(context, art);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.state_toggle, this);
ButterKnife.bind(this, view);
binding = StateToggleBinding.bind(view);
// smooth layout transitions are enabled in our layout XML; this smooths out toggle
// transitions on newer devices
allButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
someButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
focusButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
savedButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
binding.toggleAll.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
binding.toggleSome.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
binding.toggleFocus.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
binding.toggleSaved.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
setState(state);
binding.toggleAll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setState(StateFilter.ALL);
}
});
binding.toggleSome.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setState(StateFilter.SOME);
}
});
binding.toggleFocus.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setState(StateFilter.BEST);
}
});
binding.toggleSaved.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setState(StateFilter.SAVED);
}
});
}
public void setStateListener(final StateChangedListener stateChangedListener) {
this.stateChangedListener = stateChangedListener;
}
@OnClick({R.id.toggle_all, R.id.toggle_some, R.id.toggle_focus, R.id.toggle_saved})
public void onClickToggle(View v) {
if (v.getId() == R.id.toggle_all) {
setState(StateFilter.ALL);
} else if (v.getId() == R.id.toggle_some) {
setState(StateFilter.SOME);
} else if (v.getId() == R.id.toggle_focus) {
setState(StateFilter.BEST);
} else if (v.getId() == R.id.toggle_saved) {
setState(StateFilter.SAVED);
}
}
public void setState(StateFilter state) {
this.state = state;
updateButtonStates();
@ -93,21 +90,21 @@ public class StateToggleButton extends LinearLayout {
if (widthDP > COLLAPSE_WIDTH_DP) compactMode = false;
}
allButtonText.setVisibility((!compactMode || state == StateFilter.ALL) ? View.VISIBLE : View.GONE);
allButton.setEnabled(state != StateFilter.ALL);
allButtonIcon.setAlpha(state == StateFilter.ALL ? 1.0f : 0.6f);
binding.toggleAllText.setVisibility((!compactMode || state == StateFilter.ALL) ? View.VISIBLE : View.GONE);
binding.toggleAll.setEnabled(state != StateFilter.ALL);
binding.toggleAllIcon.setAlpha(state == StateFilter.ALL ? 1.0f : 0.6f);
someButtonText.setVisibility((!compactMode || state == StateFilter.SOME) ? View.VISIBLE : View.GONE);
someButton.setEnabled(state != StateFilter.SOME);
someButtonIcon.setAlpha(state == StateFilter.SOME ? 1.0f : 0.6f);
binding.toggleSomeText.setVisibility((!compactMode || state == StateFilter.SOME) ? View.VISIBLE : View.GONE);
binding.toggleSome.setEnabled(state != StateFilter.SOME);
binding.toggleSomeIcon.setAlpha(state == StateFilter.SOME ? 1.0f : 0.6f);
focusButtonText.setVisibility((!compactMode || state == StateFilter.BEST) ? View.VISIBLE : View.GONE);
focusButton.setEnabled(state != StateFilter.BEST);
focusButtonIcon.setAlpha(state == StateFilter.BEST ? 1.0f : 0.6f);
binding.toggleFocusText.setVisibility((!compactMode || state == StateFilter.BEST) ? View.VISIBLE : View.GONE);
binding.toggleFocus.setEnabled(state != StateFilter.BEST);
binding.toggleFocusIcon.setAlpha(state == StateFilter.BEST ? 1.0f : 0.6f);
savedButtonText.setVisibility((!compactMode || state == StateFilter.SAVED) ? View.VISIBLE : View.GONE);
savedButton.setEnabled(state != StateFilter.SAVED);
savedButtonIcon.setAlpha(state == StateFilter.SAVED ? 1.0f : 0.6f);
binding.toggleSavedText.setVisibility((!compactMode || state == StateFilter.SAVED) ? View.VISIBLE : View.GONE);
binding.toggleSaved.setEnabled(state != StateFilter.SAVED);
binding.toggleSavedIcon.setAlpha(state == StateFilter.SAVED ? 1.0f : 0.6f);
}
public interface StateChangedListener {

View file

@ -150,4 +150,14 @@
}
}
- (UIStatusBarStyle)preferredStatusBarStyle {
if (!ThemeManager.themeManager.isDarkTheme) {
if (@available(iOS 13.0, *)) {
return UIStatusBarStyleDarkContent;
}
}
return UIStatusBarStyleLightContent;
}
@end

View file

@ -734,8 +734,9 @@ static const CGFloat kFolderTitleHeight = 36.0;
if (self.operation == FeedChooserOperationMuteSites) {
UIImage *image = [UIImage imageNamed:@"mute_feed_on.png"];
UIImage *highlightedImage = [UIImage imageNamed:@"mute_feed_off.png"];
cell.accessoryView = [[UIImageView alloc] initWithImage:image highlightedImage:highlightedImage];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image highlightedImage:highlightedImage];
imageView.highlighted = [tableView.indexPathsForSelectedRows containsObject:indexPath];
cell.accessoryView = imageView;
} else {
cell.accessoryView = nil;
}
@ -778,6 +779,10 @@ static const CGFloat kFolderTitleHeight = 36.0;
[self setWidgetIncludes:YES itemForIndexPath:indexPath];
}
UIImageView *imageView = (UIImageView *)[tableView cellForRowAtIndexPath:indexPath].accessoryView;
imageView.highlighted = YES;
[self updateControls];
}
@ -786,6 +791,10 @@ static const CGFloat kFolderTitleHeight = 36.0;
[self setWidgetIncludes:NO itemForIndexPath:indexPath];
}
UIImageView *imageView = (UIImageView *)[tableView cellForRowAtIndexPath:indexPath].accessoryView;
imageView.highlighted = NO;
[self updateControls];
}

View file

@ -53,7 +53,6 @@
@property (nonatomic) UISearchBar *searchBar;
@property (nonatomic) IBOutlet UIView *messageView;
@property (nonatomic) IBOutlet UILabel *messageLabel;
@property (nonatomic, strong) id standardInteractivePopGestureDelegate;
@property (nonatomic, readwrite) BOOL pageFetching;
@property (nonatomic, readwrite) BOOL pageFinished;

View file

@ -332,10 +332,6 @@
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
if (self.standardInteractivePopGestureDelegate == nil) {
self.standardInteractivePopGestureDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
}
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
[self setUserAvatarLayout:orientation];
self.finishedAnimatingIn = NO;
@ -453,10 +449,6 @@
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.navigationController.interactivePopGestureRecognizer.delegate != self.standardInteractivePopGestureDelegate) {
self.navigationController.interactivePopGestureRecognizer.delegate = self.standardInteractivePopGestureDelegate;
}
if (appDelegate.inStoryDetail && self.isPhoneOrCompact) {
appDelegate.inStoryDetail = NO;
// [appDelegate.storyPageControl resetPages];
@ -663,7 +655,7 @@
}
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, CGRectGetHeight(self.searchBar.frame), 1, 1) animated:YES];
}
- (void)beginOfflineTimer {
@ -774,7 +766,7 @@
NSInteger storyCount = storiesCollection.storyCount;
if (storyCount == 0) {
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
}
if (storiesCollection.feedPage == 1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
@ -970,7 +962,7 @@
NSInteger storyCount = storiesCollection.storyCount;
if (storyCount == 0) {
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, CGRectGetHeight(self.searchBar.frame), 1) animated:YES];
// [self.notifier initWithTitle:@"Loading more..." inView:self.view];
}
@ -2193,34 +2185,15 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
}
}
if (!everything && !infrequent && !read && !saved) {
NSString *deleteText = [NSString stringWithFormat:@"Delete %@",
appDelegate.storiesCollection.isRiverView ?
@"this entire folder" :
@"this site"];
[viewController addTitle:deleteText iconName:@"menu_icn_delete.png" selectionShouldDismiss:NO handler:^{
[self confirmDeleteSite:weakViewController.navigationController];
}];
[viewController addTitle:@"Move to another folder" iconName:@"menu_icn_move.png" selectionShouldDismiss:NO handler:^{
[self openMoveView:weakViewController.navigationController];
}];
}
if (!infrequent && !saved && !read) {
NSString *renameText = [NSString stringWithFormat:@"Rename this %@", appDelegate.storiesCollection.isRiverView ? @"folder" : @"site"];
NSString *manageText = [NSString stringWithFormat:@"Manage this %@", appDelegate.storiesCollection.isRiverView ? @"folder" : @"site"];
[viewController addTitle:renameText iconName:@"menu_icn_rename.png" selectionShouldDismiss:YES handler:^{
[self openRenameSite];
[viewController addTitle:manageText iconName:@"menu_icn_move.png" selectionShouldDismiss:NO handler:^{
[self manageSite:weakViewController.navigationController manageText:manageText everything:everything];
}];
}
if (!appDelegate.storiesCollection.isRiverView && !infrequent && !saved && !read) {
[viewController addTitle:@"Mute this site" iconName:@"menu_icn_mute.png" selectionShouldDismiss:NO handler:^{
[self confirmMuteSite:weakViewController.navigationController];
}];
[viewController addTitle:@"Train this site" iconName:@"menu_icn_train.png" selectionShouldDismiss:YES handler:^{
[self openTrainSite];
}];
@ -2366,6 +2339,41 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
}];
}
- (void)manageSite:(UINavigationController *)menuNavigationController manageText:(NSString *)manageText everything:(BOOL)everything {
MenuViewController *viewController = [MenuViewController new];
__weak MenuViewController *weakViewController = viewController;
viewController.title = manageText;
if (!everything) {
NSString *deleteText = [NSString stringWithFormat:@"Delete %@",
appDelegate.storiesCollection.isRiverView ?
@"this entire folder" :
@"this site"];
[viewController addTitle:deleteText iconName:@"menu_icn_delete.png" selectionShouldDismiss:NO handler:^{
[self confirmDeleteSite:weakViewController.navigationController];
}];
[viewController addTitle:@"Move to another folder" iconName:@"menu_icn_move.png" selectionShouldDismiss:NO handler:^{
[self openMoveView:weakViewController.navigationController];
}];
}
NSString *renameText = [NSString stringWithFormat:@"Rename this %@", appDelegate.storiesCollection.isRiverView ? @"folder" : @"site"];
[viewController addTitle:renameText iconName:@"menu_icn_rename.png" selectionShouldDismiss:YES handler:^{
[self openRenameSite];
}];
if (!appDelegate.storiesCollection.isRiverView) {
[viewController addTitle:@"Mute this site" iconName:@"menu_icn_mute.png" selectionShouldDismiss:NO handler:^{
[self confirmMuteSite:weakViewController.navigationController];
}];
}
[menuNavigationController pushViewController:viewController animated:YES];
}
- (void)confirmDeleteSite:(UINavigationController *)menuNavigationController {
MenuViewController *viewController = [MenuViewController new];
viewController.title = @"Positive?";
@ -2491,6 +2499,10 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
NSString *thisIdentifier = [NSString stringWithFormat:@"%@", storiesCollection.activeFeed[@"id"]];
[activeIdentifiers removeObject:thisIdentifier];
for (NSString *feedId in self.appDelegate.dictInactiveFeeds.allKeys) {
[activeIdentifiers removeObject:feedId];
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/save_feed_chooser", self.appDelegate.url];
@ -2739,8 +2751,10 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
}
if ([ThemeManager themeManager].isDarkTheme) {
self.storyTitlesTable.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
} else {
self.storyTitlesTable.indicatorStyle = UIScrollViewIndicatorStyleBlack;
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDefault;
}
@ -2802,7 +2816,7 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
storiesCollection.feedPage = 1;
self.pageFetching = YES;
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, CGRectGetHeight(self.searchBar.frame), 1, 1) animated:YES];
}
#pragma mark -

View file

@ -35,6 +35,7 @@
@property (assign, nonatomic) int savedStoriesCount;
@property (assign, nonatomic) BOOL isSocial;
@property (assign, nonatomic) BOOL isSaved;
@property (assign, nonatomic) BOOL isInactive;
@property (nonatomic) NSString *searchQuery;
@property (nonatomic) NSString *negativeCountStr;
@property (nonatomic) UnreadCountView *unreadCount;

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