mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'highlights' into django1.6
* highlights: (93 commits) Stubbing in profile tests. Adding nginx.local.conf Adding original text and original story to API docs. 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) Fixing signup flow. New icon for Infrequent Site Stories. Allowing selection in private notes. Autoresizing private notes field. For #1035: Adding private notes to saved stories. Adding REDIS_SESSIONS to local-settings template. Adding user notes. Unverified and needs saved confirmation. Fixing typo on highlights check in starred stories. Ignoring right-click when highlighting. Adding preference to turn off highlighter. Trim highlights for consistency and to fix a bug where paragraph ends look different. The story margins now allow for highlighting. Adding in model for upcoming highlights (so non-highlights branches dont crash on new data Renaming highlights to is_highlights for starred story counts. ...
This commit is contained in:
commit
acaf222999
122 changed files with 8970 additions and 1090 deletions
|
@ -161,7 +161,7 @@ these after the installation below.
|
|||
|
||||
Then load up the database with empty NewsBlur tables and bootstrap the database:
|
||||
|
||||
./manage.py migrate
|
||||
./manage.py syncdb --all --noinput
|
||||
./manage.py migrate --fake
|
||||
./manage.py migrate
|
||||
./manage.py loaddata config/fixtures/bootstrap.json
|
||||
|
|
|
@ -10,5 +10,6 @@ urlpatterns = patterns('',
|
|||
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),
|
||||
)
|
||||
|
|
|
@ -13,7 +13,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
|
||||
|
@ -81,8 +81,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())
|
||||
|
@ -100,6 +102,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:
|
||||
|
@ -113,6 +116,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,
|
||||
|
@ -302,7 +306,6 @@ def share_story(request, token=None):
|
|||
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({
|
||||
|
@ -393,3 +396,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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
26
apps/profile/tests.py
Normal file
26
apps/profile/tests.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
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 ProfiuleTest(TestCase):
|
||||
fixtures = ['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):
|
||||
response = self.client.post(reverse('welcome-signup'), {
|
||||
'signup_username': 'test',
|
||||
'signup_password': 'password',
|
||||
'signup_email': 'test@newsblur.com',
|
||||
})
|
||||
|
||||
|
|
@ -624,7 +624,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']
|
||||
|
|
|
@ -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.REQUEST.get('query', '').strip()
|
||||
order = request.REQUEST.get('order', 'newest')
|
||||
tag = request.REQUEST.get('tag')
|
||||
highlights = is_true(request.REQUEST.get('highlights', False))
|
||||
story_hashes = request.REQUEST.getlist('h') or request.REQUEST.getlist('h[]')
|
||||
story_hashes = story_hashes[:100]
|
||||
version = int(request.REQUEST.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.REQUEST.get('story_id', None)
|
||||
story_hash = request.REQUEST.get('story_hash', None)
|
||||
user_tags = request.REQUEST.getlist('user_tags') or request.REQUEST.getlist('user_tags[]')
|
||||
user_notes = request.REQUEST.get('user_notes', None)
|
||||
highlights = request.REQUEST.getlist('highlights') or request.REQUEST.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()
|
||||
|
||||
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
|
||||
r.publish(request.user.username, 'story:starred:%s' % story.story_hash)
|
||||
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}
|
||||
|
||||
|
|
|
@ -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'):
|
||||
|
@ -2849,7 +2853,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 = {
|
||||
|
@ -2860,6 +2866,16 @@ class MStarredStory(mongo.DynamicDocument):
|
|||
'allow_inheritance': False,
|
||||
'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:
|
||||
|
@ -2969,6 +2985,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)
|
||||
|
||||
|
@ -2978,6 +2995,16 @@ class MStarredStoryCounts(mongo.Document):
|
|||
'ordering': ['tag'],
|
||||
'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):
|
||||
|
@ -2997,6 +3024,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],
|
||||
|
@ -3005,7 +3033,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']
|
||||
|
@ -3030,20 +3058,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):
|
||||
|
@ -3056,9 +3086,19 @@ class MStarredStoryCounts(mongo.Document):
|
|||
for tag, count in dict(user_tags).items():
|
||||
cls.objects(user_id=user_id, tag=tag, slug=slugify(tag)).update_one(set__count=count,
|
||||
upsert=True)
|
||||
|
||||
|
||||
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()
|
||||
cls.objects(user_id=user_id,
|
||||
is_highlights=True, slug="highlights").update_one(set__count=highlighted_count, upsert=True)
|
||||
|
||||
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')
|
||||
|
@ -3084,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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1305,6 +1305,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)
|
||||
|
|
|
@ -38,6 +38,7 @@ javascripts:
|
|||
- media/js/vendor/jquery.ajaxmanager.3.js
|
||||
- media/js/vendor/jquery.simplemodal-1.4.js
|
||||
- media/js/vendor/jquery.color.js
|
||||
- media/js/vendor/jquery.wakeup.js
|
||||
- media/js/vendor/jquery-ui-1.12.1.js
|
||||
# - media/js/vendor/jquery.ui.autocomplete.js
|
||||
# - media/js/vendor/jquery.ui.core.js
|
||||
|
@ -54,6 +55,7 @@ javascripts:
|
|||
- media/js/vendor/jquery.tinysort.js
|
||||
- media/js/vendor/jquery.fieldselection.js
|
||||
- media/js/vendor/jquery.tipsy.js
|
||||
- media/js/vendor/tippy-*.js
|
||||
- media/js/vendor/jquery.autosize.js
|
||||
- media/js/vendor/jquery.chosen.js
|
||||
- media/js/vendor/jquery.effects.core.js
|
||||
|
@ -72,6 +74,7 @@ javascripts:
|
|||
- media/js/vendor/highlight.js
|
||||
- media/js/vendor/fitvid.js
|
||||
- media/js/vendor/imagesLoaded-*.js
|
||||
- media/js/vendor/mark.js
|
||||
- media/js/newsblur/reader/reader_utils.js
|
||||
- media/js/newsblur/reader/reader.js
|
||||
- media/js/newsblur/reader/reader_popover.js
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 166
|
||||
versionName "10.0b4"
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -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 { *; }
|
||||
|
|
10
clients/android/NewsBlur/res/drawable/ic_create_folder.xml
Normal file
10
clients/android/NewsBlur/res/drawable/ic_create_folder.xml
Normal 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>
|
88
clients/android/NewsBlur/res/layout/dialog_add_feed.xml
Normal file
88
clients/android/NewsBlur/res/layout/dialog_add_feed.xml
Normal 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>
|
|
@ -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>
|
|
@ -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" />
|
|
@ -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>
|
41
clients/android/NewsBlur/res/layout/row_saved_searches.xml
Normal file
41
clients/android/NewsBlur/res/layout/row_saved_searches.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -9,5 +9,11 @@
|
|||
|
||||
<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>
|
|
@ -72,4 +72,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>
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
|
||||
<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>
|
||||
<string name="infrequent_row_title">INFREQUENT SITE 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 \"%s\"?</string>
|
||||
<string name="delete_saved_search_message">Delete saved search %s?</string>
|
||||
<string name="delete_folder_message">Delete folder \"%s\" and unsubscribe from all feeds inside?</string>
|
||||
<string name="add_saved_search_message">Save search \"%s\"?</string>
|
||||
<string name="unfollow_message">Unfollow \"%s\"?</string>
|
||||
<string name="feed_subscribers">%s subscribers</string>
|
||||
<string name="feed_opens">%d opens</string>
|
||||
|
@ -403,6 +418,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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,9 @@ public class AllStoriesItemsList extends ItemsList {
|
|||
UIUtils.startReadingActivity(fs, hash, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSaveSearchFeedId() {
|
||||
return "river:";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,4 +37,8 @@ public class InfrequentItemsList extends ItemsList implements InfrequentCutoffCh
|
|||
restartReadingSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSaveSearchFeedId() {
|
||||
return "river:infrequent";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
if (activeSearchQuery != null) {
|
||||
searchQueryInput.setText(activeSearchQuery);
|
||||
searchQueryInput.setVisibility(View.VISIBLE);
|
||||
fs.setSearchQuery(activeSearchQuery);
|
||||
}
|
||||
activeSearchQuery = bundle.getString(BUNDLE_ACTIVE_SEARCH_QUERY);
|
||||
} else {
|
||||
activeSearchQuery = fs.getSearchQuery();
|
||||
}
|
||||
searchQueryInput.setOnKeyListener(new OnKeyListener() {
|
||||
if (activeSearchQuery != null) {
|
||||
binding.itemlistSearchQuery.setText(activeSearchQuery);
|
||||
binding.itemlistSearchQuery.setVisibility(View.VISIBLE);
|
||||
fs.setSearchQuery(activeSearchQuery);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -224,6 +224,12 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
|
|||
menu.findItem(R.id.menu_theme_black).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,11 +256,11 @@ 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_light) {
|
||||
|
@ -279,6 +285,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 +329,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 +409,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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -97,25 +81,25 @@ 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;
|
||||
}
|
||||
if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
|
||||
checkSearchQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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 +108,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 +158,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 +181,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 +210,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 +222,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 +248,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 +269,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);
|
||||
|
||||
|
@ -308,12 +317,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) {
|
||||
|
@ -364,17 +373,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 +413,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;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,18 +19,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,16 +73,7 @@ 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;
|
||||
|
@ -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());
|
||||
|
@ -312,7 +302,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;
|
||||
}
|
||||
|
@ -405,16 +395,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 +506,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 +534,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 +600,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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,4 +19,8 @@ public class SocialFeedItemsList extends ItemsList {
|
|||
UIUtils.setCustomActionBar(this, socialFeed.photoUrl, socialFeed.feedTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSaveSearchFeedId() {
|
||||
return "social:" + socialFeed.userId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -185,7 +192,7 @@ public class DatabaseConstants {
|
|||
FEED_FAVICON_BORDER + TEXT + ", " +
|
||||
FEED_LINK + TEXT + ", " +
|
||||
FEED_SUBSCRIBERS + TEXT + ", " +
|
||||
FEED_TITLE + TEXT + ", " +
|
||||
FEED_TITLE + TEXT + ", " +
|
||||
FEED_OPENS + INTEGER + ", " +
|
||||
FEED_AVERAGE_STORIES_PER_MONTH + INTEGER + ", " +
|
||||
FEED_LAST_STORY_DATE + TEXT + ", " +
|
||||
|
@ -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 " +
|
||||
|
|
|
@ -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,7 +275,15 @@ 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 {
|
||||
} 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);
|
||||
TextView nameView =((TextView) v.findViewById(R.id.row_feedname));
|
||||
|
@ -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,7 +429,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
return socialFeedsActive.size();
|
||||
} else if (isRowSavedStories(groupPosition)) {
|
||||
return starredCountsByTag.size();
|
||||
} else {
|
||||
} 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,
|
||||
|
@ -564,6 +593,17 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
recountFeeds();
|
||||
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;
|
||||
|
@ -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,7 +866,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
return ChildType.SOCIAL_FEED.ordinal();
|
||||
} else if (isRowSavedStories(groupPosition)) {
|
||||
return ChildType.SAVED_BY_TAG.ordinal();
|
||||
} else {
|
||||
} else if (isRowSavedSearches(groupPosition)) {
|
||||
return ChildType.SAVED_SEARCH.ordinal();
|
||||
} else {
|
||||
return ChildType.FEED.ordinal();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -61,6 +62,15 @@ public class Folder {
|
|||
public void removeOrphanFeedIds(Collection<String> orphanFeedIds) {
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,83 +3,217 @@ 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 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();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(FEED_URI, feedUri);
|
||||
args.putString(FEED_NAME, feedName);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
public static AddFeedFragment newInstance(String feedUri, String feedName) {
|
||||
AddFeedFragment frag = new AddFeedFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(FEED_URI, feedUri);
|
||||
args.putString(FEED_NAME, feedName);
|
||||
frag.setArguments(args);
|
||||
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) {
|
||||
|
||||
new AsyncTask<Void, Void, AddFeedResponse>() {
|
||||
@Override
|
||||
protected AddFeedResponse doInBackground(Void... arg) {
|
||||
((AddFeedProgressListener) activity).addFeedStarted();
|
||||
return apiManager.addFeed(getArguments().getString(FEED_URI));
|
||||
}
|
||||
builder.setTitle("Choose folder for " + getArguments().getString(FEED_NAME));
|
||||
builder.setView(v);
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(AddFeedResponse result) {
|
||||
if (!result.isError()) {
|
||||
// trigger a sync when we return to Main so that the new feed will show up
|
||||
NBSyncService.forceFeedsFolders();
|
||||
intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, result.feed.feedId);
|
||||
} else {
|
||||
UIUtils.safeToast(activity, R.string.add_feed_error, Toast.LENGTH_SHORT);
|
||||
}
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
AddFeedFragment.this.dismiss();
|
||||
};
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
|
||||
AddFeedAdapter adapter = new AddFeedAdapter(new OnFolderClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
AddFeedFragment.this.dismiss();
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
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();
|
||||
}
|
||||
|
||||
public interface AddFeedProgressListener {
|
||||
public abstract void addFeedStarted();
|
||||
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... voids) {
|
||||
((AddFeedProgressListener) activity).addFeedStarted();
|
||||
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();
|
||||
intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, result.feed.feedId);
|
||||
} else {
|
||||
UIUtils.safeToast(activity, R.string.add_feed_error, Toast.LENGTH_SHORT);
|
||||
}
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
AddFeedFragment.this.dismiss();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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 {
|
||||
void addFeedStarted();
|
||||
}
|
||||
|
||||
public interface OnFolderClickListener {
|
||||
void onItemClick(Folder folder);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,7 +24,9 @@ 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();
|
||||
Bundle args = new Bundle();
|
||||
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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,7 +560,9 @@ 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 {
|
||||
} 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
|
||||
// a folder name on the FeedSet and making it into a folder-type set. it is just a single feed,
|
||||
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,8 +60,57 @@ public class ReadingFontDialogFragment extends DialogFragment {
|
|||
return v;
|
||||
}
|
||||
|
||||
@OnClick(R.id.radio_anonymous) void selectAnonymousPro() {
|
||||
switchFont(getString(R.string.anonymous_pro_font_prefvalue));
|
||||
@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) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ 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 +28,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 +51,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 +68,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 +95,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 +151,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 +160,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 +170,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 +178,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 +212,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 +246,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 +254,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 +305,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);
|
||||
|
||||
|
@ -386,7 +393,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 +402,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 +450,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 +480,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 +489,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 +513,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 +556,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 +578,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 +620,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 +630,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
|
||||
|
@ -786,7 +793,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
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 +894,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 +910,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 +931,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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
@ -39,6 +40,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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -150,4 +150,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
if (!ThemeManager.themeManager.isDarkTheme) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return UIStatusBarStyleDarkContent;
|
||||
}
|
||||
}
|
||||
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 -
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -192,7 +192,10 @@ static UIFont *textFont = nil;
|
|||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
if (cell.savedStoriesCount > 0) {
|
||||
if (cell.isInactive) {
|
||||
CGRect imageRect = CGRectMake(CGRectGetMaxX(r) - 25, CGRectGetMidY(r) - 8, 16, 16);
|
||||
[[UIImage imageNamed:@"mute_gray.png"] drawInRect:imageRect];
|
||||
} else if (cell.savedStoriesCount > 0) {
|
||||
[cell.unreadCount drawInRect:r ps:cell.savedStoriesCount nt:0 listType:NBFeedListSaved];
|
||||
} else {
|
||||
[cell.unreadCount drawInRect:r ps:cell.positiveCount nt:cell.neutralCount
|
||||
|
|
|
@ -253,6 +253,7 @@ SFSafariViewControllerDelegate> {
|
|||
|
||||
@property (nonatomic) NSDictionary *dictFolders;
|
||||
@property (nonatomic, strong) NSMutableDictionary *dictFeeds;
|
||||
@property (nonatomic, strong) NSMutableDictionary *dictInactiveFeeds;
|
||||
@property (nonatomic) NSMutableDictionary *dictActiveFeeds;
|
||||
@property (nonatomic) NSDictionary *dictSocialFeeds;
|
||||
@property (nonatomic) NSDictionary *dictSavedStoryTags;
|
||||
|
|
|
@ -1257,6 +1257,10 @@
|
|||
NSMutableArray *foundNotificationFeedIds = [NSMutableArray array];
|
||||
|
||||
for (NSDictionary *feed in self.dictFeeds.allValues) {
|
||||
if (![feed isKindOfClass:[NSDictionary class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSArray *types = [feed objectForKey:@"notification_types"];
|
||||
if (types) {
|
||||
for (NSString *notificationType in types) {
|
||||
|
|
|
@ -490,11 +490,11 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
[appDelegate cancelOfflineQueue];
|
||||
|
||||
if (self.inPullToRefresh_) {
|
||||
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=true",
|
||||
self.appDelegate.url];
|
||||
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=true&include_inactive=true",
|
||||
self.appDelegate.url];
|
||||
} else {
|
||||
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=false",
|
||||
self.appDelegate.url];
|
||||
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=false&include_inactive=true",
|
||||
self.appDelegate.url];
|
||||
}
|
||||
|
||||
if (appDelegate.backgroundCompletionHandler) {
|
||||
|
@ -706,10 +706,10 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
// set up dictFolders
|
||||
NSMutableDictionary * allFolders = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if (![[results objectForKey:@"flat_folders"] isKindOfClass:[NSArray class]]) {
|
||||
allFolders = [[results objectForKey:@"flat_folders"] mutableCopy];
|
||||
if (![[results objectForKey:@"flat_folders_with_inactive"] isKindOfClass:[NSArray class]]) {
|
||||
allFolders = [[results objectForKey:@"flat_folders_with_inactive"] mutableCopy];
|
||||
}
|
||||
|
||||
|
||||
[allFolders setValue:socialFolder forKey:@"river_blurblogs"];
|
||||
[allFolders setValue:[[NSMutableArray alloc] init] forKey:@"river_global"];
|
||||
|
||||
|
@ -721,8 +721,11 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
|
||||
appDelegate.dictFolders = allFolders;
|
||||
|
||||
appDelegate.dictInactiveFeeds = [results[@"inactive_feeds"] mutableCopy];
|
||||
|
||||
// set up dictFeeds
|
||||
appDelegate.dictFeeds = [[results objectForKey:@"feeds"] mutableCopy];
|
||||
[appDelegate.dictFeeds addEntriesFromDictionary:appDelegate.dictInactiveFeeds];
|
||||
[appDelegate populateDictUnreadCounts];
|
||||
[appDelegate populateDictTextFeeds];
|
||||
|
||||
|
@ -1103,8 +1106,10 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
self.searchBar.nb_searchField.tintColor = UIColorFromRGB(NEWSBLUR_BLACK_COLOR);
|
||||
|
||||
if ([ThemeManager themeManager].isDarkTheme) {
|
||||
self.feedTitlesTable.indicatorStyle = UIScrollViewIndicatorStyleWhite;
|
||||
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||
} else {
|
||||
self.feedTitlesTable.indicatorStyle = UIScrollViewIndicatorStyleBlack;
|
||||
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDefault;
|
||||
}
|
||||
|
||||
|
@ -1241,6 +1246,7 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
BOOL isSocial = [appDelegate isSocialFeed:feedIdStr];
|
||||
BOOL isSaved = [appDelegate isSavedFeed:feedIdStr];
|
||||
BOOL isSavedStoriesFeed = self.appDelegate.isSavedStoriesIntelligenceMode && [self.appDelegate savedStoriesCountForFeed:feedIdStr] > 0;
|
||||
BOOL isInactive = appDelegate.dictInactiveFeeds[feedIdStr] != nil;
|
||||
BOOL isOmitted = false;
|
||||
NSString *CellIdentifier;
|
||||
|
||||
|
@ -1284,6 +1290,7 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
cell.feedTitle = [feed objectForKey:@"feed_title"];
|
||||
cell.isSocial = isSocial;
|
||||
cell.isSaved = isSaved;
|
||||
cell.isInactive = isInactive;
|
||||
cell.searchQuery = searchQuery;
|
||||
|
||||
if (newCell) {
|
||||
|
@ -1301,6 +1308,11 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
cell.feedFavicon = [appDelegate folderIcon:searchFolder];
|
||||
cell.feedTitle = [NSString stringWithFormat:@"\"%@\" in %@", cell.searchQuery, [appDelegate folderTitle:searchFolder]];
|
||||
}
|
||||
} else if (isInactive) {
|
||||
cell.positiveCount = 0;
|
||||
cell.neutralCount = 0;
|
||||
cell.negativeCount = 0;
|
||||
cell.savedStoriesCount = 0;
|
||||
} else if (isSavedStoriesFeed) {
|
||||
cell.positiveCount = 0;
|
||||
cell.neutralCount = 0;
|
||||
|
@ -1795,6 +1807,10 @@ heightForHeaderInSection:(NSInteger)section {
|
|||
([[unreadCounts objectForKey:@"ps"] intValue] <= 0 &&
|
||||
[[unreadCounts objectForKey:@"nt"] intValue] <= 0)) {
|
||||
return NO;
|
||||
} else if (!stillVisible &&
|
||||
!self.viewShowingAllFeeds &&
|
||||
appDelegate.dictInactiveFeeds[feedId] != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
|
|
@ -47,7 +47,6 @@ UIActionSheetDelegate, WKNavigationDelegate> {
|
|||
@property (nonatomic, assign) BOOL inTextView;
|
||||
@property (nonatomic, assign) BOOL isRecentlyUnread;
|
||||
@property (nonatomic) BOOL hasStory;
|
||||
@property (nonatomic, readonly) BOOL canHideNavigationBar;
|
||||
@property (nonatomic, readonly) BOOL isSinglePage;
|
||||
|
||||
@property NSInteger pageIndex;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
@interface StoryDetailViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSString *fullStoryHTML;
|
||||
@property (nonatomic) BOOL isBarHideSwiping;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -76,6 +77,17 @@
|
|||
[audioSession setCategory:AVAudioSessionCategoryPlayback
|
||||
error:nil];
|
||||
|
||||
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
|
||||
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
||||
NSString *videoPlayback = [preferences stringForKey:@"video_playback"];
|
||||
|
||||
configuration.allowsInlineMediaPlayback = ![videoPlayback isEqualToString:@"fullscreen"];
|
||||
|
||||
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
|
||||
|
||||
[self.view addSubview:self.webView];
|
||||
|
||||
self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
self.webView.navigationDelegate = self;
|
||||
// self.webView.scalesPageToFit = YES;
|
||||
self.webView.allowsLinkPreview = YES;
|
||||
|
@ -98,6 +110,7 @@
|
|||
context:nil];
|
||||
|
||||
[self.appDelegate prepareWebView:self.webView completionHandler:nil];
|
||||
|
||||
[self clearWebView];
|
||||
|
||||
// UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc]
|
||||
|
@ -173,7 +186,7 @@
|
|||
- (void)tap:(UITapGestureRecognizer *)gestureRecognizer {
|
||||
// NSLog(@"Gesture tap: %ld (%ld) - %d", (long)gestureRecognizer.state, (long)UIGestureRecognizerStateEnded, inDoubleTap);
|
||||
|
||||
if (gestureRecognizer.state == UIGestureRecognizerStateEnded && gestureRecognizer.numberOfTouches == 1 && appDelegate.storyPageControl.autoscrollAvailable && self.presentedViewController == nil) {
|
||||
if (gestureRecognizer.state == UIGestureRecognizerStateEnded && gestureRecognizer.numberOfTouches == 1 && self.presentedViewController == nil) {
|
||||
CGPoint pt = [self pointForGesture:gestureRecognizer];
|
||||
if (pt.x == CGPointZero.x && pt.y == CGPointZero.y) return;
|
||||
if (inDoubleTap) return;
|
||||
|
@ -184,7 +197,7 @@
|
|||
[webView evaluateJavaScript:[NSString stringWithFormat:@"linkAt(%li, %li, 'id');", (long)pt.x,(long)pt.y] completionHandler:^(NSString *identifier, NSError *error) {
|
||||
[webView evaluateJavaScript:[NSString stringWithFormat:@"linkAt(%li, %li, 'outerHTML');", (long)pt.x,(long)pt.y] completionHandler:^(NSString *outerHTML, NSError *error) {
|
||||
if ([identifier isEqualToString:@"NB-story"] || ![outerHTML containsString:@"NB-"]) {
|
||||
[appDelegate.storyPageControl showAutoscrollBriefly:YES];
|
||||
[appDelegate.storyPageControl tappedStory];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
@ -194,7 +207,7 @@
|
|||
|
||||
// Ignore links, videos, and iframes (e.g. embedded YouTube videos).
|
||||
if (![@[@"A", @"VIDEO", @"IFRAME"] containsObject:tagName]) {
|
||||
[appDelegate.storyPageControl showAutoscrollBriefly:YES];
|
||||
[appDelegate.storyPageControl tappedStory];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@ -284,6 +297,8 @@
|
|||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
[self.navigationController.barHideOnSwipeGestureRecognizer removeTarget:self action:@selector(barHideSwipe:)];
|
||||
|
||||
if (!appDelegate.showingSafariViewController &&
|
||||
appDelegate.navigationController.visibleViewController != (UIViewController *)appDelegate.shareViewController &&
|
||||
appDelegate.navigationController.visibleViewController != (UIViewController *)appDelegate.trainerViewController &&
|
||||
|
@ -305,6 +320,8 @@
|
|||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self.navigationController.barHideOnSwipeGestureRecognizer addTarget:self action:@selector(barHideSwipe:)];
|
||||
|
||||
if (!self.isPhoneOrCompact) {
|
||||
[appDelegate.feedDetailViewController.view endEditing:YES];
|
||||
}
|
||||
|
@ -506,6 +523,16 @@
|
|||
// }
|
||||
// }
|
||||
|
||||
NSString *feedIdStr = [NSString stringWithFormat:@"%@",
|
||||
[self.activeStory
|
||||
objectForKey:@"story_feed_id"]];
|
||||
NSDictionary *feed = [appDelegate getFeed:feedIdStr];
|
||||
NSString *storyClassSuffix = @"";
|
||||
|
||||
if (feed[@"is_newsletter"]) {
|
||||
storyClassSuffix = @" NB-newsletter";
|
||||
}
|
||||
|
||||
NSString *riverClass = (appDelegate.storiesCollection.isRiverView ||
|
||||
appDelegate.storiesCollection.isSocialView ||
|
||||
appDelegate.storiesCollection.isSavedView ||
|
||||
|
@ -563,7 +590,7 @@
|
|||
|
||||
NSString *htmlContent = [NSString stringWithFormat:@
|
||||
"%@" // header
|
||||
" <div id=\"NB-story\" class=\"NB-story\">%@</div>"
|
||||
" <div id=\"NB-story\" class=\"NB-story%@\">%@</div>"
|
||||
" <div class=\"NB-text-view-premium-only\">%@</div>"
|
||||
" <div id=\"NB-sideoptions-container\">%@</div>"
|
||||
" <div id=\"NB-comments-wrapper\">"
|
||||
|
@ -572,6 +599,7 @@
|
|||
" %@"
|
||||
"%@", // footer
|
||||
htmlTop,
|
||||
storyClassSuffix,
|
||||
storyContent,
|
||||
premiumTextString,
|
||||
sharingHtmlString,
|
||||
|
@ -588,8 +616,6 @@
|
|||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// NSLog(@"Drawing Story: %@", [self.activeStory objectForKey:@"story_title"]);
|
||||
// [self.webView setMediaPlaybackRequiresUserAction:NO];
|
||||
// self.webView.allowsInlineMediaPlayback = YES;
|
||||
[self loadHTMLString:htmlTopAndBottom];
|
||||
[self.appDelegate.storyPageControl setTextButton:self];
|
||||
});
|
||||
|
@ -603,10 +629,17 @@
|
|||
|
||||
- (void)drawFeedGradient {
|
||||
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
||||
BOOL navigationBarHidden = self.navigationController.navigationBarHidden;
|
||||
BOOL shouldHideStatusBar = [preferences boolForKey:@"story_hide_status_bar"];
|
||||
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
BOOL shouldOffsetFeedGradient = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && !UIInterfaceOrientationIsLandscape(orientation) && self.navigationController.navigationBarHidden && !shouldHideStatusBar;
|
||||
CGFloat yOffset = shouldOffsetFeedGradient ? appDelegate.storyPageControl.statusBarBackgroundView.bounds.size.height - 1 : -1;
|
||||
BOOL shouldOffsetFeedGradient = !self.isBarHideSwiping && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && !UIInterfaceOrientationIsLandscape(orientation) && navigationBarHidden && !shouldHideStatusBar;
|
||||
CGFloat offset = 0;
|
||||
|
||||
if (shouldOffsetFeedGradient) {
|
||||
offset = appDelegate.storyPageControl.statusBarBackgroundView.bounds.size.height;
|
||||
}
|
||||
|
||||
CGFloat yOffset = offset - 1;
|
||||
NSString *feedIdStr = [NSString stringWithFormat:@"%@",
|
||||
[self.activeStory
|
||||
objectForKey:@"story_feed_id"]];
|
||||
|
@ -1305,6 +1338,9 @@
|
|||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if ([keyPath isEqual:@"contentOffset"]) {
|
||||
BOOL isHorizontal = appDelegate.storyPageControl.isHorizontal;
|
||||
BOOL isNavBarHidden = self.navigationController.navigationBarHidden;
|
||||
|
||||
if (self.webView.scrollView.contentOffset.y < (-1 * self.feedTitleGradient.frame.size.height + 1 + self.webView.scrollView.scrollIndicatorInsets.top)) {
|
||||
// Pulling
|
||||
if (!pullingScrollview) {
|
||||
|
@ -1326,11 +1362,14 @@
|
|||
if (pullingScrollview) {
|
||||
pullingScrollview = NO;
|
||||
[self.feedTitleGradient.layer setShadowOpacity:0];
|
||||
[self.webView insertSubview:self.feedTitleGradient aboveSubview:self.webView.scrollView];
|
||||
|
||||
self.feedTitleGradient.frame = CGRectMake(0, -1,
|
||||
self.feedTitleGradient.frame.size.width,
|
||||
self.feedTitleGradient.frame.size.height);
|
||||
if (!isNavBarHidden) {
|
||||
[self.webView insertSubview:self.feedTitleGradient aboveSubview:self.webView.scrollView];
|
||||
|
||||
self.feedTitleGradient.frame = CGRectMake(0, -1,
|
||||
self.feedTitleGradient.frame.size.width,
|
||||
self.feedTitleGradient.frame.size.height);
|
||||
}
|
||||
|
||||
for (id subview in self.webView.scrollView.subviews) {
|
||||
UIImageView *imgView = [subview isKindOfClass:[UIImageView class]] ?
|
||||
|
@ -1349,16 +1388,11 @@
|
|||
int viewportHeight = self.view.frame.size.height;
|
||||
int topPosition = self.webView.scrollView.contentOffset.y;
|
||||
int safeBottomMargin = 0;
|
||||
int minimumTopPositionWhenHidden = -1;
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
CGFloat bottomInset = appDelegate.storyPageControl.view.safeAreaInsets.bottom;
|
||||
|
||||
safeBottomMargin = -1 * bottomInset / 2;
|
||||
|
||||
if (bottomInset != 0) {
|
||||
minimumTopPositionWhenHidden = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int bottomPosition = webpageHeight - topPosition - viewportHeight;
|
||||
|
@ -1371,14 +1405,7 @@
|
|||
hasScrolled = YES;
|
||||
}
|
||||
|
||||
BOOL isHorizontal = appDelegate.storyPageControl.isHorizontal;
|
||||
BOOL isNavBarHidden = self.navigationController.navigationBarHidden;
|
||||
|
||||
if (!isHorizontal && appDelegate.storyPageControl.previousPage.pageIndex < 0) {
|
||||
[appDelegate.storyPageControl setNavigationBarHidden:NO];
|
||||
} else if (isHorizontal && topPosition <= minimumTopPositionWhenHidden && isNavBarHidden) {
|
||||
[appDelegate.storyPageControl setNavigationBarHidden:NO];
|
||||
} else if (!nearTop && !isNavBarHidden && self.canHideNavigationBar) {
|
||||
if (!isNavBarHidden && self.canHideNavigationBar && !nearTop && appDelegate.storyPageControl.autoscrollActive) {
|
||||
[appDelegate.storyPageControl setNavigationBarHidden:YES];
|
||||
}
|
||||
|
||||
|
@ -1401,28 +1428,18 @@
|
|||
appDelegate.storyPageControl.traverseView.alpha = 1;
|
||||
// NSLog(@" ---> Bottom position: %d", bottomPosition);
|
||||
if (bottomPosition >= 0 || !isHorizontal) {
|
||||
// appDelegate.storyPageControl.traverseView.frame = CGRectMake(tvf.origin.x,
|
||||
// self.webView.scrollView.frame.size.height - tvf.size.height - safeBottomMargin,
|
||||
// tvf.size.width, tvf.size.height);
|
||||
appDelegate.storyPageControl.traverseBottomConstraint.constant = safeBottomMargin;
|
||||
} else {
|
||||
// appDelegate.storyPageControl.traverseView.frame = CGRectMake(tvf.origin.x,
|
||||
// (webpageHeight - topPosition) - tvf.size.height - safeBottomMargin,
|
||||
// tvf.size.width, tvf.size.height);
|
||||
if (webpageHeight > 0) {
|
||||
appDelegate.storyPageControl.traverseBottomConstraint.constant = viewportHeight - (webpageHeight - topPosition) + safeBottomMargin;
|
||||
} else {
|
||||
appDelegate.storyPageControl.traverseBottomConstraint.constant = safeBottomMargin;
|
||||
}
|
||||
// appDelegate.storyPageControl.traverseBottomConstraint.constant = safeBottomMargin;
|
||||
}
|
||||
} else if (!singlePage && (atTop && !atBottom)) {
|
||||
// Pin to bottom of viewport, regardless of scrollview
|
||||
appDelegate.storyPageControl.traversePinned = YES;
|
||||
appDelegate.storyPageControl.traverseFloating = NO;
|
||||
// appDelegate.storyPageControl.traverseView.frame = CGRectMake(tvf.origin.x,
|
||||
// self.webView.scrollView.frame.size.height - tvf.size.height - safeBottomMargin,
|
||||
// tvf.size.width, tvf.size.height);
|
||||
[appDelegate.storyPageControl.view layoutIfNeeded];
|
||||
|
||||
appDelegate.storyPageControl.traverseBottomConstraint.constant = safeBottomMargin;
|
||||
|
@ -1442,9 +1459,6 @@
|
|||
[UIView animateWithDuration:.3 delay:0
|
||||
options:UIViewAnimationOptionCurveEaseInOut
|
||||
animations:^{
|
||||
// appDelegate.storyPageControl.traverseView.frame = CGRectMake(tvf.origin.x,
|
||||
// (webpageHeight - topPosition) - tvf.size.height - safeBottomMargin,
|
||||
// tvf.size.width, tvf.size.height);
|
||||
[appDelegate.storyPageControl.view layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
appDelegate.storyPageControl.traversePinned = NO;
|
||||
|
@ -1455,9 +1469,6 @@
|
|||
appDelegate.storyPageControl.traverseFloating = YES;
|
||||
appDelegate.storyPageControl.traverseView.alpha = 1;
|
||||
appDelegate.storyPageControl.traverseBottomConstraint.constant = viewportHeight - (webpageHeight - topPosition) + safeBottomMargin;
|
||||
// appDelegate.storyPageControl.traverseView.frame = CGRectMake(tvf.origin.x,
|
||||
// (webpageHeight - topPosition) - tvf.size.height - safeBottomMargin,
|
||||
// tvf.size.width, tvf.size.height);
|
||||
}
|
||||
|
||||
[self storeScrollPosition:YES];
|
||||
|
@ -1897,23 +1908,21 @@
|
|||
[self.webView evaluateJavaScript:jsString completionHandler:nil];
|
||||
|
||||
self.webView.backgroundColor = UIColorFromLightDarkRGB(0x707070, 0x404040);
|
||||
|
||||
if ([ThemeManager themeManager].isDarkTheme) {
|
||||
self.webView.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
|
||||
} else {
|
||||
self.webView.scrollView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canHideNavigationBar {
|
||||
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone || self.presentedViewController != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!appDelegate.storyPageControl.wantNavigationBarHidden) {
|
||||
if (!appDelegate.storyPageControl.allowFullscreen) {
|
||||
NSLog(@"canHideNavigationBar: no, toggle is off"); // log
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL canHide = !self.isSinglePage;
|
||||
|
||||
NSLog(@"canHideNavigationBar: %@", canHide ? @"yes" : @"no"); // log
|
||||
|
||||
return canHide;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isSinglePage {
|
||||
|
@ -2228,7 +2237,22 @@
|
|||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
# pragma mark
|
||||
- (void)barHideSwipe:(UISwipeGestureRecognizer *)recognizer {
|
||||
if (recognizer.state == UIGestureRecognizerStateEnded) {
|
||||
self.isBarHideSwiping = NO;
|
||||
|
||||
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
||||
BOOL shouldHideStatusBar = [preferences boolForKey:@"story_hide_status_bar"];
|
||||
|
||||
if (!shouldHideStatusBar) {
|
||||
[self drawFeedGradient];
|
||||
}
|
||||
} else {
|
||||
self.isBarHideSwiping = YES;
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark -
|
||||
# pragma mark Subscribing to blurblog
|
||||
|
||||
- (void)subscribeToBlurblog {
|
||||
|
|
|
@ -87,7 +87,8 @@
|
|||
@property (nonatomic) MBProgressHUD *storyHUD;
|
||||
@property (nonatomic, strong) NBNotifier *notifier;
|
||||
@property (nonatomic) NSInteger scrollingToPage;
|
||||
@property (nonatomic, readonly) BOOL wantNavigationBarHidden;
|
||||
@property (nonatomic, strong) id standardInteractivePopGestureDelegate;
|
||||
@property (nonatomic, readonly) BOOL allowFullscreen;
|
||||
@property (nonatomic) BOOL forceNavigationBarShown;
|
||||
@property (nonatomic) BOOL currentlyTogglingNavigationBar;
|
||||
@property (nonatomic, readonly) BOOL isHorizontal;
|
||||
|
@ -96,6 +97,7 @@
|
|||
- (void)resizeScrollView;
|
||||
- (void)applyNewIndex:(NSInteger)newIndex pageController:(StoryDetailViewController *)pageController;
|
||||
- (void)layoutForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
|
||||
- (void)updateStatusBarState;
|
||||
- (void)setNavigationBarHidden:(BOOL)hide;
|
||||
- (void)setNavigationBarHidden:(BOOL)hide alsoTraverse:(BOOL)alsoTraverse;
|
||||
- (void)adjustDragBar:(UIInterfaceOrientation)orientation;
|
||||
|
@ -135,6 +137,7 @@
|
|||
|
||||
- (void)flashCheckmarkHud:(NSString *)messageType;
|
||||
|
||||
- (void)tappedStory;
|
||||
- (void)showAutoscrollBriefly:(BOOL)briefly;
|
||||
- (void)hideAutoscrollAfterDelay;
|
||||
- (void)hideAutoscrollImmediately;
|
||||
|
|
|
@ -276,11 +276,17 @@
|
|||
|
||||
self.currentlyTogglingNavigationBar = NO;
|
||||
|
||||
if (self.standardInteractivePopGestureDelegate == nil) {
|
||||
self.standardInteractivePopGestureDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
|
||||
}
|
||||
|
||||
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
|
||||
BOOL swipeEnabled = [[userPreferences stringForKey:@"story_detail_swipe_left_edge"]
|
||||
isEqualToString:@"pop_to_story_list"];;
|
||||
self.navigationController.hidesBarsOnSwipe = self.allowFullscreen;
|
||||
self.navigationController.interactivePopGestureRecognizer.enabled = swipeEnabled;
|
||||
|
||||
self.navigationController.interactivePopGestureRecognizer.delegate = self;
|
||||
|
||||
if (self.isPhoneOrCompact) {
|
||||
if (!appDelegate.storiesCollection.isSocialView) {
|
||||
UIImage *titleImage;
|
||||
|
@ -398,7 +404,9 @@
|
|||
[super viewWillDisappear:animated];
|
||||
|
||||
previousPage.view.hidden = YES;
|
||||
self.navigationController.hidesBarsOnSwipe = NO;
|
||||
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
|
||||
self.navigationController.interactivePopGestureRecognizer.delegate = self.standardInteractivePopGestureDelegate;
|
||||
self.autoscrollActive = NO;
|
||||
}
|
||||
|
||||
|
@ -483,7 +491,11 @@
|
|||
return shouldHideStatusBar && isNavBarHidden;
|
||||
}
|
||||
|
||||
- (BOOL)wantNavigationBarHidden {
|
||||
- (BOOL)allowFullscreen {
|
||||
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone || self.presentedViewController != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
return ([preferences boolForKey:@"story_full_screen"] || self.autoscrollAvailable) && !self.forceNavigationBarShown;
|
||||
|
@ -502,17 +514,6 @@
|
|||
|
||||
[self.navigationController setNavigationBarHidden:hide animated:YES];
|
||||
|
||||
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
|
||||
BOOL swipeEnabled = [[userPreferences stringForKey:@"story_detail_swipe_left_edge"]
|
||||
isEqualToString:@"pop_to_story_list"];
|
||||
self.navigationController.interactivePopGestureRecognizer.enabled = swipeEnabled;
|
||||
|
||||
if (hide) {
|
||||
self.navigationController.interactivePopGestureRecognizer.delegate = self;
|
||||
} else if (appDelegate.feedDetailViewController.standardInteractivePopGestureDelegate != nil) {
|
||||
self.navigationController.interactivePopGestureRecognizer.delegate = appDelegate.feedDetailViewController.standardInteractivePopGestureDelegate;
|
||||
}
|
||||
|
||||
CGPoint oldOffset = currentPage.webView.scrollView.contentOffset;
|
||||
CGFloat navHeight = self.navigationController.navigationBar.bounds.size.height;
|
||||
CGFloat statusAdjustment = 20.0;
|
||||
|
@ -1243,16 +1244,6 @@
|
|||
}
|
||||
self.scrollView.scrollsToTop = NO;
|
||||
|
||||
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
|
||||
BOOL shouldHideStatusBar = [preferences boolForKey:@"story_hide_status_bar"];
|
||||
NSInteger statusBarOffset = shouldHideStatusBar ? 0 : self.statusBarHeight;
|
||||
NSInteger topPosition = currentPage.webView.scrollView.contentOffset.y + statusBarOffset;
|
||||
BOOL canHide = currentPage.canHideNavigationBar && topPosition >= 0;
|
||||
|
||||
if (!canHide && self.isHorizontal && self.navigationController.navigationBarHidden) {
|
||||
[self setNavigationBarHidden:NO];
|
||||
}
|
||||
|
||||
if (self.isDraggingScrollview || self.scrollingToPage == currentPage.pageIndex) {
|
||||
if (currentPage.pageIndex == -2) return;
|
||||
self.scrollingToPage = -1;
|
||||
|
@ -1270,8 +1261,10 @@
|
|||
}
|
||||
[appDelegate.feedDetailViewController redrawUnreadStory];
|
||||
}
|
||||
|
||||
[currentPage becomeFirstResponder];
|
||||
|
||||
if (!appDelegate.storiesCollection.inSearch) {
|
||||
[currentPage becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)advanceToNextUnread {
|
||||
|
@ -1567,12 +1560,11 @@
|
|||
}
|
||||
|
||||
- (void)changedFullscreen {
|
||||
BOOL wantHidden = self.wantNavigationBarHidden;
|
||||
BOOL isHidden = self.navigationController.navigationBarHidden;
|
||||
BOOL wantHidden = self.allowFullscreen;
|
||||
|
||||
if (self.currentPage.webView.scrollView.contentOffset.y > 10 || isHidden) {
|
||||
[self setNavigationBarHidden:wantHidden alsoTraverse:YES];
|
||||
}
|
||||
self.navigationController.hidesBarsOnSwipe = self.allowFullscreen;
|
||||
|
||||
[self setNavigationBarHidden:wantHidden alsoTraverse:YES];
|
||||
}
|
||||
|
||||
- (void)changedAutoscroll {
|
||||
|
@ -1600,7 +1592,7 @@
|
|||
self.statusBarBackgroundView = [[UIView alloc] initWithFrame:statusRect];
|
||||
self.statusBarBackgroundView.backgroundColor = self.navigationController.navigationBar.barTintColor;
|
||||
|
||||
[self.view addSubview:self.statusBarBackgroundView];
|
||||
[self.navigationController.view addSubview:self.statusBarBackgroundView];
|
||||
self.statusBarBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin;
|
||||
|
||||
[self updateStatusBarState];
|
||||
|
@ -1702,6 +1694,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)tappedStory {
|
||||
if (self.autoscrollAvailable) {
|
||||
[self showAutoscrollBriefly:YES];
|
||||
} else {
|
||||
[self setNavigationBarHidden: !self.navigationController.navigationBarHidden];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showAutoscrollBriefly:(BOOL)briefly {
|
||||
if (!self.autoscrollAvailable || self.currentPage.webView.scrollView.contentSize.height - 200 <= self.currentPage.view.frame.size.height) {
|
||||
[self hideAutoscrollWithAnimation];
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
17042DB92391D68A001BCD32 /* WidgetStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17042DB82391D68A001BCD32 /* WidgetStory.swift */; };
|
||||
17042DBB23922A4D001BCD32 /* WidgetLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17042DBA23922A4D001BCD32 /* WidgetLoader.swift */; };
|
||||
1715D02B2166B3F900227731 /* PremiumManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1715D02A2166B3F900227731 /* PremiumManager.m */; };
|
||||
1721C9D12497F91A00B0EDC4 /* mute_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 1721C9D02497F91900B0EDC4 /* mute_gray.png */; };
|
||||
17362ADD23639B4E00A0FCCC /* OfflineFetchText.m in Sources */ = {isa = PBXBuildFile; fileRef = 17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */; };
|
||||
1740C6881C10FD75005EA453 /* theme_color_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6841C10FD75005EA453 /* theme_color_dark.png */; };
|
||||
1740C6891C10FD75005EA453 /* theme_color_light.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6851C10FD75005EA453 /* theme_color_light.png */; };
|
||||
|
@ -251,7 +252,6 @@
|
|||
43F6A79D15B0CDC60092EE91 /* ActivityCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 43F6A79C15B0CDC60092EE91 /* ActivityCell.m */; };
|
||||
43F6A7A915B0E1ED0092EE91 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F6A7A815B0E1ED0092EE91 /* CoreText.framework */; };
|
||||
78095E3F128EF35400230C8E /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78095E3E128EF35400230C8E /* CFNetwork.framework */; };
|
||||
78095E43128EF37E00230C8E /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78095E42128EF37E00230C8E /* MobileCoreServices.framework */; };
|
||||
78095E45128EF37E00230C8E /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78095E44128EF37E00230C8E /* SystemConfiguration.framework */; };
|
||||
78095EC9128F30B500230C8E /* OriginalStoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78095EC7128F30B500230C8E /* OriginalStoryViewController.m */; };
|
||||
7842ECF811D44A530066CF9D /* StoryDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7842ECF611D44A530066CF9D /* StoryDetailViewController.m */; };
|
||||
|
@ -666,6 +666,7 @@
|
|||
17042DBA23922A4D001BCD32 /* WidgetLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetLoader.swift; sourceTree = "<group>"; };
|
||||
1715D0292166B3F900227731 /* PremiumManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PremiumManager.h; sourceTree = "<group>"; };
|
||||
1715D02A2166B3F900227731 /* PremiumManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PremiumManager.m; sourceTree = "<group>"; };
|
||||
1721C9D02497F91900B0EDC4 /* mute_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mute_gray.png; sourceTree = "<group>"; };
|
||||
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OfflineFetchText.h; path = offline/OfflineFetchText.h; sourceTree = "<group>"; };
|
||||
17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = OfflineFetchText.m; path = offline/OfflineFetchText.m; sourceTree = "<group>"; };
|
||||
1740C6841C10FD75005EA453 /* theme_color_dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = theme_color_dark.png; sourceTree = "<group>"; };
|
||||
|
@ -1505,7 +1506,6 @@
|
|||
288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */,
|
||||
788EF356127E5BC80088EDC5 /* QuartzCore.framework in Frameworks */,
|
||||
78095E3F128EF35400230C8E /* CFNetwork.framework in Frameworks */,
|
||||
78095E43128EF37E00230C8E /* MobileCoreServices.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1930,6 +1930,7 @@
|
|||
1761295D1C630AEB00702FE4 /* mute_feed_off@2x.png */,
|
||||
1761295E1C630AEB00702FE4 /* mute_feed_on.png */,
|
||||
1761295F1C630AEB00702FE4 /* mute_feed_on@2x.png */,
|
||||
1721C9D02497F91900B0EDC4 /* mute_gray.png */,
|
||||
FF1104601769695A00502C29 /* g_icn_offline@2x.png */,
|
||||
FFC518B81768E59F00542719 /* g_icn_offline.png */,
|
||||
17AACFD822279A3900DE6EA4 /* autoscroll_off.png */,
|
||||
|
@ -3082,6 +3083,7 @@
|
|||
FF5F3A87162B834E008DBE3E /* bin_closed.png in Resources */,
|
||||
17AACFE622279A3C00DE6EA4 /* autoscroll_faster.png in Resources */,
|
||||
FF5F3A89162B8377008DBE3E /* arrow_branch.png in Resources */,
|
||||
1721C9D12497F91A00B0EDC4 /* mute_gray.png in Resources */,
|
||||
FF5F3A8B162B8390008DBE3E /* car.png in Resources */,
|
||||
FF41309D162CEC7100DDB6A7 /* time.png in Resources */,
|
||||
FF4130A0162CECAE00DDB6A7 /* email.png in Resources */,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -12,7 +12,6 @@
|
|||
<outlet property="activityIndicator" destination="BHa-eI-8aP" id="szf-ha-x2J"/>
|
||||
<outlet property="noStoryMessage" destination="bfM-JE-U7c" id="3Wi-jn-nk8"/>
|
||||
<outlet property="view" destination="1" id="55"/>
|
||||
<outlet property="webView" destination="JOM-Xx-504" id="HVw-7M-HDD"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
|
@ -40,15 +39,6 @@
|
|||
</subviews>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<wkWebView multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JOM-Xx-504">
|
||||
<rect key="frame" x="0.0" y="0.0" width="768" height="980"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<wkWebViewConfiguration key="configuration" allowsInlineMediaPlayback="YES">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" audio="YES" video="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="BHa-eI-8aP">
|
||||
<rect key="frame" x="366" y="507" width="37" height="37"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -11,7 +11,6 @@
|
|||
<connections>
|
||||
<outlet property="activityIndicator" destination="LBH-6T-jFu" id="zQr-GD-QKs"/>
|
||||
<outlet property="view" destination="1" id="75"/>
|
||||
<outlet property="webView" destination="UBf-r9-Jaj" id="9Sr-LL-rd6"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
|
@ -22,23 +21,11 @@
|
|||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="LBH-6T-jFu">
|
||||
<rect key="frame" x="141.5" y="211.5" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
<wkWebView multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UBf-r9-Jaj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<wkWebViewConfiguration key="configuration" allowsInlineMediaPlayback="YES">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" audio="YES" video="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="LBH-6T-jFu" firstAttribute="centerX" secondItem="1" secondAttribute="centerX" id="AIu-Em-rhG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="UBf-r9-Jaj" secondAttribute="trailing" id="GZU-zu-Bzs"/>
|
||||
<constraint firstItem="LBH-6T-jFu" firstAttribute="centerY" secondItem="1" secondAttribute="centerY" id="T4x-f0-Y9g"/>
|
||||
<constraint firstAttribute="bottom" secondItem="UBf-r9-Jaj" secondAttribute="bottom" id="dzb-XJ-owh"/>
|
||||
<constraint firstItem="UBf-r9-Jaj" firstAttribute="leading" secondItem="1" secondAttribute="leading" id="rnC-nC-QKW"/>
|
||||
<constraint firstItem="UBf-r9-Jaj" firstAttribute="top" secondItem="1" secondAttribute="top" id="v6G-Nq-MzW"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
|
|
|
@ -774,6 +774,28 @@
|
|||
<key>Key</key>
|
||||
<string>story_browser</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This setting only applies after the app is restarted.</string>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Play videos</string>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>Inline</string>
|
||||
<string>Full-screen</string>
|
||||
</array>
|
||||
<key>DefaultValue</key>
|
||||
<string>inline</string>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>inline</string>
|
||||
<string>fullscreen</string>
|
||||
</array>
|
||||
<key>Key</key>
|
||||
<string>video_playback</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
|
|
BIN
clients/ios/Resources/mute_gray.png
Normal file
BIN
clients/ios/Resources/mute_gray.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -137,6 +137,26 @@ del {
|
|||
background-color: #685253
|
||||
}
|
||||
|
||||
.NB-newsletter p,
|
||||
.NB-newsletter div,
|
||||
.NB-newsletter span,
|
||||
.NB-newsletter td {
|
||||
color: #c0c0c0 !important;
|
||||
background-color: #000000;
|
||||
}
|
||||
.NB-newsletter td {
|
||||
background-color: #191b1c;
|
||||
}
|
||||
.NB-newsletter h1,
|
||||
.NB-newsletter h2,
|
||||
.NB-newsletter h3,
|
||||
.NB-newsletter h4,
|
||||
.NB-newsletter h5,
|
||||
.NB-newsletter h6 {
|
||||
color: #c0c0c0 !important;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
|
||||
.NB-story-comments-group:last-child {
|
||||
|
|
|
@ -140,6 +140,26 @@ del {
|
|||
background-color: #685253
|
||||
}
|
||||
|
||||
.NB-newsletter p,
|
||||
.NB-newsletter div,
|
||||
.NB-newsletter span,
|
||||
.NB-newsletter td {
|
||||
color: #c0c0c0 !important;
|
||||
background-color: #444444;
|
||||
}
|
||||
.NB-newsletter td {
|
||||
background-color: #191b1c;
|
||||
}
|
||||
.NB-newsletter h1,
|
||||
.NB-newsletter h2,
|
||||
.NB-newsletter h3,
|
||||
.NB-newsletter h4,
|
||||
.NB-newsletter h5,
|
||||
.NB-newsletter h6 {
|
||||
color: #c0c0c0 !important;
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
|
||||
.NB-story-comments-group:last-child {
|
||||
|
|
|
@ -190,6 +190,21 @@ a:active {
|
|||
text-shadow: 0 1px 0 rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.NB-newsletter p,
|
||||
.NB-newsletter div,
|
||||
.NB-newsletter span,
|
||||
.NB-newsletter td {
|
||||
background-color: #FFFEE5;
|
||||
}
|
||||
.NB-newsletter h1,
|
||||
.NB-newsletter h2,
|
||||
.NB-newsletter h3,
|
||||
.NB-newsletter h4,
|
||||
.NB-newsletter h5,
|
||||
.NB-newsletter h6 {
|
||||
background-color: #FFFEE5;
|
||||
}
|
||||
|
||||
/* Story Comments */
|
||||
|
||||
#story_pane .NB-story-comment {
|
||||
|
|
|
@ -7,22 +7,6 @@
|
|||
"name": "NewsBlur"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "profile.profile",
|
||||
"fields": {
|
||||
"secret_token": "4a700381ea08",
|
||||
"feed_pane_size": 242,
|
||||
"last_seen_ip": null,
|
||||
"view_settings": "{}",
|
||||
"is_premium": false,
|
||||
"user": 1,
|
||||
"collapsed_folders": "[]",
|
||||
"timezone": "America/New_York",
|
||||
"last_seen_on": "2011-07-18 02:30:37",
|
||||
"preferences": "{}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "auth.user",
|
||||
|
@ -41,7 +25,23 @@
|
|||
"date_joined": "2011-07-18 00:23:49"
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "profile.profile",
|
||||
"fields": {
|
||||
"secret_token": "4a700381ea08",
|
||||
"feed_pane_size": 242,
|
||||
"last_seen_ip": null,
|
||||
"view_settings": "{}",
|
||||
"is_premium": false,
|
||||
"user": 1,
|
||||
"collapsed_folders": "[]",
|
||||
"timezone": "America/New_York",
|
||||
"last_seen_on": "2011-07-18 02:30:37",
|
||||
"preferences": "{}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "rss_feeds.feed",
|
||||
"fields": {
|
||||
|
@ -50,8 +50,8 @@
|
|||
"exception_code": 500,
|
||||
"last_load_time": 1,
|
||||
"active_subscribers": 1,
|
||||
"feed_address": "http://blog.newsblur.com/rss",
|
||||
"feed_link": "http://blog.newsblur.com/",
|
||||
"feed_address": "https://blog.newsblur.com/rss",
|
||||
"feed_link": "https://blog.newsblur.com/",
|
||||
"last_update": "2011-07-18 04:27:26",
|
||||
"etag": "",
|
||||
"average_stories_per_month": 0,
|
||||
|
|
135
config/nginx.local.conf
Normal file
135
config/nginx.local.conf
Normal file
|
@ -0,0 +1,135 @@
|
|||
upstream app_server {
|
||||
server 127.0.0.1:8000 fail_timeout=10 max_fails=3 ;
|
||||
}
|
||||
|
||||
upstream icon_server {
|
||||
server 127.0.0.1:3030 fail_timeout=2 max_fails=3;
|
||||
server 127.0.0.1:8000 backup;
|
||||
}
|
||||
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
server_name _;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl default_server;
|
||||
|
||||
ssl_certificate /srv/secrets-newsblur/certificates/newsblur.com.crt;
|
||||
ssl_certificate_key /srv/secrets-newsblur/certificates/newsblur.com.key;
|
||||
|
||||
client_max_body_size 4M;
|
||||
server_name *.nb.local.com nb.local.com;
|
||||
add_header X-nginx-server nginx_none;
|
||||
|
||||
# if ($host = 'newsblur.com') {
|
||||
# rewrite ^/(.*)$ https://www.newsblur.com/$1 permanent;
|
||||
# }
|
||||
|
||||
if (-f /srv/newsblur/templates/maintenance_on.html) {
|
||||
return 503;
|
||||
}
|
||||
|
||||
error_page 502 @down;
|
||||
location @down {
|
||||
root /srv/newsblur/;
|
||||
rewrite ^(.*)$ /templates/502.html break;
|
||||
}
|
||||
|
||||
error_page 503 @maintenance;
|
||||
location @maintenance {
|
||||
if ($uri !~ ^/media/) {
|
||||
root /srv/newsblur/;
|
||||
rewrite ^(.*)$ /templates/maintenance_on.html break;
|
||||
}
|
||||
root /srv/newsblur;
|
||||
}
|
||||
|
||||
error_page 504 @timeout;
|
||||
location @timeout {
|
||||
root /srv/newsblur/;
|
||||
rewrite ^(.*)$ /templates/502.html break;
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
expires max;
|
||||
keepalive_timeout 1;
|
||||
root /srv/newsblur;
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
expires max;
|
||||
keepalive_timeout 1;
|
||||
root /srv/newsblur;
|
||||
}
|
||||
|
||||
location /favicon.ico {
|
||||
alias /srv/newsblur/media/img/favicon_32.png;
|
||||
expires max;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location /maintenance {
|
||||
alias /srv/newsblur/templates/maintenance_on.html;
|
||||
expires max;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location ^~ /crossdomain.xml {
|
||||
expires max;
|
||||
alias /srv/newsblur/media/crossdomain.xml;
|
||||
types {
|
||||
text/x-cross-domain-policy xml;
|
||||
}
|
||||
}
|
||||
|
||||
location ^~ /robots.txt {
|
||||
expires max;
|
||||
alias /srv/newsblur/media/robots.txt;
|
||||
}
|
||||
|
||||
location /munin/static/ {
|
||||
alias /etc/munin/static/;
|
||||
}
|
||||
|
||||
location /munin/ {
|
||||
# alias /var/cache/munin/www/;
|
||||
fastcgi_split_path_info ^(/munin)(.*);
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_pass unix:/var/run/munin/fcgi-html.sock;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ^~ /cgi-bin/munin-cgi-graph/ {
|
||||
fastcgi_split_path_info ^(/cgi-bin/munin-cgi-graph)(.*);
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_pass unix:/var/run/munin/fcgi-graph.sock;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ^~ /rss_feeds/icon/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://icon_server;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_redirect off;
|
||||
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://app_server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
169
local_settings.py.template
Normal file
169
local_settings.py.template
Normal file
|
@ -0,0 +1,169 @@
|
|||
import logging
|
||||
import pymongo
|
||||
|
||||
# ===================
|
||||
# = Server Settings =
|
||||
# ===================
|
||||
|
||||
ADMINS = (
|
||||
('Samuel Clay', 'samuel@newsblur.com'),
|
||||
)
|
||||
|
||||
SERVER_EMAIL = 'server@newsblur.com'
|
||||
HELLO_EMAIL = 'hello@newsblur.com'
|
||||
NEWSBLUR_URL = 'http://www.newsblur.com'
|
||||
SESSION_COOKIE_DOMAIN = '.localhost'
|
||||
|
||||
# ===================
|
||||
# = Global Settings =
|
||||
# ===================
|
||||
|
||||
DEBUG = True
|
||||
DEBUG_ASSETS = DEBUG
|
||||
MEDIA_URL = '/media/'
|
||||
SECRET_KEY = 'YOUR SECRET KEY'
|
||||
AUTO_PREMIUM_NEW_USERS = True
|
||||
AUTO_ENABLE_NEW_USERS = True
|
||||
ENFORCE_SIGNUP_CAPTCHA = False
|
||||
|
||||
# CACHE_BACKEND = 'dummy:///'
|
||||
# CACHE_BACKEND = 'locmem:///'
|
||||
# CACHE_BACKEND = 'memcached://127.0.0.1:11211'
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'redis_cache.RedisCache',
|
||||
'LOCATION': '127.0.0.1:6379',
|
||||
'OPTIONS': {
|
||||
'DB': 6,
|
||||
'PARSER_CLASS': 'redis.connection.HiredisParser'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
# Set this to the username that is shown on the homepage to unauthenticated users.
|
||||
HOMEPAGE_USERNAME = 'popular'
|
||||
|
||||
# Google Reader OAuth API Keys
|
||||
OAUTH_KEY = 'www.example.com'
|
||||
OAUTH_SECRET = 'SECRET_KEY_FROM_GOOGLE'
|
||||
|
||||
S3_ACCESS_KEY = 'XXX'
|
||||
S3_SECRET = 'SECRET'
|
||||
S3_BACKUP_BUCKET = 'newsblur_backups'
|
||||
S3_PAGES_BUCKET_NAME = 'pages-XXX.newsblur.com'
|
||||
S3_ICONS_BUCKET_NAME = 'icons-XXX.newsblur.com'
|
||||
|
||||
STRIPE_SECRET = "YOUR-SECRET-API-KEY"
|
||||
STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY"
|
||||
|
||||
# ===============
|
||||
# = Social APIs =
|
||||
# ===============
|
||||
|
||||
FACEBOOK_APP_ID = '111111111111111'
|
||||
FACEBOOK_SECRET = '99999999999999999999999999999999'
|
||||
TWITTER_CONSUMER_KEY = 'ooooooooooooooooooooo'
|
||||
TWITTER_CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
|
||||
YOUTUBE_API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
|
||||
# =============
|
||||
# = Databases =
|
||||
# =============
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'NAME': 'newsblur',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'USER': 'newsblur',
|
||||
'PASSWORD': '',
|
||||
'HOST': '127.0.0.1',
|
||||
'OPTIONS': {
|
||||
"autocommit": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
MONGO_DB = {
|
||||
'name': 'newsblur',
|
||||
'host': '127.0.0.1',
|
||||
'port': 27017
|
||||
}
|
||||
MONGO_ANALYTICS_DB = {
|
||||
'name': 'nbanalytics',
|
||||
'host': '128.0.0.1',
|
||||
'port': 27017,
|
||||
}
|
||||
|
||||
MONGODB_SLAVE = {
|
||||
'host': '127.0.0.1'
|
||||
}
|
||||
|
||||
# Celery RabbitMQ/Redis Broker
|
||||
BROKER_URL = "redis://127.0.0.1:6379/0"
|
||||
CELERY_RESULT_BACKEND = BROKER_URL
|
||||
|
||||
REDIS = {
|
||||
'host': '127.0.0.1',
|
||||
}
|
||||
REDIS_PUBSUB = {
|
||||
'host': '127.0.0.1',
|
||||
}
|
||||
REDIS_STORY = {
|
||||
'host': '127.0.0.1',
|
||||
}
|
||||
REDIS_SESSIONS = {
|
||||
'host': '127.0.0.1',
|
||||
'port': 6379
|
||||
}
|
||||
|
||||
ELASTICSEARCH_FEED_HOSTS = ["127.0.0.1:9200"]
|
||||
ELASTICSEARCH_STORY_HOSTS = ["127.0.0.1:9200"]
|
||||
|
||||
BACKED_BY_AWS = {
|
||||
'pages_on_node': False,
|
||||
'pages_on_s3': False,
|
||||
'icons_on_s3': False,
|
||||
}
|
||||
|
||||
ORIGINAL_PAGE_SERVER = "127.0.0.1:3060"
|
||||
|
||||
# ===========
|
||||
# = Logging =
|
||||
# ===========
|
||||
|
||||
# Logging (setup for development)
|
||||
LOG_TO_STREAM = True
|
||||
|
||||
if len(logging._handlerList) < 1:
|
||||
LOG_FILE = '~/newsblur/logs/development.log'
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)-12s: %(message)s',
|
||||
datefmt='%b %d %H:%M:%S',
|
||||
handler=logging.StreamHandler)
|
||||
|
||||
S3_ACCESS_KEY = '000000000000000000000'
|
||||
S3_SECRET = '000000000000000000000000/0000000000000000'
|
||||
S3_BACKUP_BUCKET = 'newsblur_backups'
|
||||
S3_PAGES_BUCKET_NAME = 'pages-dev.newsblur.com'
|
||||
S3_ICONS_BUCKET_NAME = 'icons-dev.newsblur.com'
|
||||
S3_AVATARS_BUCKET_NAME = 'avatars-dev.newsblur.com'
|
||||
|
||||
MAILGUN_ACCESS_KEY = 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
MAILGUN_SERVER_NAME = 'newsblur.com'
|
||||
|
||||
DO_TOKEN_LOG = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
DO_TOKEN_FABRIC = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
SERVER_NAME = "nblocalhost"
|
||||
NEWSBLUR_URL = 'http://nb.local.com'
|
||||
|
||||
SESSION_ENGINE = 'redis_sessions.session'
|
||||
|
||||
# CORS_ORIGIN_REGEX_WHITELIST = ('^(https?://)?(\w+\.)?nb.local\.com$', )
|
||||
|
||||
YOUTUBE_API_KEY = "000000000000000000000000000000000000000"
|
||||
RECAPTCHA_SECRET_KEY = "0000000000000000000000000000000000000000"
|
||||
IMAGES_SECRET_KEY = "0000000000000000000000000000000"
|
|
@ -2,6 +2,12 @@
|
|||
/* = Resets = */
|
||||
/* ========== */
|
||||
|
||||
#simplemodal-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.NB-modal {
|
||||
padding: 0;
|
||||
}
|
||||
.NB-modal p,
|
||||
.NB-modal ol,
|
||||
.NB-modal ul,
|
||||
|
@ -100,7 +106,7 @@
|
|||
width: 514px;
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
border-left: 6px solid #38457E;
|
||||
border-left: 2px solid #F0F0F0;
|
||||
margin: 24px 0 0;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-side {
|
||||
|
@ -181,7 +187,7 @@
|
|||
width: 8px;
|
||||
}
|
||||
.NB-bookmarklet .NB-folders {
|
||||
width: 186px;
|
||||
width: 174px;
|
||||
}
|
||||
.NB-bookmarklet .NB-modal-submit {
|
||||
margin: 12px 0 8px;
|
||||
|
@ -193,7 +199,6 @@
|
|||
clear: both;
|
||||
padding: 0 16px;
|
||||
line-height: 24px;
|
||||
width: 176px;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
font-weight: normal;
|
||||
|
@ -213,6 +218,9 @@
|
|||
float: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-button-subscribe {
|
||||
width: 210px;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-error {
|
||||
margin: 0;
|
||||
padding: 0 12px 0 24px;
|
||||
|
@ -270,14 +278,13 @@
|
|||
.NB-bookmarklet .NB-bookmarklet-page-comment {
|
||||
margin-top: 12px;
|
||||
/* border-top: 1px solid #F0F0F0;*/
|
||||
padding: 12px 0 0 88px;
|
||||
padding: 12px 0 0 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-photo {
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
top: 12px;
|
||||
margin: 0 12px 0 0;
|
||||
float: left;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-photo img {
|
||||
border-radius: 6px;
|
||||
|
@ -288,37 +295,68 @@
|
|||
float: left;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-input textarea {
|
||||
width: 300px;
|
||||
float: left;
|
||||
width: 160px;
|
||||
border: 1px solid #E0E0E0;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif !important;
|
||||
color: #404040;
|
||||
font-size: 13px;
|
||||
height: 34px;
|
||||
height: 98px;
|
||||
background-color: white;
|
||||
display: block;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit {
|
||||
float: left;
|
||||
clear: none;
|
||||
width: 98px;
|
||||
height: 32px;
|
||||
margin: 0 0 4px 12px;
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit,
|
||||
.NB-bookmarklet .NB-bookmarklet-save-button {
|
||||
clear: both;
|
||||
margin: 4px 0 4px 73px;
|
||||
padding: 8px 0;
|
||||
line-height: 16px;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit.NB-disabled {
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit {
|
||||
width: 160px;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-save-button {
|
||||
margin-left: 0;
|
||||
width: 220px;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit.NB-disabled,
|
||||
.NB-bookmarklet .NB-bookmarklet-save.NB-disabled {
|
||||
padding: 14px 0 0;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept {
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept,
|
||||
.NB-bookmarklet .NB-bookmarklet-save .NB-bookmarklet-accept {
|
||||
padding: 0 12px;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept img {
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept img,
|
||||
.NB-bookmarklet .NB-bookmarklet-save .NB-bookmarklet-accept img {
|
||||
vertical-align: top;
|
||||
margin: 0;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-comment-separator {
|
||||
float: left;
|
||||
width: 1px;
|
||||
height: 48px;
|
||||
background-color: #e0e0e0;
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-user-tags select {
|
||||
width: 220px;
|
||||
height: 72px;
|
||||
margin: 0 12px 0 0;
|
||||
}
|
||||
|
||||
.NB-bookmarklet .NB-bookmarklet-add-tag {
|
||||
width: 220px;
|
||||
margin: 4px 12px 0 0;
|
||||
}
|
||||
.NB-bookmarklet-submit-left,
|
||||
.NB-bookmarklet-submit-right {
|
||||
float: left;
|
||||
}
|
||||
/* =============== */
|
||||
/* = Side Layout = */
|
||||
/* =============== */
|
||||
|
@ -334,12 +372,13 @@
|
|||
}
|
||||
.NB-bookmarklet .NB-bookmarklet-side-loading {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
color: #C0C0C0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
line-height: 18px;
|
||||
padding: 48px 0 0;
|
||||
height: 400px;
|
||||
}
|
||||
.NB-bookmarklet .NB-subscribe-loader {
|
||||
margin: 0 auto;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue