Merge branch 'master' into organizer

* master: (115 commits)
  Fixing ios images. Submitting v3.5.
  Fixing mongo btree munin graph.
  Adding Swift Reader to Goodies.
  Speeding up scrolling on feed list.
  Support navigating back to home page from action bar in Preferences
  Redrawing intelligence slider on ios.
  Normal profile.
  V3.0.4 (RC3)
  Massively speeding up the viewWillAppear on the feed list.
  RC2 (or is it RC3 by now?) of iOS app.
  Fixing a crash on mark read.
  Fixing broken story page control.
  Adding Pinterest.
  Tightening up comments line height.
  Fixing bug around loading feeds when there's no offline db.
  Correctly render DB changes in folder list more quickly.
  Fixing empty story hashes.
  Cleaning up kill utils.
  Fixing hiding and showing of notifier on fetch.
  iOS7 app RC2
  ...
This commit is contained in:
Samuel Clay 2013-10-29 13:24:46 -07:00
commit 1841b0eac5
294 changed files with 9763 additions and 68563 deletions

View file

@ -57,11 +57,15 @@ class Profile(models.Model):
return "%s <%s> (Premium: %s)" % (self.user, self.user.email, self.is_premium)
@property
def unread_cutoff(self):
if self.is_premium:
def unread_cutoff(self, force_premium=False):
if self.is_premium or force_premium:
return datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
return datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD_FREE)
@property
def unread_cutoff_premium(self):
return datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
def canonical(self):
return {

View file

@ -1,5 +1,6 @@
import datetime
import time
import re
import redis
from utils import log as logging
from utils import json_functions as json
@ -150,9 +151,10 @@ class UserSubscription(models.Model):
pipeline.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
byscorefunc(unread_ranked_stories_key, min_score, max_score, withscores=include_timestamps)
pipeline.expire(unread_ranked_stories_key, 60*60)
pipeline.delete(unread_ranked_stories_key)
if expire_unread_stories_key:
pipeline.delete(unread_stories_key)
results = pipeline.execute()
@ -163,12 +165,13 @@ class UserSubscription(models.Model):
feed_counter += 1
else:
story_hashes.extend(hashes)
return story_hashes
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all', withscores=False,
hashes_only=False, cutoff_date=None):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
rt = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_TEMP_POOL)
ignore_user_stories = False
stories_key = 'F:%s' % (self.feed_id)
@ -177,34 +180,42 @@ class UserSubscription(models.Model):
unread_ranked_stories_key = 'z%sU:%s:%s' % ('h' if hashes_only else '',
self.user_id, self.feed_id)
if offset and not withscores and r.exists(unread_ranked_stories_key):
pass
else:
r.delete(unread_ranked_stories_key)
if withscores or not offset or not rt.exists(unread_ranked_stories_key):
rt.delete(unread_ranked_stories_key)
if not r.exists(stories_key):
# print " ---> No stories on feed: %s" % self
return []
elif read_filter != 'unread' or not r.exists(read_stories_key):
elif read_filter == 'all' or not r.exists(read_stories_key):
ignore_user_stories = True
unread_stories_key = stories_key
else:
r.sdiffstore(unread_stories_key, stories_key, read_stories_key)
sorted_stories_key = 'zF:%s' % (self.feed_id)
r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
if not ignore_user_stories:
r.delete(unread_stories_key)
dump = r.dump(unread_ranked_stories_key)
if dump:
pipeline = rt.pipeline()
pipeline.delete(unread_ranked_stories_key)
pipeline.restore(unread_ranked_stories_key, 1*60*60*1000, dump)
pipeline.execute()
r.delete(unread_ranked_stories_key)
current_time = int(time.time() + 60*60*24)
current_time = int(time.time() + 60*60*24)
if not cutoff_date:
cutoff_date = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
if order == 'oldest':
byscorefunc = r.zrangebyscore
byscorefunc = rt.zrangebyscore
if read_filter == 'unread':
min_score = int(time.mktime(self.mark_read_date.timetuple())) + 1
else:
min_score = int(time.mktime(cutoff_date.timetuple()))-1000
max_score = current_time
else:
byscorefunc = r.zrevrangebyscore
byscorefunc = rt.zrevrangebyscore
min_score = current_time
if read_filter == 'unread':
# +1 for the intersection b/w zF and F, which carries an implicit score of 1.
@ -213,7 +224,7 @@ class UserSubscription(models.Model):
max_score = 0
if settings.DEBUG and False:
debug_stories = r.zrevrange(unread_ranked_stories_key, 0, -1, withscores=True)
debug_stories = rt.zrevrange(unread_ranked_stories_key, 0, -1, withscores=True)
print " ---> Unread all stories (%s - %s) %s stories: %s" % (
min_score,
max_score,
@ -222,10 +233,7 @@ class UserSubscription(models.Model):
story_ids = byscorefunc(unread_ranked_stories_key, min_score,
max_score, start=offset, num=500,
withscores=withscores)[:limit]
r.expire(unread_ranked_stories_key, 1*60*60)
if not ignore_user_stories:
r.delete(unread_stories_key)
if withscores:
story_ids = [(s[0], int(s[1])) for s in story_ids]
@ -241,23 +249,26 @@ class UserSubscription(models.Model):
@classmethod
def feed_stories(cls, user_id, feed_ids=None, offset=0, limit=6,
order='newest', read_filter='all', usersubs=None, cutoff_date=None):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
order='newest', read_filter='all', usersubs=None, cutoff_date=None,
all_feed_ids=None):
rt = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_TEMP_POOL)
if order == 'oldest':
range_func = r.zrange
range_func = rt.zrange
else:
range_func = r.zrevrange
range_func = rt.zrevrange
if not feed_ids:
feed_ids = []
if not all_feed_ids:
all_feed_ids = [f for f in feed_ids]
# feeds_string = ""
feeds_string = ','.join(str(f) for f in sorted(feed_ids))[:30]
ranked_stories_keys = 'zU:%s:feeds:%s' % (user_id, feeds_string)
feeds_string = ','.join(str(f) for f in sorted(all_feed_ids))[:30]
ranked_stories_keys = 'zU:%s:feeds:%s' % (user_id, feeds_string)
unread_ranked_stories_keys = 'zhU:%s:feeds:%s' % (user_id, feeds_string)
stories_cached = r.exists(ranked_stories_keys)
unreads_cached = True if read_filter == "unread" else r.exists(unread_ranked_stories_keys)
stories_cached = rt.exists(ranked_stories_keys)
unreads_cached = True if read_filter == "unread" else rt.exists(unread_ranked_stories_keys)
if offset and stories_cached and unreads_cached:
story_hashes = range_func(ranked_stories_keys, offset, limit)
if read_filter == "unread":
@ -266,23 +277,27 @@ class UserSubscription(models.Model):
unread_story_hashes = range_func(unread_ranked_stories_keys, 0, offset+limit)
return story_hashes, unread_story_hashes
else:
r.delete(ranked_stories_keys)
r.delete(unread_ranked_stories_keys)
rt.delete(ranked_stories_keys)
rt.delete(unread_ranked_stories_keys)
story_hashes = cls.story_hashes(user_id, feed_ids=feed_ids,
read_filter=read_filter, order=order,
include_timestamps=True,
group_by_feed=False, usersubs=usersubs,
group_by_feed=False,
usersubs=usersubs,
cutoff_date=cutoff_date)
if not story_hashes:
return [], []
pipeline = rt.pipeline()
for story_hash_group in chunks(story_hashes, 100):
r.zadd(ranked_stories_keys, **dict(story_hash_group))
pipeline.zadd(ranked_stories_keys, **dict(story_hash_group))
pipeline.execute()
story_hashes = range_func(ranked_stories_keys, offset, limit)
if read_filter == "unread":
unread_feed_story_hashes = story_hashes
rt.zunionstore(unread_ranked_stories_keys, [ranked_stories_keys])
else:
unread_story_hashes = cls.story_hashes(user_id, feed_ids=feed_ids,
read_filter="unread", order=order,
@ -291,11 +306,11 @@ class UserSubscription(models.Model):
cutoff_date=cutoff_date)
if unread_story_hashes:
for unread_story_hash_group in chunks(unread_story_hashes, 100):
r.zadd(unread_ranked_stories_keys, **dict(unread_story_hash_group))
rt.zadd(unread_ranked_stories_keys, **dict(unread_story_hash_group))
unread_feed_story_hashes = range_func(unread_ranked_stories_keys, offset, limit)
r.expire(ranked_stories_keys, 60*60)
r.expire(unread_ranked_stories_keys, 60*60)
rt.expire(ranked_stories_keys, 60*60)
rt.expire(unread_ranked_stories_keys, 60*60)
return story_hashes, unread_feed_story_hashes
@ -407,6 +422,42 @@ class UserSubscription(models.Model):
r.srem(read_stories_key, *stale_story_hashes)
r.srem("RS:%s" % self.feed_id, *stale_story_hashes)
@classmethod
def trim_user_read_stories(self, user_id):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
subs = UserSubscription.objects.filter(user_id=user_id).only('feed')
if not subs: return
feeds = [f.feed_id for f in subs]
old_rs = r.smembers("RS:%s" % user_id)
old_count = len(old_rs)
# new_rs = r.sunionstore("RS:%s" % user_id, *["RS:%s:%s" % (user_id, f) for f in feeds])
new_rs = r.sunion(*["RS:%s:%s" % (user_id, f) for f in feeds])
if not old_count: return
r.sunionstore("RS:%s:backup" % user_id, "RS:%s" % user_id)
r.expire("RS:%s:backup" % user_id, 60*60*24)
missing_rs = []
feed_re = re.compile(r'(\d+):.*?')
for rs in old_rs:
found = feed_re.search(rs)
if not found:
print " ---> Not found: %s" % rs
continue
rs_feed_id = found.groups()[0]
if int(rs_feed_id) not in feeds:
missing_rs.append(rs)
# r.sadd("RS:%s" % user_id, *missing_rs)
new_count = len(new_rs)
missing_count = len(missing_rs)
new_total = new_count + missing_count
user = User.objects.get(pk=user_id)
logging.user(user, "~FBTrimming ~FR%s~FB/%s (~SB%s~SN+~SB%s~SN saved) user read stories..." %
(old_count - new_total, old_count, new_count, missing_count))
def mark_feed_read(self, cutoff_date=None):
if (self.unread_count_negative == 0
and self.unread_count_neutral == 0
@ -419,7 +470,8 @@ 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).order_by('-story_date').only('story_date').limit(1)
latest_story = MStory.objects(story_feed_id=self.feed.pk)\
.order_by('-story_date').only('story_date').limit(1)
if latest_story and len(latest_story) >= 1:
cutoff_date = (latest_story[0]['story_date']
+ datetime.timedelta(seconds=1))
@ -496,8 +548,10 @@ class UserSubscription(models.Model):
if not stories:
stories = cache.get('S:%s' % self.feed_id)
unread_story_hashes = self.get_stories(read_filter='unread', limit=500, hashes_only=True,
cutoff_date=self.user.profile.unread_cutoff)
unread_story_hashes = self.story_hashes(user_id=self.user_id, feed_ids=[self.feed_id],
usersubs=[self],
read_filter='unread', group_by_feed=False,
cutoff_date=self.user.profile.unread_cutoff)
if not stories:
stories_db = MStory.objects(story_hash__in=unread_story_hashes)
@ -554,9 +608,12 @@ class UserSubscription(models.Model):
else:
feed_scores['neutral'] += 1
else:
unread_story_hashes = self.get_stories(read_filter='unread', limit=500, hashes_only=True,
withscores=True,
cutoff_date=self.user.profile.unread_cutoff)
unread_story_hashes = self.story_hashes(user_id=self.user_id, feed_ids=[self.feed_id],
usersubs=[self],
read_filter='unread', group_by_feed=False,
include_timestamps=True,
cutoff_date=self.user.profile.unread_cutoff)
feed_scores['neutral'] = len(unread_story_hashes)
if feed_scores['neutral']:
oldest_unread_story_date = datetime.datetime.fromtimestamp(unread_story_hashes[-1][1])

View file

@ -172,7 +172,7 @@ def signup(request):
url = "https://%s%s" % (Site.objects.get_current().domain,
reverse('stripe-form'))
return HttpResponseRedirect(url)
return index(request)
@never_cache
@ -307,7 +307,7 @@ def load_feeds_flat(request):
feeds = {}
flat_folders = {" ": []}
iphone_version = "2.1"
iphone_version = "3.0"
if include_favicons == 'false': include_favicons = False
if update_counts == 'false': update_counts = False
@ -583,7 +583,10 @@ def load_single_feed(request, feed_id):
unread_story_hashes = []
if stories:
if (read_filter == 'all' or query) and usersub:
unread_story_hashes = usersub.get_stories(read_filter='unread', limit=500, hashes_only=True,
unread_story_hashes = UserSubscription.story_hashes(user.pk, read_filter='unread',
feed_ids=[usersub.feed_id],
usersubs=[usersub],
group_by_feed=False,
cutoff_date=user.profile.unread_cutoff)
story_hashes = [story['story_hash'] for story in stories]
starred_stories = MStarredStory.objects(user_id=user.pk,
@ -604,7 +607,7 @@ def load_single_feed(request, feed_id):
if not include_story_content:
del story['story_content']
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if usersub:
story['read_status'] = 1
@ -674,9 +677,10 @@ def load_single_feed(request, feed_id):
# if page <= 1:
# import random
# if random.random() < .5:
# assert False
# time.sleep(random.randint(0, 3))
# if page == 2:
# assert False
return data
@ -780,7 +784,7 @@ def load_starred_stories(request):
for story in stories:
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
starred_date = localtime_for_timezone(story['starred_date'], user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
@ -869,11 +873,13 @@ def load_river_stories__redis(request):
else:
usersubs = UserSubscription.subs_for_feeds(user.pk, feed_ids=feed_ids,
read_filter=read_filter)
all_feed_ids = [f for f in feed_ids]
feed_ids = [sub.feed_id for sub in usersubs]
if feed_ids:
params = {
"user_id": user.pk,
"feed_ids": feed_ids,
"all_feed_ids": all_feed_ids,
"offset": offset,
"limit": limit,
"order": order,
@ -940,7 +946,7 @@ def load_river_stories__redis(request):
story['story_hash'] not in unread_feed_story_hashes):
story['read_status'] = 1
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['story_hash'] in starred_stories:
story['starred'] = True
@ -1251,9 +1257,16 @@ def mark_story_as_unread(request):
if story.story_date < request.user.profile.unread_cutoff:
data['code'] = -1
data['message'] = "Story is more than %s days old, cannot mark as unread." % (
settings.DAYS_OF_UNREAD if request.user.profile.is_premium else
settings.DAYS_OF_UNREAD_FREE)
if request.user.profile.is_premium:
data['message'] = "Story is more than %s days old, cannot mark as unread." % (
settings.DAYS_OF_UNREAD)
elif story.story_date > request.user.profile.unread_cutoff_premium:
data['message'] = "Story is more than %s days old. Premiums can mark unread up to 30 days." % (
settings.DAYS_OF_UNREAD_FREE)
else:
data['message'] = "Story is more than %s days old, cannot mark as unread." % (
settings.DAYS_OF_UNREAD_FREE)
return data
social_subs = MSocialSubscription.mark_dirty_sharing_story(user_id=request.user.pk,
story_feed_id=feed_id,

View file

@ -46,7 +46,10 @@ class IconImporter(object):
# print "Bad .ICO"
pass
image = self.normalize_image(image)
color = self.determine_dominant_color_in_image(image)
try:
color = self.determine_dominant_color_in_image(image)
except IndexError:
return
try:
image_str = self.string_from_image(image)
except TypeError:

View file

@ -431,7 +431,7 @@ def original_text(request):
story_id = request.REQUEST.get('story_id')
feed_id = request.REQUEST.get('feed_id')
force = request.REQUEST.get('force', False)
story, _ = MStory.find_story(story_id=story_id, story_feed_id=feed_id)
if not story:

View file

@ -29,7 +29,7 @@ from vendor import appdotnet
from vendor import pynliner
from utils import log as logging
from utils import json_functions as json
from utils.feed_functions import relative_timesince
from utils.feed_functions import relative_timesince, chunks
from utils.story_functions import truncate_chars, strip_tags, linkify, image_size
from utils.scrubber import SelectiveScriptScrubber
from utils import s3_utils
@ -862,7 +862,95 @@ class MSocialSubscription(mongo.Document):
'is_trained': self.is_trained,
'feed_opens': self.feed_opens,
}
@classmethod
def subs_for_users(cls, user_id, subscription_user_ids=None, read_filter="unread"):
socialsubs = cls.objects
if read_filter == "unread":
socialsubs = socialsubs.filter(Q(unread_count_neutral__gt=0) |
Q(unread_count_positive__gt=0))
if not subscription_user_ids:
socialsubs = socialsubs.filter(user_id=user_id)\
.only('subscription_user_id', 'mark_read_date', 'is_trained')
else:
socialsubs = socialsubs.filter(user_id=user_id,
subscription_user_id__in=subscription_user_ids)\
.only('subscription_user_id', 'mark_read_date', 'is_trained')
return socialsubs
@classmethod
def story_hashes(cls, user_id, relative_user_id, subscription_user_ids=None, socialsubs=None,
read_filter="unread", order="newest",
include_timestamps=False, group_by_user=True, cutoff_date=None):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
pipeline = r.pipeline()
story_hashes = {} if group_by_user else []
if not socialsubs:
socialsubs = cls.subs_for_users(relative_user_id,
subscription_user_ids=subscription_user_ids,
read_filter=read_filter)
subscription_user_ids = [sub.subscription_user_id for sub in socialsubs]
if not subscription_user_ids:
return story_hashes
read_dates = dict((us.subscription_user_id,
int(us.mark_read_date.strftime('%s'))) for us in socialsubs)
current_time = int(time.time() + 60*60*24)
if not cutoff_date:
cutoff_date = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_STORY_HASHES)
unread_timestamp = int(time.mktime(cutoff_date.timetuple()))-1000
feed_counter = 0
for sub_user_id_group in chunks(subscription_user_ids, 20):
pipeline = r.pipeline()
for sub_user_id in sub_user_id_group:
stories_key = 'B:%s' % (sub_user_id)
sorted_stories_key = 'zB:%s' % (sub_user_id)
read_stories_key = 'RS:%s' % (user_id)
read_social_stories_key = 'RS:%s:B:%s' % (user_id, sub_user_id)
unread_stories_key = 'UB:%s:%s' % (user_id, sub_user_id)
sorted_stories_key = 'zB:%s' % (sub_user_id)
unread_ranked_stories_key = 'zUB:%s:%s' % (user_id, sub_user_id)
expire_unread_stories_key = False
max_score = current_time
if read_filter == 'unread':
# +1 for the intersection b/w zF and F, which carries an implicit score of 1.
min_score = read_dates[sub_user_id] + 1
pipeline.sdiffstore(unread_stories_key, stories_key, read_stories_key)
pipeline.sdiffstore(unread_stories_key, unread_stories_key, read_social_stories_key)
expire_unread_stories_key = True
else:
min_score = unread_timestamp
unread_stories_key = stories_key
if order == 'oldest':
byscorefunc = pipeline.zrangebyscore
else:
byscorefunc = pipeline.zrevrangebyscore
min_score, max_score = max_score, min_score
pipeline.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
byscorefunc(unread_ranked_stories_key, min_score, max_score, withscores=include_timestamps)
pipeline.delete(unread_ranked_stories_key)
if expire_unread_stories_key:
pipeline.delete(unread_stories_key)
results = pipeline.execute()
for hashes in results:
if not isinstance(hashes, list): continue
if group_by_user:
story_hashes[subscription_user_ids[feed_counter]] = hashes
feed_counter += 1
else:
story_hashes.extend(hashes)
return story_hashes
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all',
withscores=False, hashes_only=False, cutoff_date=None,
mark_read_complement=False):
@ -926,56 +1014,80 @@ class MSocialSubscription(mongo.Document):
@classmethod
def feed_stories(cls, user_id, social_user_ids, offset=0, limit=6,
order='newest', read_filter='all', relative_user_id=None, cache=True,
cutoff_date=None):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
socialsubs=None, cutoff_date=None):
rt = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_TEMP_POOL)
if not relative_user_id:
relative_user_id = user_id
if order == 'oldest':
range_func = r.zrange
range_func = rt.zrange
else:
range_func = r.zrevrange
range_func = rt.zrevrange
if not isinstance(social_user_ids, list):
social_user_ids = [social_user_ids]
ranked_stories_keys = 'zU:%s:social' % (user_id)
read_ranked_stories_keys = 'zhU:%s:social' % (user_id)
unread_ranked_stories_keys = 'zhU:%s:social' % (user_id)
if (offset and cache and
r.exists(ranked_stories_keys) and
r.exists(read_ranked_stories_keys)):
story_hashes = range_func(ranked_stories_keys, offset, offset+limit, withscores=True)
read_story_hashes = range_func(read_ranked_stories_keys, 0, -1)
if story_hashes:
story_hashes, story_dates = zip(*story_hashes)
return story_hashes, story_dates, read_story_hashes
else:
rt.exists(ranked_stories_keys) and
rt.exists(unread_ranked_stories_keys)):
story_hashes_and_dates = range_func(ranked_stories_keys, offset, limit, withscores=True)
if not story_hashes_and_dates:
return [], [], []
story_hashes, story_dates = zip(*story_hashes_and_dates)
if read_filter == "unread":
unread_story_hashes = story_hashes
else:
unread_story_hashes = range_func(unread_ranked_stories_keys, 0, offset+limit)
return story_hashes, story_dates, unread_story_hashes
else:
r.delete(ranked_stories_keys)
r.delete(read_ranked_stories_keys)
rt.delete(ranked_stories_keys)
rt.delete(unread_ranked_stories_keys)
for social_user_id in social_user_ids:
us = cls.objects.get(user_id=relative_user_id, subscription_user_id=social_user_id)
story_hashes = us.get_stories(offset=0, limit=100,
order=order, read_filter=read_filter,
withscores=True, cutoff_date=cutoff_date)
if story_hashes:
r.zadd(ranked_stories_keys, **dict(story_hashes))
r.zinterstore(read_ranked_stories_keys, [ranked_stories_keys, "RS:%s" % user_id])
story_hashes = range_func(ranked_stories_keys, offset, limit, withscores=True)
read_story_hashes = range_func(read_ranked_stories_keys, offset, limit)
r.expire(ranked_stories_keys, 1*60*60)
r.expire(read_ranked_stories_keys, 1*60*60)
if story_hashes:
story_hashes, story_dates = zip(*story_hashes)
return story_hashes, story_dates, read_story_hashes
else:
story_hashes = cls.story_hashes(user_id, relative_user_id,
subscription_user_ids=social_user_ids,
read_filter=read_filter, order=order,
include_timestamps=True,
group_by_user=False,
socialsubs=socialsubs,
cutoff_date=cutoff_date)
if not story_hashes:
return [], [], []
pipeline = rt.pipeline()
for story_hash_group in chunks(story_hashes, 100):
pipeline.zadd(ranked_stories_keys, **dict(story_hash_group))
pipeline.execute()
story_hashes_and_dates = range_func(ranked_stories_keys, offset, limit, withscores=True)
if not story_hashes_and_dates:
return [], [], []
story_hashes, story_dates = zip(*story_hashes_and_dates)
if read_filter == "unread":
unread_feed_story_hashes = story_hashes
rt.zunionstore(unread_ranked_stories_keys, [ranked_stories_keys])
else:
unread_story_hashes = cls.story_hashes(user_id, relative_user_id,
subscription_user_ids=social_user_ids,
read_filter="unread", order=order,
include_timestamps=True,
group_by_user=False,
socialsubs=socialsubs,
cutoff_date=cutoff_date)
if unread_story_hashes:
pipeline = rt.pipeline()
for unread_story_hash_group in chunks(unread_story_hashes, 100):
pipeline.zadd(unread_ranked_stories_keys, **dict(unread_story_hash_group))
pipeline.execute()
unread_feed_story_hashes = range_func(unread_ranked_stories_keys, offset, limit)
rt.expire(ranked_stories_keys, 60*60)
rt.expire(unread_ranked_stories_keys, 60*60)
return story_hashes, story_dates, unread_feed_story_hashes
def mark_story_ids_as_read(self, story_hashes, feed_id=None, mark_all_read=False, request=None):
data = dict(code=0, payload=story_hashes)
r = redis.Redis(connection_pool=settings.REDIS_POOL)
@ -1077,7 +1189,7 @@ class MSocialSubscription(mongo.Document):
# Use the latest story to get last read time.
latest_shared_story = MSharedStory.objects(user_id=self.subscription_user_id,
shared_date__gte=user_profile.unread_cutoff
).order_by('shared_date').only('shared_date').first()
).order_by('-shared_date').only('shared_date').first()
if latest_shared_story:
cutoff_date = latest_shared_story['shared_date'] + datetime.timedelta(seconds=1)
else:
@ -1749,7 +1861,7 @@ class MSharedStory(mongo.Document):
for c, comment in enumerate(story[comment_set]):
if comment['user_id'] not in profiles: continue
stories[s][comment_set][c]['user'] = profiles[comment['user_id']]
if comment['source_user_id']:
if comment['source_user_id'] and comment['source_user_id'] in profiles:
stories[s][comment_set][c]['source_user'] = profiles[comment['source_user_id']]
for r, reply in enumerate(comment['replies']):
if reply['user_id'] not in profiles: continue
@ -1799,18 +1911,22 @@ class MSharedStory(mongo.Document):
self.guid_hash[:6]
)
def generate_post_to_service_message(self, include_url=True):
def generate_post_to_service_message(self, truncate=None, include_url=True):
message = strip_tags(self.comments)
if not message or len(message) < 1:
message = self.story_title
if include_url:
message = truncate_chars(message, 92)
if include_url and truncate:
message = truncate_chars(message, truncate - 18 - 30)
feed = Feed.get_by_id(self.story_feed_id)
message += " (%s)" % truncate_chars(feed.feed_title, 18)
if truncate:
message += " (%s)" % truncate_chars(feed.feed_title, 18)
else:
message += " (%s)" % truncate_chars(feed.feed_title, 30)
if include_url:
message += " " + self.blurblog_permalink()
elif include_url:
message = truncate_chars(message, 116)
if truncate:
message = truncate_chars(message, truncate - 14)
message += " " + self.blurblog_permalink()
return message
@ -2409,7 +2525,7 @@ class MSocialServices(mongo.Document):
return profile
def post_to_twitter(self, shared_story):
message = shared_story.generate_post_to_service_message()
message = shared_story.generate_post_to_service_message(truncate=140)
try:
api = self.twitter_api()
@ -2443,7 +2559,7 @@ class MSocialServices(mongo.Document):
return True
def post_to_appdotnet(self, shared_story):
message = shared_story.generate_post_to_service_message()
message = shared_story.generate_post_to_service_message(truncate=256)
try:
api = self.appdotnet_api()

View file

@ -123,8 +123,8 @@ def load_social_stories(request, user_id, username=None):
story['social_user_id'] = social_user_id
# story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(shared_date, now)
story['long_parsed_date'] = format_story_link_date__long(shared_date, now)
story['short_parsed_date'] = format_story_link_date__short(shared_date)
story['long_parsed_date'] = format_story_link_date__long(shared_date)
story['read_status'] = 1
if (read_filter == 'all' or query) and socialsub:
@ -192,19 +192,23 @@ def load_river_blurblog(request):
if not relative_user_id:
relative_user_id = user.pk
socialsubs = MSocialSubscription.objects.filter(user_id=relative_user_id)
if social_user_ids:
socialsubs = socialsubs.filter(subscription_user_id__in=social_user_ids)
if not social_user_ids:
socialsubs = MSocialSubscription.objects.filter(user_id=relative_user_id)
social_user_ids = [s.subscription_user_id for s in socialsubs]
offset = (page-1) * limit
limit = page * limit - 1
story_hashes, story_dates, read_feed_story_hashes = MSocialSubscription.feed_stories(
user.pk, social_user_ids,
offset=offset, limit=limit,
order=order, read_filter=read_filter,
relative_user_id=relative_user_id,
cutoff_date=user.profile.unread_cutoff)
story_hashes, story_dates, unread_feed_story_hashes = MSocialSubscription.feed_stories(
user.pk, social_user_ids,
offset=offset, limit=limit,
order=order, read_filter=read_filter,
relative_user_id=relative_user_id,
socialsubs=socialsubs,
cutoff_date=user.profile.unread_cutoff)
mstories = MStory.find_by_story_hashes(story_hashes)
story_hashes_to_dates = dict(zip(story_hashes, story_dates))
def sort_stories_by_hash(a, b):
@ -268,10 +272,10 @@ def load_river_blurblog(request):
# Just need to format stories
for story in stories:
story['read_status'] = 0
if story['story_hash'] in read_feed_story_hashes:
if story['story_hash'] not in unread_feed_story_hashes:
story['read_status'] = 1
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['story_hash'] in starred_stories:
story['starred'] = True
@ -291,7 +295,7 @@ def load_river_blurblog(request):
story['shared_date'] = format_story_link_date__long(shared_date, now)
story['shared_comments'] = strip_tags(shared_stories[story['story_hash']]['comments'])
if (shared_stories[story['story_hash']]['shared_date'] < user.profile.unread_cutoff or
story['story_hash'] in read_feed_story_hashes):
story['story_hash'] not in unread_feed_story_hashes):
story['read_status'] = 1
classifiers = sort_classifiers_by_feed(user=user, feed_ids=story_feed_ids,
@ -570,6 +574,7 @@ def mark_story_as_shared(request):
story = stories[0]
story['shared_comments'] = strip_tags(shared_story['comments'] or "")
story['shared_by_user'] = True
story['shared'] = True
shared_date = localtime_for_timezone(shared_story['shared_date'], request.user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(shared_date)
story['long_parsed_date'] = format_story_link_date__long(shared_date)

View file

@ -89,7 +89,7 @@ class RStats:
for k, key in enumerate(keys):
match = prefix_re.match(key)
if not match:
if not match or dump[k] is None:
errors.add(key)
continue
prefix, rest = match.groups()
@ -102,16 +102,16 @@ class RStats:
prefixes_ttls[prefix]['X'] += 1
elif ttl < 60*60: # 1 hour
prefixes_ttls[prefix]['1h'] += 1
elif ttl < 60*60*12:
prefixes_ttls[prefix]['12h'] += 1
elif ttl < 60*60*24:
prefixes_ttls[prefix]['1d'] += 1
elif ttl < 60*60*168:
elif ttl < 60*60*24*7:
prefixes_ttls[prefix]['1w'] += 1
elif ttl < 60*60*336:
elif ttl < 60*60*24*14:
prefixes_ttls[prefix]['2w'] += 1
elif ttl < 60*60*24*30:
prefixes_ttls[prefix]['4w'] += 1
else:
prefixes_ttls[prefix]['2w+'] += 1
prefixes_ttls[prefix]['4w+'] += 1
keys_count = len(keys)
total_size = float(sum([k for k in sizes.values()]))

View file

@ -8,5 +8,7 @@
android:title="@string/menu_mark_previous_stories_as_read" />
<!-- TODO: Share/Unshare, Save/Unsave, mark unread -->
<item android:id="@+id/menu_shared"
android:title="@string/menu_share"/>
</menu>

View file

@ -6,6 +6,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.newsblur.R;
import com.newsblur.network.APIConstants;
public class AddFacebook extends NbFragmentActivity {
@ -22,7 +23,7 @@ public class AddFacebook extends NbFragmentActivity {
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url){
if (TextUtils.equals(url, "http://www.newsblur.com/")) {
if (TextUtils.equals(url, APIConstants.NEWSBLUR_URL + "/")) {
AddFacebook.this.setResult(FACEBOOK_AUTHED);
AddFacebook.this.finish();
return true;
@ -32,7 +33,7 @@ public class AddFacebook extends NbFragmentActivity {
}
});
webview.loadUrl("http://www.newsblur.com/oauth/facebook_connect/");
webview.loadUrl(APIConstants.URL_CONNECT_FACEBOOK);
}
}

View file

@ -6,6 +6,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.newsblur.R;
import com.newsblur.network.APIConstants;
public class AddTwitter extends NbFragmentActivity {
@ -22,7 +23,7 @@ public class AddTwitter extends NbFragmentActivity {
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url){
if (TextUtils.equals(url, "http://www.newsblur.com/")) {
if (TextUtils.equals(url, APIConstants.NEWSBLUR_URL + "/")) {
AddTwitter.this.setResult(TWITTER_AUTHED);
AddTwitter.this.finish();
return true;
@ -32,7 +33,7 @@ public class AddTwitter extends NbFragmentActivity {
}
});
webview.loadUrl("http://www.newsblur.com/oauth/twitter_connect/");
webview.loadUrl(APIConstants.URL_CONNECT_TWITTER);
}
}

View file

@ -36,12 +36,12 @@ public class AllSharedStoriesItemsList extends ItemsList {
feedIds.add(cursor.getString(cursor.getColumnIndex(DatabaseConstants.SOCIAL_FEED_ID)));
}
itemListFragment = (AllSharedStoriesItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.FRAGMENT_TAG);
itemListFragment = (AllSharedStoriesItemListFragment) fragmentManager.findFragmentByTag(AllSharedStoriesItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = AllSharedStoriesItemListFragment.newInstance(currentState, getStoryOrder());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, AllSharedStoriesItemListFragment.class.getName());
listTransaction.commit();
}
@ -49,17 +49,11 @@ public class AllSharedStoriesItemsList extends ItemsList {
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
cursor.close();
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {

View file

@ -1,9 +1,6 @@
package com.newsblur.activity;
import java.util.ArrayList;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import com.newsblur.R;
@ -17,58 +14,33 @@ import com.newsblur.util.StoryOrder;
public class AllSharedStoriesReading extends Reading {
private int currentPage;
private boolean requestingPage = false;
private boolean stopLoading = false;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
stories = contentResolver.query(FeedProvider.ALL_SHARED_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
setTitle(getResources().getString(R.string.all_shared_stories));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setResult(RESULT_OK);
setupPager();
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
stories = contentResolver.query(FeedProvider.ALL_SHARED_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
setTitle(getResources().getString(R.string.all_shared_stories));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !stopLoading && !requestingPage) {
currentPage += 1;
requestingPage = true;
triggerRefresh(currentPage);
}
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTISOCIALFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, new String[0]); // query for all shared storis via wildcard
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME));
startService(intent);
}
}
@Override
public void updateAfterSync() {
requestingPage = false;
super.updateAfterSync();
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTISOCIALFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, new String[0]); // query for all shared storis via wildcard
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFolder(this, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME));
startService(intent);
}
}

View file

@ -45,12 +45,12 @@ public class AllStoriesItemsList extends ItemsList implements MarkAllReadDialogL
resolver = getContentResolver();
itemListFragment = (AllStoriesItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.FRAGMENT_TAG);
itemListFragment = (AllStoriesItemListFragment) fragmentManager.findFragmentByTag(AllStoriesItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = AllStoriesItemListFragment.newInstance(currentState, getStoryOrder());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, AllStoriesItemListFragment.class.getName());
listTransaction.commit();
}
@ -58,16 +58,10 @@ public class AllStoriesItemsList extends ItemsList implements MarkAllReadDialogL
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {

View file

@ -1,7 +1,5 @@
package com.newsblur.activity;
import java.util.ArrayList;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
@ -17,59 +15,33 @@ import com.newsblur.util.StoryOrder;
public class AllStoriesReading extends Reading {
private int currentPage;
private ArrayList<String> feedIds;
private boolean stopLoading = false;
private boolean requestedPage = false;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME);
stories = contentResolver.query(FeedProvider.ALL_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
setTitle(getResources().getString(R.string.all_stories_row_title));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setResult(RESULT_OK);
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME);
stories = contentResolver.query(FeedProvider.ALL_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
setTitle(getResources().getString(R.string.all_stories_row_title));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !stopLoading && !requestedPage) {
requestedPage = true;
currentPage += 1;
triggerRefresh(currentPage);
}
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTIFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, new String[0]); // ask for all stories via wildcarding the feed param
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME));
startService(intent);
}
}
@Override
public void updateAfterSync() {
requestedPage = false;
super.updateAfterSync();
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTIFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, new String[0]); // ask for all stories via wildcarding the feed param
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFolder(this, PrefConstants.ALL_STORIES_FOLDER_NAME));
startService(intent);
}
}

View file

@ -52,12 +52,12 @@ public class FeedItemsList extends ItemsList {
setTitle(feed.title);
}
itemListFragment = (FeedItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.FRAGMENT_TAG);
itemListFragment = (FeedItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = FeedItemListFragment.newInstance(feedId, currentState, getStoryOrder());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.class.getName());
listTransaction.commit();
}
@ -65,7 +65,7 @@ public class FeedItemsList extends ItemsList {
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
}
@ -118,11 +118,6 @@ public class FeedItemsList extends ItemsList {
return true;
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {

View file

@ -18,78 +18,52 @@ import com.newsblur.util.StoryOrder;
public class FeedReading extends Reading {
String feedId;
private Feed feed;
private int currentPage;
private boolean requestedPage = false;
String feedId;
private Feed feed;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setResult(RESULT_OK);
feedId = getIntent().getStringExtra(Reading.EXTRA_FEED);
feedId = getIntent().getStringExtra(Reading.EXTRA_FEED);
Uri classifierUri = FeedProvider.CLASSIFIER_URI.buildUpon().appendPath(feedId).build();
Cursor feedClassifierCursor = contentResolver.query(classifierUri, null, null, null, null);
Classifier classifier = Classifier.fromCursor(feedClassifierCursor);
Uri classifierUri = FeedProvider.CLASSIFIER_URI.buildUpon().appendPath(feedId).build();
Cursor feedClassifierCursor = contentResolver.query(classifierUri, null, null, null, null);
Classifier classifier = Classifier.fromCursor(feedClassifierCursor);
Uri storiesURI = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(feedId).build();
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFeed(this, feedId);
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
Uri storiesURI = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(feedId).build();
StoryOrder storyOrder = PrefsUtils.getStoryOrderForFeed(this, feedId);
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.getStorySortOrder(storyOrder));
final Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
Cursor feedCursor = contentResolver.query(feedUri, null, null, null, null);
final Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
Cursor feedCursor = contentResolver.query(feedUri, null, null, null, null);
feedCursor.moveToFirst();
feed = Feed.fromCursor(feedCursor);
setTitle(feed.title);
feedCursor.moveToFirst();
feed = Feed.fromCursor(feedCursor);
setTitle(feed.title);
this.unreadCount = FeedUtils.getFeedUnreadCount(this.feed, this.currentState);
readingAdapter = new FeedReadingAdapter(getSupportFragmentManager(), feed, stories, classifier);
readingAdapter = new FeedReadingAdapter(getSupportFragmentManager(), feed, stories, classifier);
setupPager();
setupPager();
syncFragment = (SyncUpdateFragment) fragmentManager.findFragmentByTag(SyncUpdateFragment.TAG);
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
}
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void updateAfterSync() {
requestedPage = false;
super.updateAfterSync();
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !stopLoading && !requestedPage) {
requestedPage = true;
currentPage += 1;
triggerRefresh(currentPage);
}
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.FEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_FEED_ID, feedId);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFeed(this, feedId));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFeed(this, feedId));
startService(intent);
}
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.FEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_FEED_ID, feedId);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFeed(this, feedId));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFeed(this, feedId));
startService(intent);
}
}

View file

@ -52,12 +52,12 @@ public class FolderItemsList extends ItemsList implements MarkAllReadDialogListe
feedIds.add(cursor.getString(cursor.getColumnIndex(DatabaseConstants.FEED_ID)));
}
itemListFragment = (FolderItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.FRAGMENT_TAG);
itemListFragment = (FolderItemListFragment) fragmentManager.findFragmentByTag(FolderItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = FolderItemListFragment.newInstance(feedIds, folderName, currentState, getStoryOrder());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FolderItemListFragment.class.getName());
listTransaction.commit();
}
@ -65,16 +65,10 @@ public class FolderItemsList extends ItemsList implements MarkAllReadDialogListe
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getSupportMenuInflater();

View file

@ -12,61 +12,40 @@ import com.newsblur.util.PrefsUtils;
public class FolderReading extends Reading {
private String[] feedIds;
private String folderName;
private boolean stopLoading = false; // will go high iff we are out of pages
private boolean requestedPage;
private int currentPage;
private String[] feedIds;
private String folderName;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setResult(RESULT_OK);
feedIds = getIntent().getStringArrayExtra(Reading.EXTRA_FEED_IDS);
folderName = getIntent().getStringExtra(Reading.EXTRA_FOLDERNAME);
setTitle(folderName);
feedIds = getIntent().getStringArrayExtra(Reading.EXTRA_FEED_IDS);
folderName = getIntent().getStringExtra(Reading.EXTRA_FOLDERNAME);
setTitle(folderName);
Uri storiesURI = FeedProvider.MULTIFEED_STORIES_URI;
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), feedIds, null);
Uri storiesURI = FeedProvider.MULTIFEED_STORIES_URI;
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), feedIds, null);
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTIFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, feedIds);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.MULTIFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_MULTIFEED_IDS, feedIds);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFolder(this, folderName));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFolder(this, folderName));
startService(intent);
}
@Override
public void updateAfterSync() {
requestedPage = false;
super.updateAfterSync();
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !stopLoading && !requestedPage) {
currentPage += 1;
requestedPage = true;
triggerRefresh(currentPage);
}
}
startService(intent);
}
}

View file

@ -6,6 +6,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.newsblur.R;
import com.newsblur.network.APIConstants;
public class ImportFeeds extends NbFragmentActivity {
@ -21,7 +22,7 @@ public class ImportFeeds extends NbFragmentActivity {
webContainer.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url){
if (TextUtils.equals(url, "http://www.newsblur.com/")) {
if (TextUtils.equals(url, APIConstants.NEWSBLUR_URL + "/")) {
ImportFeeds.this.setResult(RESULT_OK);
ImportFeeds.this.finish();
return true;
@ -31,7 +32,7 @@ public class ImportFeeds extends NbFragmentActivity {
}
});
webContainer.loadUrl("http://www.newsblur.com/import/authorize/");
webContainer.loadUrl(APIConstants.URL_IMPORT_AUTHORIZATION);
}

View file

@ -25,7 +25,6 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
public static final String EXTRA_BLURBLOG_USERNAME = "blurblogName";
public static final String EXTRA_BLURBLOG_USERID = "blurblogId";
public static final String EXTRA_BLURBLOG_USER_ICON = "userIcon";
public static final String RESULT_EXTRA_READ_STORIES = "storiesToMarkAsRead";
public static final String EXTRA_BLURBLOG_TITLE = "blurblogTitle";
private static final String STORY_ORDER = "storyOrder";
private static final String READ_FILTER = "readFilter";
@ -33,7 +32,6 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
protected ItemListFragment itemListFragment;
protected FragmentManager fragmentManager;
protected SyncUpdateFragment syncFragment;
protected String TAG = "ItemsList";
protected int currentState;
private Menu menu;
@ -44,7 +42,6 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
requestWindowFeature(Window.FEATURE_PROGRESS);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(bundle);
setResult(RESULT_OK);
setContentView(R.layout.activity_itemslist);
fragmentManager = getSupportFragmentManager();
@ -55,14 +52,13 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
}
protected void onResume() {
super.onResume();
// Reading activities almost certainly changed the read/unread state of some stories. Ensure
// we reflect those changes promptly.
itemListFragment.hasUpdated();
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
itemListFragment.hasUpdated();
}
}
public abstract void triggerRefresh();
public abstract void triggerRefresh(int page);
public abstract void markItemListAsRead();
@ -98,7 +94,7 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
if (itemListFragment != null) {
itemListFragment.hasUpdated();
} else {
Log.e(TAG, "Error updating list as it doesn't exist.");
Log.e(this.getClass().getName(), "Error updating list as it doesn't exist.");
}
setSupportProgressBarIndeterminateVisibility(false);
}
@ -108,7 +104,7 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
if (itemListFragment != null) {
itemListFragment.hasUpdated();
} else {
Log.e(TAG, "Error updating list as it doesn't exist.");
Log.e(this.getClass().getName(), "Error updating list as it doesn't exist.");
}
}
@ -134,17 +130,16 @@ public abstract class ItemsList extends NbFragmentActivity implements SyncUpdate
updateStoryOrderPreference(newValue);
itemListFragment.setStoryOrder(newValue);
stopLoading = false;
triggerRefresh();
triggerRefresh(1);
}
public abstract void updateStoryOrderPreference(StoryOrder newValue);
@Override
public void readFilterChanged(ReadFilter newValue) {
updateReadFilterPreference(newValue);
stopLoading = false;
triggerRefresh();
triggerRefresh(1);
}
protected abstract void updateReadFilterPreference(ReadFilter newValue);

View file

@ -12,7 +12,6 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
@ -35,8 +34,6 @@ import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.UIUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.NonfocusScrollview.ScrollChangeListener;
@ -66,7 +63,9 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
private APIManager apiManager;
protected SyncUpdateFragment syncFragment;
protected Cursor stories;
protected boolean stopLoading = false;
private boolean noMoreApiPages;
protected volatile boolean requestedPage; // set high iff a syncservice request for stories is already in flight
private int currentApiPage = 0;
private Set<Story> storiesToMarkAsRead;
// subclasses may set this to a nonzero value to enable the unread count overlay
@ -168,13 +167,7 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
}
return true;
} else if (item.getItemId() == R.id.menu_shared) {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.putExtra(Intent.EXTRA_SUBJECT, story.title);
final String shareString = getResources().getString(R.string.share);
intent.putExtra(Intent.EXTRA_TEXT, String.format(shareString, new Object[] { story.title, story.permalink }));
startActivity(Intent.createChooser(intent, "Share using"));
FeedUtils.shareStory(story, this);
return true;
} else if (item.getItemId() == R.id.menu_textsize) {
float currentValue = getSharedPreferences(PrefConstants.PREFERENCES, 0).getFloat(PrefConstants.PREFERENCE_TEXT_SIZE, 0.5f);
@ -262,6 +255,7 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
@Override
public void updateAfterSync() {
this.requestedPage = false;
setSupportProgressBarIndeterminateVisibility(false);
stories.requery();
readingAdapter.notifyDataSetChanged();
@ -283,10 +277,17 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
*/
@Override
public void setNothingMoreToUpdate() {
this.stopLoading = true;
this.noMoreApiPages = true;
}
public abstract void checkStoryCount(int position);
private void checkStoryCount(int position) {
// if the pager is at or near the number of stories loaded, check for more unless we know we are at the end of the list
if (((position + 1) >= stories.getCount()) && !noMoreApiPages && !requestedPage) {
currentApiPage += 1;
requestedPage = true;
triggerRefresh(currentApiPage);
}
}
@Override
public void updateSyncStatus(boolean syncRunning) {

View file

@ -19,7 +19,6 @@ import com.newsblur.database.FeedProvider;
import com.newsblur.fragment.SavedStoriesItemListFragment;
import com.newsblur.fragment.FeedItemListFragment;
import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.network.APIManager;
import com.newsblur.service.SyncService;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
@ -28,7 +27,6 @@ import com.newsblur.util.StoryOrder;
public class SavedStoriesItemsList extends ItemsList {
private APIManager apiManager;
private ContentResolver resolver;
@Override
@ -37,15 +35,14 @@ public class SavedStoriesItemsList extends ItemsList {
setTitle(getResources().getString(R.string.saved_stories_title));
apiManager = new APIManager(this);
resolver = getContentResolver();
itemListFragment = (SavedStoriesItemListFragment) fragmentManager.findFragmentByTag(FeedItemListFragment.FRAGMENT_TAG);
itemListFragment = (SavedStoriesItemListFragment) fragmentManager.findFragmentByTag(SavedStoriesItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = SavedStoriesItemListFragment.newInstance();
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, SavedStoriesItemListFragment.class.getName());
listTransaction.commit();
}
@ -53,16 +50,10 @@ public class SavedStoriesItemsList extends ItemsList {
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {

View file

@ -1,7 +1,6 @@
package com.newsblur.activity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import com.newsblur.R;
@ -12,49 +11,27 @@ import com.newsblur.service.SyncService;
public class SavedStoriesReading extends Reading {
private int currentPage;
private boolean requestedPage = false;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
stories = contentResolver.query(FeedProvider.STARRED_STORIES_URI, null, null, null, null);
setTitle(getResources().getString(R.string.saved_stories_title));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setResult(RESULT_OK);
stories = contentResolver.query(FeedProvider.STARRED_STORIES_URI, null, null, null, null);
setTitle(getResources().getString(R.string.saved_stories_title));
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
}
setupPager();
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !stopLoading && !requestedPage) {
requestedPage = true;
currentPage += 1;
triggerRefresh(currentPage);
}
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.STARRED_STORIES_UPDATE);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
startService(intent);
}
}
@Override
public void updateAfterSync() {
requestedPage = false;
super.updateAfterSync();
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.STARRED_STORIES_UPDATE);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
startService(intent);
}
}

View file

@ -1,6 +1,7 @@
package com.newsblur.activity;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import com.actionbarsherlock.view.MenuItem;
import com.newsblur.R;
import com.newsblur.util.PrefConstants;
@ -11,8 +12,19 @@ public class Settings extends SherlockPreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
super.getPreferenceManager().setSharedPreferencesName(PrefConstants.PREFERENCES);
addPreferencesFromResource(R.layout.activity_settings);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View file

@ -40,7 +40,7 @@ public class SocialFeedItemsList extends ItemsList {
itemListFragment = SocialFeedItemListFragment.newInstance(userId, username, currentState, getStoryOrder());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, FeedItemListFragment.FRAGMENT_TAG);
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, SocialFeedItemListFragment.class.getName());
listTransaction.commit();
}
@ -48,7 +48,7 @@ public class SocialFeedItemsList extends ItemsList {
if (syncFragment == null) {
syncFragment = new SyncUpdateFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
triggerRefresh();
triggerRefresh(1);
}
}
@ -61,11 +61,6 @@ public class SocialFeedItemsList extends ItemsList {
return true;
}
@Override
public void triggerRefresh() {
triggerRefresh(1);
}
@Override
public void triggerRefresh(int page) {
if (!stopLoading) {

View file

@ -1,9 +1,5 @@
package com.newsblur.activity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -12,64 +8,55 @@ import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.Story;
import com.newsblur.service.SyncService;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StoryOrder;
public class SocialFeedReading extends Reading {
private String userId;
private String username;
private SocialFeed socialFeed;
private boolean requestedPage;
private int currentPage;
private String userId;
private String username;
private SocialFeed socialFeed;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setResult(RESULT_OK);
userId = getIntent().getStringExtra(Reading.EXTRA_USERID);
username = getIntent().getStringExtra(Reading.EXTRA_USERNAME);
userId = getIntent().getStringExtra(Reading.EXTRA_USERID);
username = getIntent().getStringExtra(Reading.EXTRA_USERNAME);
Uri socialFeedUri = FeedProvider.SOCIAL_FEEDS_URI.buildUpon().appendPath(userId).build();
socialFeed = SocialFeed.fromCursor(contentResolver.query(socialFeedUri, null, null, null, null));
Uri socialFeedUri = FeedProvider.SOCIAL_FEEDS_URI.buildUpon().appendPath(userId).build();
socialFeed = SocialFeed.fromCursor(contentResolver.query(socialFeedUri, null, null, null, null));
Uri storiesURI = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, null);
setTitle(getIntent().getStringExtra(EXTRA_USERNAME));
Uri storiesURI = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, null);
setTitle(getIntent().getStringExtra(EXTRA_USERNAME));
this.unreadCount = FeedUtils.getFeedUnreadCount(this.socialFeed, this.currentState);
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.SOCIALFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_SOCIALFEED_ID, userId);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_SOCIALFEED_USERNAME, username);
startService(intent);
}
@Override
public void checkStoryCount(int position) {
if (position == stories.getCount() - 1 && !requestedPage && !stopLoading) {
currentPage += 1;
requestedPage = true;
triggerRefresh(currentPage);
}
}
@Override
public void triggerRefresh(int page) {
setSupportProgressBarIndeterminateVisibility(true);
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.SOCIALFEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_SOCIALFEED_ID, userId);
if (page > 1) {
intent.putExtra(SyncService.EXTRA_TASK_PAGE_NUMBER, Integer.toString(page));
}
intent.putExtra(SyncService.EXTRA_TASK_SOCIALFEED_USERNAME, username);
intent.putExtra(SyncService.EXTRA_TASK_ORDER, PrefsUtils.getStoryOrderForFeed(this, userId));
intent.putExtra(SyncService.EXTRA_TASK_READ_FILTER, PrefsUtils.getReadFilterForFeed(this, userId));
startService(intent);
}
}

View file

@ -41,7 +41,6 @@ public class AllSharedStoriesItemListFragment extends ItemListFragment implement
private int currentPage = 0;
public static int ITEMLIST_LOADER = 0x01;
private static final String TAG = "AllSharedStoriesItemListFragment";
private Cursor countCursor;
private ListView itemList;
private String[] groupFrom;
@ -135,7 +134,7 @@ public class AllSharedStoriesItemListFragment extends ItemListFragment implement
Intent i = new Intent(getActivity(), AllSharedStoriesReading.class);
i.putExtra(FeedReading.EXTRA_POSITION, position);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
@Override

View file

@ -137,7 +137,7 @@ public class AllStoriesItemListFragment extends StoryItemListFragment implements
Intent i = new Intent(getActivity(), AllStoriesReading.class);
i.putExtra(FeedReading.EXTRA_POSITION, position);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
@Override

View file

@ -34,7 +34,6 @@ import com.newsblur.view.FeedItemViewBinder;
public class FeedItemListFragment extends StoryItemListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnScrollListener {
public static final String FRAGMENT_TAG = "itemListFragment";
private ContentResolver contentResolver;
private String feedId;
private FeedItemsAdapter adapter;
@ -43,7 +42,6 @@ public class FeedItemListFragment extends StoryItemListFragment implements Loade
private boolean requestedPage = false;
public static int ITEMLIST_LOADER = 0x01;
private int READING_RETURNED = 0x02;
private StoryOrder storyOrder;
@ -141,7 +139,7 @@ public class FeedItemListFragment extends StoryItemListFragment implements Loade
i.putExtra(Reading.EXTRA_FEED, feedId);
i.putExtra(FeedReading.EXTRA_POSITION, position);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
public void changeState(int state) {

View file

@ -35,8 +35,6 @@ import com.newsblur.view.FeedItemViewBinder;
public class FolderItemListFragment extends StoryItemListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnScrollListener {
private static final String TAG = "itemListFragment";
public static final String FRAGMENT_TAG = "itemListFragment";
private ContentResolver contentResolver;
private String[] feedIds;
private MultipleFeedItemsAdapter adapter;
@ -143,7 +141,7 @@ public class FolderItemListFragment extends StoryItemListFragment implements Loa
i.putExtra(FeedReading.EXTRA_POSITION, position);
i.putExtra(FeedReading.EXTRA_FOLDERNAME, folderName);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
public void changeState(int state) {

View file

@ -55,7 +55,6 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
private FolderTreeViewBinder groupViewBinder;
private APIManager apiManager;
private int currentState = AppConstants.STATE_SOME;
private int FEEDCHECK = 0x01;
private SocialFeedViewBinder blogViewBinder;
private SharedPreferences sharedPreferences;
@ -89,6 +88,12 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
super.onAttach(activity);
}
@Override
public void onStart() {
super.onStart();
hasUpdated();
}
public void hasUpdated() {
folderAdapter.notifyDataSetChanged();
checkOpenFolderPreferences();
@ -281,7 +286,7 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
if (folderAdapter.isFolderRoot(groupPosition)) {
Intent i = new Intent(getActivity(), AllStoriesItemsList.class);
i.putExtra(AllStoriesItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, FEEDCHECK);
startActivity(i);
return true;
} else if (folderAdapter.isRowSavedStories(groupPosition)) {
Intent i = new Intent(getActivity(), SavedStoriesItemsList.class);
@ -315,7 +320,7 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
intent.putExtra(ItemsList.EXTRA_BLURBLOG_TITLE, blurblogTitle);
intent.putExtra(ItemsList.EXTRA_BLURBLOG_USERID, userId);
intent.putExtra(ItemsList.EXTRA_STATE, currentState);
getActivity().startActivityForResult(intent, FEEDCHECK );
getActivity().startActivity(intent);
} else {
final Intent intent = new Intent(getActivity(), FeedItemsList.class);
Cursor childCursor = folderAdapter.getChild(groupPosition, childPosition);
@ -327,7 +332,7 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
intent.putExtra(FeedItemsList.EXTRA_FEED_TITLE, feedTitle);
intent.putExtra(FeedItemsList.EXTRA_FOLDER_NAME, folderName);
intent.putExtra(ItemsList.EXTRA_STATE, currentState);
getActivity().startActivityForResult(intent, FEEDCHECK );
getActivity().startActivity(intent);
}
return true;
}

View file

@ -6,8 +6,6 @@ import android.support.v4.app.Fragment;
public abstract class ItemListFragment extends Fragment {
protected int READING_RETURNED = 0x02;
public abstract void hasUpdated();
public abstract void changeState(final int state);
public abstract void setStoryOrder(StoryOrder storyOrder);

View file

@ -117,7 +117,7 @@ public class SavedStoriesItemListFragment extends ItemListFragment implements Lo
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent i = new Intent(getActivity(), SavedStoriesReading.class);
i.putExtra(FeedReading.EXTRA_POSITION, position);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
@Override

View file

@ -32,8 +32,6 @@ import com.newsblur.view.SocialItemViewBinder;
public class SocialFeedItemListFragment extends ItemListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnScrollListener {
private static final String TAG = "socialfeedListFragment";
public static final String FRAGMENT_TAG = "socialfeedListFragment";
private ContentResolver contentResolver;
private String userId, username;
private SimpleCursorAdapter adapter;
@ -43,7 +41,6 @@ public class SocialFeedItemListFragment extends ItemListFragment implements Load
private boolean requestedPage;
public static int ITEMLIST_LOADER = 0x01;
private int READING_RETURNED = 0x02;
private Uri socialFeedUri;
private String[] groupFroms;
private int[] groupTos;
@ -147,7 +144,7 @@ public class SocialFeedItemListFragment extends ItemListFragment implements Load
i.putExtra(Reading.EXTRA_USERNAME, username);
i.putExtra(Reading.EXTRA_POSITION, position);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivityForResult(i, READING_RETURNED );
startActivity(i);
}
public void changeState(int state) {

View file

@ -48,6 +48,9 @@ public abstract class StoryItemListFragment extends ItemListFragment implements
}
FeedUtils.markStoriesAsRead(storiesToMarkAsRead, getActivity());
refreshStories();
} else if (item.getItemId() == R.id.menu_shared) {
Story story = adapter.getStory(menuInfo.position);
FeedUtils.shareStory(story, getActivity());
}
return super.onContextItemSelected(item);
}

View file

@ -2,38 +2,45 @@ package com.newsblur.network;
public class APIConstants {
public static final String NEWSBLUR_URL = "https://newsblur.com";
public static final String COOKIE_DOMAIN = ".newsblur.com";
public static final String URL_CATEGORIES = NEWSBLUR_URL + "/categories/";
public static final String PARAMETER_CATEGORY = "category";
public static final String URL_ADD_CATEGORIES = NEWSBLUR_URL + "/categories/subscribe";
public static final String URL_AUTOFOLLOW_PREF = NEWSBLUR_URL + "/profile/set_preference";
// TODO: make use of trailing slashes on URLs consistent or document why
// they are not.
public static final String URL_LOGIN = "http://newsblur.com/api/login";
public static final String URL_FEEDS = "http://newsblur.com/reader/feeds/";
public static final String URL_USER_PROFILE = "http://newsblur.com/social/profile";
public static final String URL_MY_PROFILE = "http://newsblur.com/social/load_user_profile";
public static final String URL_FOLLOW = "http://newsblur.com/social/follow";
public static final String URL_UNFOLLOW = "http://newsblur.com/social/unfollow";
public static final String URL_LOGIN = NEWSBLUR_URL + "/api/login";
public static final String URL_FEEDS = NEWSBLUR_URL + "/reader/feeds/";
public static final String URL_USER_PROFILE = NEWSBLUR_URL + "/social/profile";
public static final String URL_MY_PROFILE = NEWSBLUR_URL + "/social/load_user_profile";
public static final String URL_FOLLOW = NEWSBLUR_URL + "/social/follow";
public static final String URL_UNFOLLOW = NEWSBLUR_URL + "/social/unfollow";
public static final String URL_USER_INTERACTIONS = "http://newsblur.com/social/interactions";
public static final String URL_RIVER_STORIES = "http://newsblur.com/reader/river_stories";
public static final String URL_SHARED_RIVER_STORIES = "http://newsblur.com/social/river_stories";
public static final String URL_USER_INTERACTIONS = NEWSBLUR_URL + "/social/interactions";
public static final String URL_RIVER_STORIES = NEWSBLUR_URL + "/reader/river_stories";
public static final String URL_SHARED_RIVER_STORIES = NEWSBLUR_URL + "/social/river_stories";
public static final String URL_FEED_STORIES = "http://newsblur.com/reader/feed";
public static final String URL_SOCIALFEED_STORIES = "http://newsblur.com/social/stories";
public static final String URL_SIGNUP = "http://newsblur.com/api/signup";
public static final String URL_FEED_COUNTS = "http://newsblur.com/reader/refresh_feeds/";
public static final String URL_MARK_FEED_AS_READ = "http://newsblur.com/reader/mark_feed_as_read/";
public static final String URL_MARK_ALL_AS_READ = "http://newsblur.com/reader/mark_all_as_read/";
public static final String URL_MARK_STORIES_READ = "http://newsblur.com/reader/mark_story_hashes_as_read/";
public static final String URL_SHARE_STORY = "http://newsblur.com/social/share_story";
public static final String URL_MARK_STORY_AS_STARRED = "http://newsblur.com/reader/mark_story_as_starred/";
public static final String URL_MARK_STORY_AS_UNREAD = "http://newsblur.com/reader/mark_story_as_unread/";
public static final String URL_STARRED_STORIES = "http://newsblur.com/reader/starred_stories";
public static final String URL_FEED_AUTOCOMPLETE = "http://newsblur.com/rss_feeds/feed_autocomplete";
public static final String URL_LIKE_COMMENT = "http://newsblur.com/social/like_comment";
public static final String URL_UNLIKE_COMMENT = "http://newsblur.com/social/remove_like_comment";
public static final String URL_REPLY_TO = "http://newsblur.com/social/save_comment_reply";
public static final String URL_ADD_FEED = "http://newsblur.com/reader/add_url";
public static final String URL_DELETE_FEED = "http://newsblur.com/reader/delete_feed";
public static final String URL_CLASSIFIER_SAVE = "http://newsblur.com/classifier/save";
public static final String URL_FEED_STORIES = NEWSBLUR_URL + "/reader/feed";
public static final String URL_SOCIALFEED_STORIES = NEWSBLUR_URL + "/social/stories";
public static final String URL_SIGNUP = NEWSBLUR_URL + "/api/signup";
public static final String URL_FEED_COUNTS = NEWSBLUR_URL + "/reader/refresh_feeds/";
public static final String URL_MARK_FEED_AS_READ = NEWSBLUR_URL + "/reader/mark_feed_as_read/";
public static final String URL_MARK_ALL_AS_READ = NEWSBLUR_URL + "/reader/mark_all_as_read/";
public static final String URL_MARK_STORIES_READ = NEWSBLUR_URL + "/reader/mark_story_hashes_as_read/";
public static final String URL_SHARE_STORY = NEWSBLUR_URL + "/social/share_story";
public static final String URL_MARK_STORY_AS_STARRED = NEWSBLUR_URL + "/reader/mark_story_as_starred/";
public static final String URL_MARK_STORY_AS_UNREAD = NEWSBLUR_URL + "/reader/mark_story_as_unread/";
public static final String URL_STARRED_STORIES = NEWSBLUR_URL + "/reader/starred_stories";
public static final String URL_FEED_AUTOCOMPLETE = NEWSBLUR_URL + "/rss_feeds/feed_autocomplete";
public static final String URL_LIKE_COMMENT = NEWSBLUR_URL + "/social/like_comment";
public static final String URL_UNLIKE_COMMENT = NEWSBLUR_URL + "/social/remove_like_comment";
public static final String URL_REPLY_TO = NEWSBLUR_URL + "/social/save_comment_reply";
public static final String URL_ADD_FEED = NEWSBLUR_URL + "/reader/add_url";
public static final String URL_DELETE_FEED = NEWSBLUR_URL + "/reader/delete_feed";
public static final String URL_CLASSIFIER_SAVE = NEWSBLUR_URL + "/classifier/save";
public static final String PARAMETER_FEEDS = "f";
public static final String PARAMETER_PASSWORD = "password";
@ -62,10 +69,7 @@ public class APIConstants {
public static final String PARAMETER_ORDER = "order";
public static final String PARAMETER_READ_FILTER = "read_filter";
public static final String NEWSBLUR_URL = "http://newsblur.com";
public static final String URL_CATEGORIES = "http://newsblur.com/categories/" ;
public static final String PARAMETER_CATEGORY = "category";
public static final String URL_ADD_CATEGORIES = "http://newsblur.com/categories/subscribe";
public static final String URL_AUTOFOLLOW_PREF = "http://newsblur.com/profile/set_preference";
public static final String URL_CONNECT_FACEBOOK = NEWSBLUR_URL + "/oauth/facebook_connect/";
public static final String URL_CONNECT_TWITTER = NEWSBLUR_URL + "/oauth/twitter_connect/";
public static final String URL_IMPORT_AUTHORIZATION = NEWSBLUR_URL + "/import/authorize/";
}

View file

@ -167,7 +167,7 @@ public class APIManager {
CookieSyncManager.createInstance(context.getApplicationContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie(".newsblur.com", response.getCookie());
cookieManager.setCookie(APIConstants.COOKIE_DOMAIN, response.getCookie());
CookieSyncManager.getInstance().sync();
}
return registerResponse;

View file

@ -145,7 +145,7 @@ public class SyncService extends IntentService {
case STARRED_STORIES_UPDATE:
StoriesResponse starredStories = apiManager.getStarredStories(intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER));
if (starredStories == null && starredStories.stories.length == 0) {
if (starredStories == null || starredStories.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
break;

View file

@ -16,7 +16,6 @@ public class AppConstants {
public static final int REGISTRATION_COMPLETED = 1;
public static final String FOLDER_PRE = "folder_collapsed";
public static final String NEWSBLUR_URL = "http://www.newsblur.com";
public static final float FONT_SIZE_LOWER_BOUND = 0.7f;
public static final float FONT_SIZE_INCREMENT_FACTOR = 8;

View file

@ -131,7 +131,7 @@ public class FeedUtils {
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
if (result.isError()) {
Log.e(FeedUtils.class.getName(), "Could not update unread counts via API: " + result.getErrorMessage());
}
}
@ -234,5 +234,14 @@ public class FeedUtils {
}
return count;
}
public static void shareStory(Story story, Context context) {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.putExtra(Intent.EXTRA_SUBJECT, story.title);
final String shareString = context.getResources().getString(R.string.share);
intent.putExtra(Intent.EXTRA_TEXT, String.format(shareString, new Object[] { story.title, story.permalink }));
context.startActivity(Intent.createChooser(intent, "Share using"));
}
}

View file

@ -22,6 +22,7 @@ import android.util.Log;
import android.widget.ImageView;
import com.newsblur.R;
import com.newsblur.network.APIConstants;
public class ImageLoader {
@ -126,7 +127,7 @@ public class ImageLoader {
try {
if (url.startsWith("/")) {
url = AppConstants.NEWSBLUR_URL + url;
url = APIConstants.NEWSBLUR_URL + url;
}
final URL imageUrl = new URL(url);
final HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();

View file

@ -21,7 +21,6 @@ import com.newsblur.util.ImageLoader;
public class FolderTreeViewBinder implements ViewBinder {
private int currentState = AppConstants.STATE_SOME;
private int READING_RETURNED = 0x02;
private final ImageLoader imageLoader;
public FolderTreeViewBinder(ImageLoader imageLoader) {
@ -66,7 +65,7 @@ public class FolderTreeViewBinder implements ViewBinder {
Intent i = new Intent(v.getContext(), FolderItemsList.class);
i.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, folderName);
i.putExtra(FolderItemsList.EXTRA_STATE, currentState);
((Activity) v.getContext()).startActivityForResult(i, READING_RETURNED);
((Activity) v.getContext()).startActivity(i);
}
});
return true;

565
clients/ios/AFNetworking/AFHTTPClient.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFHTTPClient.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -21,25 +21,58 @@
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFURLConnectionOperation.h"
@class AFHTTPRequestOperation;
@protocol AFHTTPClientOperation;
@protocol AFMultipartFormData;
#import <Availability.h>
/**
Posted when network reachability changes.
The notification object is an `NSNumber` object containing the boolean value for the current network reachability.
This notification contains no information in the `userInfo` dictionary.
@warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (Prefix.pch).
*/
#ifdef _SYSTEMCONFIGURATION_H
extern NSString * const AFNetworkingReachabilityDidChangeNotification;
#endif
`AFHTTPClient` captures the common patterns of communicating with an web application over HTTP. It encapsulates information like base URL, authorization credentials, and HTTP headers, and uses them to construct and manage the execution of HTTP request operations.
/**
Specifies network reachability of the client to its `baseURL` domain.
## Automatic Content Parsing
Instances of `AFHTTPClient` may specify which types of requests it expects and should handle by registering HTTP operation classes for automatic parsing. Registered classes will determine whether they can handle a particular request, and then construct a request operation accordingly in `enqueueHTTPRequestOperationWithRequest:success:failure`.
## Subclassing Notes
In most cases, one should create an `AFHTTPClient` subclass for each website or web application that your application communicates with. It is often useful, also, to define a class method that returns a singleton shared HTTP client in each subclass, that persists authentication credentials and other configuration across the entire application.
## Methods to Override
To change the behavior of all url request construction for an `AFHTTPClient` subclass, override `requestWithMethod:path:parameters`.
To change the behavior of all request operation construction for an `AFHTTPClient` subclass, override `HTTPRequestOperationWithRequest:success:failure`.
## Default Headers
By default, `AFHTTPClient` sets the following HTTP headers:
- `Accept-Language: (comma-delimited preferred languages), en-us;q=0.8`
- `User-Agent: (generated user agent)`
You can override these HTTP headers or define new ones using `setDefaultHeader:value:`.
## URL Construction Using Relative Paths
Both `-requestWithMethod:path:parameters:` and `-multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` construct URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`. Below are a few examples of how `baseURL` and relative paths interact:
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
Also important to note is that a trailing slash will be added to any `baseURL` without one, which would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash.
## NSCoding / NSCopying Conformance
`AFHTTPClient` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. There are a few minor caveats to keep in mind, however:
- Archives and copies of HTTP clients will be initialized with an empty operation queue.
- NSCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set.
*/
#ifdef _SYSTEMCONFIGURATION_H
typedef enum {
AFNetworkReachabilityStatusUnknown = -1,
@ -47,94 +80,37 @@ typedef enum {
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
} AFNetworkReachabilityStatus;
#else
#pragma message("SystemConfiguration framework not found in project, or not included in precompiled header. Network reachability functionality will not be available.")
#endif
#ifndef __UTTYPE__
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#pragma message("MobileCoreServices framework not found in project, or not included in precompiled header. Automatic MIME type detection when uploading files in multipart requests will not be available.")
#else
#pragma message("CoreServices framework not found in project, or not included in precompiled header. Automatic MIME type detection when uploading files in multipart requests will not be available.")
#endif
#endif
/**
Specifies the method used to encode parameters into request body.
*/
typedef enum {
AFFormURLParameterEncoding,
AFJSONParameterEncoding,
AFPropertyListParameterEncoding,
} AFHTTPClientParameterEncoding;
/**
Returns a string, replacing certain characters with the equivalent percent escape sequence based on the specified encoding.
@param string The string to URL encode
@param encoding The encoding to use for the replacement. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs.
@discussion The characters escaped are all characters that are not legal URL characters (based on RFC 3986), including any whitespace, punctuation, or special characters.
@return A URL-encoded string. If it does not need to be modified (no percent escape sequences are missing), this function may merely return string argument.
*/
extern NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding);
@class AFHTTPRequestOperation;
@protocol AFMultipartFormData;
/**
Returns a query string constructed by a set of parameters, using the specified encoding.
@param parameters The parameters used to construct the query string
@param encoding The encoding to use in constructing the query string. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs.
@discussion Query strings are constructed by collecting each key-value pair, URL-encoding a string representation of the key-value pair, and then joining the components with "&".
If a key-value pair has a an `NSArray` for its value, each member of the array will be represented in the format `key[]=value1&key[]value2`. Otherwise, the key-value pair will be formatted as "key=value". String representations of both keys and values are derived using the `-description` method. The constructed query string does not include the ? character used to delimit the query component.
@return A URL-encoded query string
*/
extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding);
/**
`AFHTTPClient` captures the common patterns of communicating with an web application over HTTP. It encapsulates information like base URL, authorization credentials, and HTTP headers, and uses them to construct and manage the execution of HTTP request operations.
## Automatic Content Parsing
Instances of `AFHTTPClient` may specify which types of requests it expects and should handle by registering HTTP operation classes for automatic parsing. Registered classes will determine whether they can handle a particular request, and then construct a request operation accordingly in `enqueueHTTPRequestOperationWithRequest:success:failure`. See `AFHTTPClientOperation` for further details.
## Subclassing Notes
In most cases, one should create an `AFHTTPClient` subclass for each website or web application that your application communicates with. It is often useful, also, to define a class method that returns a singleton shared HTTP client in each subclass, that persists authentication credentials and other configuration across the entire application.
## Methods to Override
To change the behavior of all url request construction for an `AFHTTPClient` subclass, override `requestWithMethod:path:parameters`.
To change the behavior of all request operation construction for an `AFHTTPClient` subclass, override `HTTPRequestOperationWithRequest:success:failure`.
## Default Headers
By default, `AFHTTPClient` sets the following HTTP headers:
- `Accept-Encoding: gzip`
- `Accept-Language: ([NSLocale preferredLanguages]), en-us;q=0.8`
- `User-Agent: (generated user agent)`
You can override these HTTP headers or define new ones using `setDefaultHeader:value:`.
## URL Construction Using Relative Paths
Both `requestWithMethod:path:parameters` and `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` construct URLs from the path relative to the `baseURL`, using `NSURL +URLWithString:relativeToURL:`. Below are a few examples of how `baseURL` and relative paths interract:
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
*/
@interface AFHTTPClient : NSObject
@interface AFHTTPClient : NSObject <NSCoding, NSCopying>
///---------------------------------------
/// @name Accessing HTTP Client Properties
///---------------------------------------
/**
The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure`
The url used as the base for paths specified in methods such as `getPath:parameters:success:failure`
*/
@property (readonly, nonatomic, retain) NSURL *baseURL;
@property (readonly, nonatomic, strong) NSURL *baseURL;
/**
The string encoding used in constructing url requests. This is `NSUTF8StringEncoding` by default.
@ -144,44 +120,56 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
The `AFHTTPClientParameterEncoding` value corresponding to how parameters are encoded into a request body. This is `AFFormURLParameterEncoding` by default.
@warning JSON encoding will automatically use JSONKit, SBJSON, YAJL, or NextiveJSON, if provided. Otherwise, the built-in `NSJSONSerialization` class is used, if available (iOS 5.0 and Mac OS 10.7). If the build target does not either support `NSJSONSerialization` or include a third-party JSON library, a runtime exception will be thrown when attempting to encode parameters as JSON.
@warning Some nested parameter structures, such as a keyed array of hashes containing inconsistent keys (i.e. `@{@"": @[@{@"a" : @(1)}, @{@"b" : @(2)}]}`), cannot be unambiguously represented in query strings. It is strongly recommended that an unambiguous encoding, such as `AFJSONParameterEncoding`, is used when posting complicated or nondeterministic parameter structures.
*/
@property (nonatomic, assign) AFHTTPClientParameterEncoding parameterEncoding;
/**
The operation queue which manages operations enqueued by the HTTP client.
*/
@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue;
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
/**
The reachability status from the device to the current `baseURL` of the `AFHTTPClient`.
@warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (Prefix.pch).
@warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
*/
#ifdef _SYSTEMCONFIGURATION_H
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
#endif
/**
Default SSL pinning mode for each `AFHTTPRequestOperation` created by `HTTPRequestOperationWithRequest:success:failure:`.
*/
@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode defaultSSLPinningMode;
/**
Whether each `AFHTTPRequestOperation` created by `HTTPRequestOperationWithRequest:success:failure:` should accept an invalid SSL certificate.
If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is set, this property defaults to `YES` for backwards compatibility. Otherwise, this property defaults to `NO`.
*/
@property (nonatomic, assign) BOOL allowsInvalidSSLCertificate;
///---------------------------------------------
/// @name Creating and Initializing HTTP Clients
///---------------------------------------------
/**
Creates and initializes an `AFHTTPClient` object with the specified base URL.
@param url The base URL for the HTTP client. This argument must not be nil.
@param url The base URL for the HTTP client. This argument must not be `nil`.
@return The newly-initialized HTTP client
*/
+ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url;
+ (instancetype)clientWithBaseURL:(NSURL *)url;
/**
Initializes an `AFHTTPClient` object with the specified base URL.
@param url The base URL for the HTTP client. This argument must not be nil.
@discussion This is the designated initializer.
This is the designated initializer.
@param url The base URL for the HTTP client. This argument must not be `nil`.
@return The newly-initialized HTTP client
*/
- (id)initWithBaseURL:(NSURL *)url;
@ -192,10 +180,10 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Sets a callback to be executed when the network availability of the `baseURL` host changes.
@param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`.
@warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (Prefix.pch).
@warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
*/
#ifdef _SYSTEMCONFIGURATION_H
- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block;
@ -207,23 +195,19 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Attempts to register a subclass of `AFHTTPRequestOperation`, adding it to a chain to automatically generate request operations from a URL request.
When `enqueueHTTPRequestOperationWithRequest:success:failure` is invoked, each registered class is consulted in turn to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to create an operation using `initWithURLRequest:` and do `setCompletionBlockWithSuccess:failure:`. There is no guarantee that all registered classes will be consulted. Classes are consulted in the reverse order of their registration. Attempting to register an already-registered class will move it to the top of the list.
@param The subclass of `AFHTTPRequestOperation` to register
@return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` does is not a subclass of `AFHTTPRequestOperation`.
@discussion When `enqueueHTTPRequestOperationWithRequest:success:failure` is invoked, each registered class is consulted in turn to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to create an operation using `initWithURLRequest:` and do `setCompletionBlockWithSuccess:failure:`. There is no guarantee that all registered classes will be consulted. Classes are consulted in the reverse order of their registration. Attempting to register an already-registered class will move it to the top of the list.
@see `AFHTTPClientOperation`
@param operationClass The subclass of `AFHTTPRequestOperation` to register
@return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` is not a subclass of `AFHTTPRequestOperation`.
*/
- (BOOL)registerHTTPOperationClass:(Class)operationClass;
/**
Unregisters the specified subclass of `AFHTTPRequestOperation`.
@param The class conforming to the `AFHTTPClientOperation` protocol to unregister
@discussion After this method is invoked, `operationClass` is no longer consulted when `requestWithMethod:path:parameters` is invoked.
Unregisters the specified subclass of `AFHTTPRequestOperation` from the chain of classes consulted when `-requestWithMethod:path:parameters` is called.
@param operationClass The subclass of `AFHTTPRequestOperation` to register
*/
- (void)unregisterHTTPOperationClass:(Class)operationClass;
@ -233,72 +217,84 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Returns the value for the HTTP headers set in request objects created by the HTTP client.
@param header The HTTP header to return the default value for
@return The default value for the HTTP header, or `nil` if unspecified
*/
- (NSString *)defaultValueForHeader:(NSString *)header;
/**
Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header.
@param header The HTTP header to set a default value for
@param value The value set as default for the specified header, or `nil
*/
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value;
- (void)setDefaultHeader:(NSString *)header
value:(NSString *)value;
/**
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header.
@param username The HTTP basic auth username
@param password The HTTP basic auth password
*/
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password;
- (void)setAuthorizationHeaderWithUsername:(NSString *)username
password:(NSString *)password;
/**
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a token-based authentication value, such as an OAuth access token. This overwrites any existing value for this header.
@param token The authentication token
*/
- (void)setAuthorizationHeaderWithToken:(NSString *)token;
/**
Clears any existing value for the "Authorization" HTTP header.
*/
- (void)clearAuthorizationHeader;
///-------------------------------
/// @name Managing URL Credentials
///-------------------------------
/**
Set the default URL credential to be set for request operations.
@param credential The URL credential
*/
- (void)setDefaultCredential:(NSURLCredential *)credential;
///-------------------------------
/// @name Creating Request Objects
///-------------------------------
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and path.
If the HTTP method is `GET`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body.
@param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
If the HTTP method is `GET`, `HEAD`, or `DELETE`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body.
@param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. This parameter must not be `nil`.
@param path The path to be appended to the HTTP client's base URL and used as the request URL. If `nil`, no path will be appended to the base URL.
@param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body.
@return An `NSMutableURLRequest` object
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters;
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and path, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
Multipart form requests are automatically streamed, reading files directly from disk along with in-memory data in a single HTTP body. The resulting `NSMutableURLRequest` object has an `HTTPBodyStream` property, so refrain from setting `HTTPBodyStream` or `HTTPBody` on this request object, as it will clear out the multipart form body stream.
@param method The HTTP method for the request. Must be either `POST`, `PUT`, or `DELETE`.
@param method The HTTP method for the request. This parameter must not be `GET` or `HEAD`, or `nil`.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. This can be used to upload files, encode HTTP body as JSON or XML, or specify multiple values for the same parameter, as one might for array values.
@discussion The multipart form data is constructed synchronously in the specified block, so in cases where large amounts of data are being added to the request, you should consider performing this method in the background. Likewise, the form data is constructed in-memory, so it may be advantageous to instead write parts of the form data to a file and stream the request body using the `HTTPBodyStream` property of `NSURLRequest`.
@warning An exception will be raised if the specified method is not `POST`, `PUT` or `DELETE`.
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
@ -312,14 +308,14 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Creates an `AFHTTPRequestOperation`.
In order to determine what kind of operation is created, each registered subclass conforming to the `AFHTTPClient` protocol is consulted (in reverse order of when they were specified) to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to generate an operation using `HTTPRequestOperationWithRequest:success:failure:`.
@param request The request object to be loaded asynchronously during execution of the operation.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
*/
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
@ -329,7 +325,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue.
@param operation The HTTP request operation to be enqueued.
*/
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation;
@ -337,8 +333,10 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Cancels all operations in the HTTP client's operation queue whose URLs match the specified HTTP method and path.
@param method The HTTP method to match for the cancelled requests, such as `GET`, `POST`, `PUT`, or `DELETE`. If `nil`, all request operations with URLs matching the path will be cancelled.
@param url The path to match for the cancelled requests.
This method only cancels `AFHTTPRequestOperations` whose request URL matches the HTTP client base URL with the path appended. For complete control over the lifecycle of enqueued operations, you can access the `operationQueue` property directly, which allows you to, for instance, cancel operations filtered by a predicate, or simply use `-cancelAllRequests`. Note that the operation queue may include non-HTTP operations, so be sure to check the type before attempting to directly introspect an operation's `request` property.
@param method The HTTP method to match for the cancelled requests, such as `GET`, `POST`, `PUT`, or `DELETE`. If `nil`, all request operations with URLs matching the path will be cancelled.
@param path The path appended to the HTTP client base URL to match against the cancelled requests. If `nil`, no path will be appended to the base URL.
*/
- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path;
@ -348,26 +346,26 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Creates and enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue for each specified request object into a batch. When each request operation finishes, the specified progress block is executed, until all of the request operations have finished, at which point the completion block also executes.
Operations are created by passing the specified `NSURLRequest` objects in `requests`, using `-HTTPRequestOperationWithRequest:success:failure:`, with `nil` for both the `success` and `failure` parameters.
@param requests The `NSURLRequest` objects used to create and enqueue operations.
@param urlRequests The `NSURLRequest` objects used to create and enqueue operations.
@param progressBlock A block object to be executed upon the completion of each request operation in the batch. This block has no return value and takes two arguments: the number of operations that have already finished execution, and the total number of operations.
@param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations.
@discussion Operations are created by passing the specified `NSURLRequest` objects in `requests`, using `-HTTPRequestOperationWithRequest:success:failure:`, with `nil` for both the `success` and `failure` parameters.
@param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations.
*/
- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests
progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock
- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)urlRequests
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock;
/**
Enqueues the specified request operations into a batch. When each request operation finishes, the specified progress block is executed, until all of the request operations have finished, at which point the completion block also executes.
@param operations The request operations used to be batched and enqueued.
@param progressBlock A block object to be executed upon the completion of each request operation in the batch. This block has no return value and takes two arguments: the number of operations that have already finished execution, and the total number of operations.
@param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations.
@param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations.
*/
- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations
progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock
- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock;
///---------------------------
@ -376,13 +374,13 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Creates an `AFHTTPRequestOperation` with a `GET` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and appended as the query string for the request URL.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see HTTPRequestOperationWithRequest:success:failure
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
@ -391,126 +389,253 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete
/**
Creates an `AFHTTPRequestOperation` with a `POST` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see HTTPRequestOperationWithRequest:success:failure
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `PUT` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see HTTPRequestOperationWithRequest:success:failure
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (void)putPath:(NSString *)path
parameters:(NSDictionary *)parameters
- (void)putPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `DELETE` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param parameters The parameters to be encoded and appended as the query string for the request URL.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see HTTPRequestOperationWithRequest:success:failure
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (void)deletePath:(NSString *)path
parameters:(NSDictionary *)parameters
- (void)deletePath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `PATCH` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see HTTPRequestOperationWithRequest:success:failure
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments: the created request operation and the `NSError` object describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (void)patchPath:(NSString *)path
parameters:(NSDictionary *)parameters
parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
@end
#pragma mark -
///----------------
/// @name Constants
///----------------
/**
The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`.
## Network Reachability
The following constants are provided by `AFHTTPClient` as possible network reachability statuses.
enum {
AFNetworkReachabilityStatusUnknown,
AFNetworkReachabilityStatusNotReachable,
AFNetworkReachabilityStatusReachableViaWWAN,
AFNetworkReachabilityStatusReachableViaWiFi,
}
`AFNetworkReachabilityStatusUnknown`
The `baseURL` host reachability is not known.
`AFNetworkReachabilityStatusNotReachable`
The `baseURL` host cannot be reached.
`AFNetworkReachabilityStatusReachableViaWWAN`
The `baseURL` host can be reached via a cellular connection, such as EDGE or GPRS.
`AFNetworkReachabilityStatusReachableViaWiFi`
The `baseURL` host can be reached via a Wi-Fi connection.
### Keys for Notification UserInfo Dictionary
Strings that are used as keys in a `userInfo` dictionary in a network reachability status change notification.
`AFNetworkingReachabilityNotificationStatusItem`
A key in the userInfo dictionary in a `AFNetworkingReachabilityDidChangeNotification` notification.
The corresponding value is an `NSNumber` object representing the `AFNetworkReachabilityStatus` value for the current reachability status.
## Parameter Encoding
The following constants are provided by `AFHTTPClient` as possible methods for serializing parameters into query string or message body values.
enum {
AFFormURLParameterEncoding,
AFJSONParameterEncoding,
AFPropertyListParameterEncoding,
}
`AFFormURLParameterEncoding`
Parameters are encoded into field/key pairs in the URL query string for `GET` `HEAD` and `DELETE` requests, and in the message body otherwise. Dictionary keys are sorted with the `caseInsensitiveCompare:` selector of their description, in order to mitigate the possibility of ambiguous query strings being generated non-deterministically. See the warning for the `parameterEncoding` property for additional information.
`AFJSONParameterEncoding`
Parameters are encoded into JSON in the message body.
`AFPropertyListParameterEncoding`
Parameters are encoded into a property list in the message body.
*/
///----------------
/// @name Functions
///----------------
/**
Returns a query string constructed by a set of parameters, using the specified encoding.
Query strings are constructed by collecting each key-value pair, percent escaping a string representation of the key-value pair, and then joining the pairs with "&".
@see `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`
If a query string pair has a an `NSArray` for its value, each member of the array will be represented in the format `field[]=value1&field[]value2`. Otherwise, the pair will be formatted as "field=value". String representations of both keys and values are derived using the `-description` method. The constructed query string does not include the ? character used to delimit the query component.
@param parameters The parameters used to construct the query string
@param encoding The encoding to use in constructing the query string. If you are uncertain of the correct encoding, you should use UTF-8 (`NSUTF8StringEncoding`), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs.
@return A percent-escaped query string
*/
extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding);
///--------------------
/// @name Notifications
///--------------------
/**
Posted when network reachability changes.
This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability.
@warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
*/
#ifdef _SYSTEMCONFIGURATION_H
extern NSString * const AFNetworkingReachabilityDidChangeNotification;
extern NSString * const AFNetworkingReachabilityNotificationStatusItem;
#endif
#pragma mark -
extern NSUInteger const kAFUploadStream3GSuggestedPacketSize;
extern NSTimeInterval const kAFUploadStream3GSuggestedDelay;
/**
The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`.
*/
@protocol AFMultipartFormData
/**
Appends HTTP headers, followed by the encoded data and the multipart form boundary.
@param headers The HTTP headers to be appended to the form data.
@param body The data to be encoded and appended to the form data.
*/
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body;
Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary.
/**
Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary.
The filename and MIME type for this data in the form will be automatically generated, using the last path component of the `fileURL` and system associated MIME type for the `fileURL` extension, respectively.
@param data The data to be encoded and appended to the form data.
@param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@return `YES` if the file data was successfully appended, otherwise `NO`.
*/
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name;
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
@param filename The filename to be associated with the specified data. This parameter must not be `nil`.
*/
- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary.
@param fileURL The URL corresponding to the file whose content will be appended to the form.
@param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param fileName The file name to be used in the `Content-Disposition` header. This parameter must not be `nil`.
@param mimeType The declared MIME type of the file data. This parameter must not be `nil`.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@return `YES` if the file data was successfully appended, otherwise `NO`.
@discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively.
@return `YES` if the file data was successfully appended otherwise `NO`.
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error;
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * __autoreleasing *)error;
/**
Appends encoded data to the form data.
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary.
@param inputStream The input stream to be appended to the form data
@param name The name to be associated with the specified input stream. This parameter must not be `nil`.
@param fileName The filename to be associated with the specified input stream. This parameter must not be `nil`.
@param length The length of the specified input stream in bytes.
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
*/
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(unsigned long long)length
mimeType:(NSString *)mimeType;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param fileName The filename to be associated with the specified data. This parameter must not be `nil`.
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
*/
- (void)appendData:(NSData *)data;
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
/**
Appends a string to the form data.
@param string The string to be encoded and appended to the form data.
Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
*/
- (void)appendString:(NSString *)string;
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
/**
Appends HTTP headers, followed by the encoded data and the multipart form boundary.
@param headers The HTTP headers to be appended to the form data.
@param body The data to be encoded and appended to the form data.
*/
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body;
/**
Throttles request bandwidth by limiting the packet size and adding a delay for each chunk read from the upload stream.
When uploading over a 3G or EDGE connection, requests may fail with "request body stream exhausted". Setting a maximum packet size and delay according to the recommended values (`kAFUploadStream3GSuggestedPacketSize` and `kAFUploadStream3GSuggestedDelay`) lowers the risk of the input stream exceeding its allocated bandwidth. Unfortunately, as of iOS 6, there is no definite way to distinguish between a 3G, EDGE, or LTE connection. As such, it is not recommended that you throttle bandwidth based solely on network reachability. Instead, you should consider checking for the "request body stream exhausted" in a failure block, and then retrying the request with throttled bandwidth.
@param numberOfBytes Maximum packet size, in number of bytes. The default packet size for an input stream is 32kb.
@param delay Duration of delay each time a packet is read. By default, no delay is set.
*/
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end

1282
clients/ios/AFNetworking/AFHTTPClient.m Normal file → Executable file

File diff suppressed because it is too large Load diff

83
clients/ios/AFNetworking/AFHTTPRequestOperation.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFHTTPRequestOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -23,13 +23,6 @@
#import <Foundation/Foundation.h>
#import "AFURLConnectionOperation.h"
/**
Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header.
*/
extern NSSet * AFContentTypesFromHTTPHeader(NSString *string);
extern NSString * AFCreateIncompleteDownloadDirectoryPath(void);
/**
`AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request.
*/
@ -42,31 +35,7 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void);
/**
The last HTTP response received by the operation's connection.
*/
@property (readonly, nonatomic, retain) NSHTTPURLResponse *response;
/**
Set a target file for the response, will stream directly into this destination.
Defaults to nil, which will use a memory stream. Will create a new outputStream on change.
Note: Changing this while the request is not in ready state will be ignored.
*/
@property (nonatomic, copy) NSString *responseFilePath;
/**
Expected total length. This is different than expectedContentLength if the file is resumed.
On regular requests, this is equal to self.response.expectedContentLength unless we resume a request.
Note: this can also be -1 if the file size is not sent (*)
*/
@property (assign, readonly) long long totalContentLength;
/**
Indicator for the file offset on partial/resumed downloads.
This is greater than zero if the file download is resumed.
*/
@property (assign, readonly) long long offsetContentLength;
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
///----------------------------------------------------------
/// @name Managing And Checking For Acceptable HTTP Responses
@ -75,50 +44,50 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void);
/**
A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`.
*/
@property (readonly) BOOL hasAcceptableStatusCode;
@property (nonatomic, readonly) BOOL hasAcceptableStatusCode;
/**
A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`.
*/
@property (readonly) BOOL hasAcceptableContentType;
@property (nonatomic, readonly) BOOL hasAcceptableContentType;
/**
/**
The callback dispatch queue on success. If `NULL` (default), the main queue is used.
*/
@property (nonatomic, assign) dispatch_queue_t successCallbackQueue;
/**
/**
The callback dispatch queue on failure. If `NULL` (default), the main queue is used.
*/
@property (nonatomic, assign) dispatch_queue_t failureCallbackQueue;
///-------------------------------------------------------------
/// @name Managing Accceptable HTTP Status Codes & Content Types
///-------------------------------------------------------------
///------------------------------------------------------------
/// @name Managing Acceptable HTTP Status Codes & Content Types
///------------------------------------------------------------
/**
Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
By default, this is the range 200 to 299, inclusive.
*/
+ (NSIndexSet *)acceptableStatusCodes;
/**
Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendents.
Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendants.
@param statusCodes The status codes to be added to the set of acceptable HTTP status codes
*/
+ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes;
/**
Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
By default, this is `nil`.
*/
+ (NSSet *)acceptableContentTypes;
/**
Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendents.
Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendants.
@param contentTypes The content types to be added to the set of acceptable MIME types
*/
@ -131,7 +100,7 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void);
/**
A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`.
@param urlRequest The request that is determined to be supported or not supported for this class.
*/
+ (BOOL)canProcessRequest:(NSURLRequest *)urlRequest;
@ -142,13 +111,23 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void);
/**
Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed.
This method should be overridden in subclasses in order to specify the response object passed into the success block.
@param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request.
@param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occured during the request.
@discussion This method should be overridden in subclasses in order to specify the response object passed into the success block.
@param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request.
*/
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
@end
///----------------
/// @name Functions
///----------------
/**
Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header.
*/
extern NSSet * AFContentTypesFromHTTPHeader(NSString *string);

288
clients/ios/AFNetworking/AFHTTPRequestOperation.m Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFHTTPRequestOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -23,42 +23,50 @@
#import "AFHTTPRequestOperation.h"
#import <objc/runtime.h>
NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete";
// Workaround for change in imp_implementationWithBlock() with Xcode 4.5
#if defined(__IPHONE_6_0) || defined(__MAC_10_8)
#define AF_CAST_TO_BLOCK id
#else
#define AF_CAST_TO_BLOCK __bridge void *
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-selector-match"
NSSet * AFContentTypesFromHTTPHeader(NSString *string) {
static NSCharacterSet *_skippedCharacterSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_skippedCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:@" ,"] retain];
});
if (!string) {
return nil;
}
NSScanner *scanner = [NSScanner scannerWithString:string];
scanner.charactersToBeSkipped = _skippedCharacterSet;
NSMutableSet *mutableContentTypes = [NSMutableSet set];
while (![scanner isAtEnd]) {
NSString *contentType = nil;
if ([scanner scanUpToString:@";" intoString:&contentType]) {
[scanner scanUpToString:@"," intoString:nil];
NSArray *mediaRanges = [string componentsSeparatedByString:@","];
NSMutableSet *mutableContentTypes = [NSMutableSet setWithCapacity:mediaRanges.count];
[mediaRanges enumerateObjectsUsingBlock:^(NSString *mediaRange, __unused NSUInteger idx, __unused BOOL *stop) {
NSRange parametersRange = [mediaRange rangeOfString:@";"];
if (parametersRange.location != NSNotFound) {
mediaRange = [mediaRange substringToIndex:parametersRange.location];
}
if (contentType) {
[mutableContentTypes addObject:contentType];
mediaRange = [mediaRange stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (mediaRange.length > 0) {
[mutableContentTypes addObject:mediaRange];
}
}
}];
return [NSSet setWithSet:mutableContentTypes];
}
static void AFSwizzleClassMethodWithImplementation(Class klass, SEL selector, IMP implementation) {
Method originalMethod = class_getClassMethod(klass, selector);
if (method_getImplementation(originalMethod) != implementation) {
class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod));
static void AFGetMediaTypeAndSubtypeWithString(NSString *string, NSString **type, NSString **subtype) {
if (!string) {
return;
}
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[scanner scanUpToString:@"/" intoString:type];
[scanner scanString:@"/" intoString:nil];
[scanner scanUpToString:@";" intoString:subtype];
}
static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
@ -77,11 +85,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
}
if (range.length == 1) {
[string appendFormat:@"%u", range.location];
[string appendFormat:@"%lu", (long)range.location];
} else {
NSUInteger firstIndex = range.location;
NSUInteger lastIndex = firstIndex + range.length - 1;
[string appendFormat:@"%u-%u", firstIndex, lastIndex];
[string appendFormat:@"%lu-%lu", (long)firstIndex, (long)lastIndex];
}
range.location = nextIndex;
@ -91,77 +99,66 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
return string;
}
NSString * AFCreateIncompleteDownloadDirectoryPath(void) {
static NSString *incompleteDownloadPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *tempDirectory = NSTemporaryDirectory();
incompleteDownloadPath = [[tempDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain];
NSError *error = nil;
NSFileManager *fileMan = [[NSFileManager alloc] init];
if(![fileMan createDirectoryAtPath:incompleteDownloadPath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"Failed to create incomplete downloads directory at %@", incompleteDownloadPath);
}
[fileMan release];
});
return incompleteDownloadPath;
static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL selector, id block) {
Method originalMethod = class_getClassMethod(klass, selector);
IMP implementation = imp_implementationWithBlock((AF_CAST_TO_BLOCK)block);
class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod));
}
#pragma mark -
@interface AFHTTPRequestOperation ()
@property (readwrite, nonatomic, retain) NSURLRequest *request;
@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response;
@property (readwrite, nonatomic, retain) NSError *HTTPError;
@property (assign) long long totalContentLength;
@property (assign) long long offsetContentLength;
@property (readwrite, nonatomic, strong) NSURLRequest *request;
@property (readwrite, nonatomic, strong) NSHTTPURLResponse *response;
@property (readwrite, nonatomic, strong) NSError *HTTPError;
@end
@implementation AFHTTPRequestOperation
@synthesize HTTPError = _HTTPError;
@synthesize responseFilePath = _responseFilePath;
@synthesize successCallbackQueue = _successCallbackQueue;
@synthesize failureCallbackQueue = _failureCallbackQueue;
@synthesize totalContentLength = _totalContentLength;
@synthesize offsetContentLength = _offsetContentLength;
@dynamic request;
@dynamic response;
- (void)dealloc {
[_HTTPError release];
if (_successCallbackQueue) {
if (_successCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_release(_successCallbackQueue);
#endif
_successCallbackQueue = NULL;
}
if (_failureCallbackQueue) {
dispatch_release(_failureCallbackQueue);
if (_failureCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_release(_failureCallbackQueue);
#endif
_failureCallbackQueue = NULL;
}
[super dealloc];
}
- (NSError *)error {
if (self.response && !self.HTTPError) {
if (![self hasAcceptableStatusCode]) {
if (!self.HTTPError && self.response) {
if (![self hasAcceptableStatusCode] || ![self hasAcceptableContentType]) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), [self.response statusCode]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:self.responseString forKey:NSLocalizedRecoverySuggestionErrorKey];
[userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease];
} else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease];
[userInfo setValue:self.request forKey:AFNetworkingOperationFailingURLRequestErrorKey];
[userInfo setValue:self.response forKey:AFNetworkingOperationFailingURLResponseErrorKey];
if (![self hasAcceptableStatusCode]) {
NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200;
[userInfo setValue:[NSString stringWithFormat:NSLocalizedStringFromTable(@"Expected status code in (%@), got %d", @"AFNetworking", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), statusCode] forKey:NSLocalizedDescriptionKey];
self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo];
} else if (![self hasAcceptableContentType]) {
// Don't invalidate content type if there is no content
if ([self.responseData length] > 0) {
[userInfo setValue:[NSString stringWithFormat:NSLocalizedStringFromTable(@"Expected content type %@, got %@", @"AFNetworking", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey];
self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
}
}
}
if (self.HTTPError) {
return self.HTTPError;
} else {
@ -169,113 +166,136 @@ NSString * AFCreateIncompleteDownloadDirectoryPath(void) {
}
}
- (NSStringEncoding)responseStringEncoding {
// When no explicit charset parameter is provided by the sender, media subtypes of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via HTTP. Data in character sets other than "ISO-8859-1" or its subsets MUST be labeled with an appropriate charset value.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.4.1
if (self.response && !self.response.textEncodingName && self.responseData && [self.response respondsToSelector:@selector(allHeaderFields)]) {
NSString *type = nil;
AFGetMediaTypeAndSubtypeWithString([[self.response allHeaderFields] valueForKey:@"Content-Type"], &type, nil);
if ([type isEqualToString:@"text"]) {
return NSISOLatin1StringEncoding;
}
}
return [super responseStringEncoding];
}
- (void)pause {
unsigned long long offset = 0;
unsigned long long offset = 0;
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
offset = [[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue];
} else {
offset = [[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length];
}
NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease];
if ([[self.response allHeaderFields] valueForKey:@"ETag"]) {
NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy];
if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) {
[mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"];
}
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"];
self.request = mutableURLRequest;
[super pause];
}
- (BOOL)hasAcceptableStatusCode {
return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]];
if (!self.response) {
return NO;
}
NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200;
return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:statusCode];
}
- (BOOL)hasAcceptableContentType {
return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]];
if (!self.response) {
return NO;
}
// Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html
NSString *contentType = [self.response MIMEType];
if (!contentType) {
contentType = @"application/octet-stream";
}
return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:contentType];
}
- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue {
if (successCallbackQueue != _successCallbackQueue) {
if (_successCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_release(_successCallbackQueue);
#endif
_successCallbackQueue = NULL;
}
if (successCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_retain(successCallbackQueue);
#endif
_successCallbackQueue = successCallbackQueue;
}
}
}
}
- (void)setFailureCallbackQueue:(dispatch_queue_t)failureCallbackQueue {
if (failureCallbackQueue != _failureCallbackQueue) {
if (_failureCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_release(_failureCallbackQueue);
#endif
_failureCallbackQueue = NULL;
}
if (failureCallbackQueue) {
#if !OS_OBJECT_USE_OBJC
dispatch_retain(failureCallbackQueue);
#endif
_failureCallbackQueue = failureCallbackQueue;
}
}
}
}
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
self.completionBlock = ^ {
if ([self isCancelled]) {
return;
}
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, self.responseData);
});
}
}
};
}
- (void)setResponseFilePath:(NSString *)responseFilePath {
if ([self isReady] && responseFilePath != _responseFilePath) {
[_responseFilePath release];
_responseFilePath = [responseFilePath retain];
if (responseFilePath) {
self.outputStream = [NSOutputStream outputStreamToFileAtPath:responseFilePath append:NO];
}else {
self.outputStream = [NSOutputStream outputStreamToMemory];
}
}
#pragma clang diagnostic pop
}
#pragma mark - AFHTTPRequestOperation
static id AFStaticClassValueImplementation(id self, SEL _cmd) {
return objc_getAssociatedObject([self class], _cmd);
}
+ (NSIndexSet *)acceptableStatusCodes {
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
}
+ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes {
NSMutableIndexSet *mutableStatusCodes = [[[NSMutableIndexSet alloc] initWithIndexSet:[self acceptableStatusCodes]] autorelease];
NSMutableIndexSet *mutableStatusCodes = [[NSMutableIndexSet alloc] initWithIndexSet:[self acceptableStatusCodes]];
[mutableStatusCodes addIndexes:statusCodes];
SEL selector = @selector(acceptableStatusCodes);
AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation);
objc_setAssociatedObject([self class], selector, mutableStatusCodes, OBJC_ASSOCIATION_COPY_NONATOMIC);
AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableStatusCodes), ^(__unused id _self) {
return mutableStatusCodes;
});
}
+ (NSSet *)acceptableContentTypes {
@ -283,53 +303,21 @@ static id AFStaticClassValueImplementation(id self, SEL _cmd) {
}
+ (void)addAcceptableContentTypes:(NSSet *)contentTypes {
NSMutableSet *mutableContentTypes = [[[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES] autorelease];
NSMutableSet *mutableContentTypes = [[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES];
[mutableContentTypes unionSet:contentTypes];
SEL selector = @selector(acceptableContentTypes);
AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation);
objc_setAssociatedObject([self class], selector, mutableContentTypes, OBJC_ASSOCIATION_COPY_NONATOMIC);
AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableContentTypes), ^(__unused id _self) {
return mutableContentTypes;
});
}
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
if ([[self class] isEqual:[AFHTTPRequestOperation class]]) {
return YES;
}
return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:@"Accept"])];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
self.response = (NSHTTPURLResponse *)response;
// 206 = Partial Content.
long long totalContentLength = self.response.expectedContentLength;
long long fileOffset = 0;
if ([self.response statusCode] != 206) {
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
[self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey];
} else {
if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) {
self.outputStream = [NSOutputStream outputStreamToMemory];
}
}
}else {
NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"];
if ([contentRange hasPrefix:@"bytes"]) {
NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
if ([bytes count] == 4) {
fileOffset = [[bytes objectAtIndex:1] longLongValue];
totalContentLength = [[bytes objectAtIndex:2] longLongValue] ?: -1; // if this is *, it's converted to 0, but -1 is default.
}
}
}
self.offsetContentLength = MAX(fileOffset, 0);
self.totalContentLength = totalContentLength;
[self.outputStream open];
}
@end
#pragma clang diagnostic pop

81
clients/ios/AFNetworking/AFImageRequestOperation.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFImageRequestOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -25,19 +25,19 @@
#import <Availability.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
#import <Cocoa/Cocoa.h>
#endif
/**
`AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading an processing images.
`AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and processing images.
## Acceptable Content Types
By default, `AFImageRequestOperation` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
- `image/tiff`
- `image/jpeg`
- `image/gif`
@ -54,59 +54,60 @@
/**
An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED
@property (readonly, nonatomic, retain) UIImage *responseImage;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
@property (readonly, nonatomic, retain) NSImage *responseImage;
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
@property (readonly, nonatomic, strong) UIImage *responseImage;
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
@property (readonly, nonatomic, strong) NSImage *responseImage;
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
/**
The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of `[[UIScreen mainScreen] scale]` by default, which automatically scales images for retina displays, for instance.
The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance.
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
Whether to automatically inflate response image data for compressed formats (such as PNG or JPEG). Enabling this can significantly improve drawing performance on iOS when used with `setCompletionBlockWithSuccess:failure:`, as it allows a bitmap representation to be constructed in the background rather than on the main thread. `YES` by default.
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
#endif
/**
An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error.
*/
/**
Creates and returns an `AFImageRequestOperation` object and sets the specified success callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param success A block object to be executed when the request finishes successfully. This block has no return value and takes a single arguments, the image created from the response data of the request.
@param success A block object to be executed when the request finishes successfully. This block has no return value and takes a single argument, the image created from the response data of the request.
@return A new image request operation
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSImage *image))success;
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success;
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSImage *image))success;
#endif
/**
Creates and returns an `AFImageRequestOperation` object and sets the specified success callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param imageProcessingBlock A block object to be executed after the image request finishes successfully, but before the image is returned in the `success` block. This block takes a single argument, the image loaded from the response body, and returns the processed image.
@param success A block object to be executed when the request finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the image created from the response data.
@param failure A block object to be executed when the request finishes unsuccessfully. This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the error associated with the cause for the unsuccessful operation.
@return A new image request operation
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(UIImage *(^)(UIImage *image))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(NSImage *(^)(NSImage *image))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(UIImage *(^)(UIImage *image))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(NSImage *(^)(NSImage *image))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
#endif
@end

227
clients/ios/AFNetworking/AFImageRequestOperation.m Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFImageRequestOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -22,32 +22,117 @@
#import "AFImageRequestOperation.h"
static dispatch_queue_t af_image_request_operation_processing_queue;
static dispatch_queue_t image_request_operation_processing_queue() {
if (af_image_request_operation_processing_queue == NULL) {
af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", 0);
}
static dispatch_queue_t af_image_request_operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_image_request_operation_processing_queue;
}
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <CoreGraphics/CoreGraphics.h>
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
if ([UIImage instancesRespondToSelector:@selector(initWithData:scale:)]) {
return [[UIImage alloc] initWithData:data scale:scale];
} else {
UIImage *image = [[UIImage alloc] initWithData:data];
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
}
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
CGImageRef imageRef = nil;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
}
if (!imageRef) {
UIImage *image = AFImageWithDataAtScale(data, scale);
if (image.images) {
CGDataProviderRelease(dataProvider);
return image;
}
imageRef = CGImageCreateCopy([image CGImage]);
}
CGDataProviderRelease(dataProvider);
if (!imageRef) {
return nil;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bytesPerRow = 0; // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate()
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
if (CGColorSpaceGetNumberOfComponents(colorSpace) == 3) {
int alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (!context) {
CGImageRelease(imageRef);
return [[UIImage alloc] initWithData:data];
}
CGRect rect = CGRectMake(0.0f, 0.0f, width, height);
CGContextDrawImage(context, rect, imageRef);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
#endif
@interface AFImageRequestOperation ()
#if __IPHONE_OS_VERSION_MIN_REQUIRED
@property (readwrite, nonatomic, retain) UIImage *responseImage;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
@property (readwrite, nonatomic, retain) NSImage *responseImage;
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
@property (readwrite, nonatomic, strong) UIImage *responseImage;
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
@property (readwrite, nonatomic, strong) NSImage *responseImage;
#endif
@end
@implementation AFImageRequestOperation
@synthesize responseImage = _responseImage;
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
@synthesize imageScale = _imageScale;
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success
{
return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) {
if (success) {
@ -55,9 +140,9 @@ static dispatch_queue_t image_request_operation_processing_queue() {
}
} failure:nil];
}
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSImage *image))success
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSImage *image))success
{
return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) {
if (success) {
@ -68,23 +153,25 @@ static dispatch_queue_t image_request_operation_processing_queue() {
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease];
AFImageRequestOperation *requestOperation = [(AFImageRequestOperation *)[self alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
UIImage *image = responseObject;
if (imageProcessingBlock) {
dispatch_async(image_request_operation_processing_queue(), ^(void) {
UIImage *processedImage = imageProcessingBlock(image);
dispatch_async(requestOperation.successCallbackQueue ? requestOperation.successCallbackQueue : dispatch_get_main_queue(), ^(void) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(operation.successCallbackQueue ?: dispatch_get_main_queue(), ^(void) {
success(operation.request, operation.response, processedImage);
});
#pragma clang diagnostic pop
});
} else {
success(operation.request, operation.response, image);
@ -95,17 +182,17 @@ static dispatch_queue_t image_request_operation_processing_queue() {
failure(operation.request, operation.response, error);
}
}];
return requestOperation;
}
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+ (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease];
AFImageRequestOperation *requestOperation = [(AFImageRequestOperation *)[self alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
NSImage *image = responseObject;
@ -113,7 +200,7 @@ static dispatch_queue_t image_request_operation_processing_queue() {
dispatch_async(image_request_operation_processing_queue(), ^(void) {
NSImage *processedImage = imageProcessingBlock(image);
dispatch_async(requestOperation.successCallbackQueue ? requestOperation.successCallbackQueue : dispatch_get_main_queue(), ^(void) {
dispatch_async(operation.successCallbackQueue ?: dispatch_get_main_queue(), ^(void) {
success(operation.request, operation.response, processedImage);
});
});
@ -126,7 +213,7 @@ static dispatch_queue_t image_request_operation_processing_queue() {
failure(operation.request, operation.response, error);
}
}];
return requestOperation;
}
#endif
@ -136,54 +223,55 @@ static dispatch_queue_t image_request_operation_processing_queue() {
if (!self) {
return nil;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
self.imageScale = [[UIScreen mainScreen] scale];
self.automaticallyInflatesResponseImage = YES;
#endif
return self;
}
- (void)dealloc {
[_responseImage release];
[super dealloc];
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
- (UIImage *)responseImage {
if (!_responseImage && [self.responseData length] > 0 && [self isFinished]) {
UIImage *image = [UIImage imageWithData:self.responseData];
self.responseImage = [UIImage imageWithCGImage:[image CGImage] scale:self.imageScale orientation:image.imageOrientation];
if (self.automaticallyInflatesResponseImage) {
self.responseImage = AFInflatedImageFromResponseWithDataAtScale(self.response, self.responseData, self.imageScale);
} else {
self.responseImage = AFImageWithDataAtScale(self.responseData, self.imageScale);
}
}
return _responseImage;
}
- (void)setImageScale:(CGFloat)imageScale {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
if (imageScale == _imageScale) {
return;
}
#pragma clang diagnostic pop
_imageScale = imageScale;
self.responseImage = nil;
}
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
- (NSImage *)responseImage {
if (!_responseImage && [self.responseData length] > 0 && [self isFinished]) {
// Ensure that the image is set to it's correct pixel width and height
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:self.responseData];
self.responseImage = [[[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])] autorelease];
self.responseImage = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[self.responseImage addRepresentation:bitimage];
[bitimage release];
}
return _responseImage;
}
#endif
#pragma mark - AFHTTPClientOperation
#pragma mark - AFHTTPRequestOperation
+ (NSSet *)acceptableContentTypes {
return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];
@ -195,42 +283,43 @@ static dispatch_queue_t image_request_operation_processing_queue() {
dispatch_once(&onceToken, ^{
_acceptablePathExtension = [[NSSet alloc] initWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil];
});
return [_acceptablePathExtension containsObject:[[request URL] pathExtension]] || [super canProcessRequest:request];
return [_acceptablePathExtension containsObject:[[request URL] pathExtension]] || [super canProcessRequest:request];
}
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^ {
if ([self isCancelled]) {
return;
}
dispatch_async(image_request_operation_processing_queue(), ^(void) {
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
} else {
if (success) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
UIImage *image = nil;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
NSImage *image = nil;
#endif
image = self.responseImage;
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, image);
});
}
}
});
};
});
};
#pragma clang diagnostic pop
}
@end

17
clients/ios/AFNetworking/AFJSONRequestOperation.h Normal file → Executable file
View file

@ -33,7 +33,7 @@
- `application/json`
- `text/json`
@warning JSON parsing will automatically use JSONKit, SBJSON, YAJL, or NextiveJSON, if provided. Otherwise, the built-in `NSJSONSerialization` class is used, if available (iOS 5.0 and Mac OS 10.7). If the build target does not either support `NSJSONSerialization` or include a third-party JSON library, a runtime exception will be thrown when attempting to parse a JSON request.
@warning JSON parsing will use the built-in `NSJSONSerialization` class.
*/
@interface AFJSONRequestOperation : AFHTTPRequestOperation
@ -44,8 +44,13 @@
/**
A JSON object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error.
*/
@property (readonly, nonatomic, retain) id responseJSON;
@property (readonly, nonatomic, strong) id responseJSON;
/**
Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions".
*/
@property (nonatomic, assign) NSJSONReadingOptions JSONReadingOptions;
///----------------------------------
/// @name Creating Request Operations
///----------------------------------
@ -55,12 +60,12 @@
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the JSON object created from the response data of request.
@param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as JSON. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@return A new JSON request operation
*/
+ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure;
+ (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure;
@end

88
clients/ios/AFNetworking/AFJSONRequestOperation.m Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFJSONRequestOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -21,31 +21,34 @@
// THE SOFTWARE.
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
static dispatch_queue_t af_json_request_operation_processing_queue;
static dispatch_queue_t json_request_operation_processing_queue() {
if (af_json_request_operation_processing_queue == NULL) {
af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", 0);
}
static dispatch_queue_t af_json_request_operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_json_request_operation_processing_queue;
}
@interface AFJSONRequestOperation ()
@property (readwrite, nonatomic, retain) id responseJSON;
@property (readwrite, nonatomic, retain) NSError *JSONError;
@property (readwrite, nonatomic, strong) id responseJSON;
@property (readwrite, nonatomic, strong) NSError *JSONError;
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
@end
@implementation AFJSONRequestOperation
@synthesize responseJSON = _responseJSON;
@synthesize JSONReadingOptions = _JSONReadingOptions;
@synthesize JSONError = _JSONError;
@dynamic lock;
+ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure
+ (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure
{
AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
AFJSONRequestOperation *requestOperation = [(AFJSONRequestOperation *)[self alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(operation.request, operation.response, responseObject);
@ -55,29 +58,37 @@ static dispatch_queue_t json_request_operation_processing_queue() {
failure(operation.request, operation.response, error, [(AFJSONRequestOperation *)operation responseJSON]);
}
}];
return requestOperation;
}
- (void)dealloc {
[_responseJSON release];
[_JSONError release];
[super dealloc];
}
- (id)responseJSON {
[self.lock lock];
if (!_responseJSON && [self.responseData length] > 0 && [self isFinished] && !self.JSONError) {
NSError *error = nil;
if ([self.responseData length] == 0) {
self.responseJSON = nil;
} else {
self.responseJSON = AFJSONDecode(self.responseData, &error);
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
if (self.responseString && ![self.responseString isEqualToString:@" "]) {
// Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character
// See http://stackoverflow.com/a/12843465/157142
NSData *data = [self.responseString dataUsingEncoding:NSUTF8StringEncoding];
if (data) {
self.responseJSON = [NSJSONSerialization JSONObjectWithData:data options:self.JSONReadingOptions error:&error];
} else {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:@"Operation responseData failed decoding as a UTF-8 string" forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[NSString stringWithFormat:@"Could not decode string: %@", self.responseString] forKey:NSLocalizedFailureReasonErrorKey];
error = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
}
self.JSONError = error;
}
[self.lock unlock];
return _responseJSON;
}
@ -102,37 +113,38 @@ static dispatch_queue_t json_request_operation_processing_queue() {
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^ {
if ([self isCancelled]) {
return;
}
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
dispatch_async(json_request_operation_processing_queue(), ^{
id JSON = self.responseJSON;
if (self.JSONError) {
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, JSON);
});
}
}
}
});
}
};
};
#pragma clang diagnostic pop
}
@end

View file

@ -1,17 +1,17 @@
// AFNetworkActivityIndicatorManager.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -24,34 +24,41 @@
#import <Availability.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
/**
`AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero.
@discussion By setting `isNetworkActivityIndicatorVisible` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself.
You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code:
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
By setting `isNetworkActivityIndicatorVisible` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself.
See the Apple Human Interface Guidelines section about the Network Activity Indicator for more information:
http://developer.apple.com/library/iOS/#documentation/UserExperience/Conceptual/MobileHIG/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW44
*/
@interface AFNetworkActivityIndicatorManager : NSObject
/**
A Boolean value indicating whether the manager is enabled.
@discussion If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO.
A Boolean value indicating whether the manager is enabled.
If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO.
*/
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
/**
A Boolean value indicating whether the network activity indicator is currently displayed in the status bar.
*/
@property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible;
@property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible;
/**
Returns the shared network activity indicator manager object for the system.
@return The systemwide network activity indicator manager.
*/
+ (AFNetworkActivityIndicatorManager *)sharedManager;
+ (instancetype)sharedManager;
/**
Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator.

View file

@ -1,17 +1,17 @@
// AFNetworkActivityIndicatorManager.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -24,15 +24,16 @@
#import "AFHTTPRequestOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED
static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17;
@interface AFNetworkActivityIndicatorManager ()
@property (readwrite, atomic, assign) NSInteger activityCount;
@property (readwrite, nonatomic, retain) NSTimer *activityIndicatorVisibilityTimer;
@property (readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
@property (readwrite, nonatomic, assign) NSInteger activityCount;
@property (readwrite, nonatomic, strong) NSTimer *activityIndicatorVisibilityTimer;
@property (readonly, nonatomic, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
- (void)updateNetworkActivityIndicatorVisibility;
- (void)updateNetworkActivityIndicatorVisibilityDelayed;
@end
@implementation AFNetworkActivityIndicatorManager
@ -41,35 +42,37 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
@synthesize enabled = _enabled;
@dynamic networkActivityIndicatorVisible;
+ (AFNetworkActivityIndicatorManager *)sharedManager {
+ (instancetype)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
+ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible {
return [NSSet setWithObject:@"activityCount"];
}
- (id)init {
self = [super init];
if (!self) {
return nil;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incrementActivityCount) name:AFNetworkingOperationDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(decrementActivityCount) name:AFNetworkingOperationDidFinishNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingOperationDidStart:) name:AFNetworkingOperationDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_activityIndicatorVisibilityTimer invalidate];
[_activityIndicatorVisibilityTimer release]; _activityIndicatorVisibilityTimer = nil;
[super dealloc];
}
- (void)updateNetworkActivityIndicatorVisibilityDelayed {
@ -78,9 +81,9 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
if (![self isNetworkActivityIndicatorVisible]) {
[self.activityIndicatorVisibilityTimer invalidate];
self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes];
} else {
[self updateNetworkActivityIndicatorVisibility];
[self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
}
}
@ -90,9 +93,7 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
}
- (void)updateNetworkActivityIndicatorVisibility {
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];
});
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];
}
// Not exposed, but used if activityCount is set via KVC.
@ -104,7 +105,10 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
@synchronized(self) {
_activityCount = activityCount;
}
[self updateNetworkActivityIndicatorVisibilityDelayed];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
- (void)incrementActivityCount {
@ -113,20 +117,39 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
[self updateNetworkActivityIndicatorVisibilityDelayed];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount--;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
_activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
}
[self didChangeValueForKey:@"activityCount"];
[self updateNetworkActivityIndicatorVisibilityDelayed];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
+ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible {
return [NSSet setWithObject:@"activityCount"];
- (void)networkingOperationDidStart:(NSNotification *)notification {
AFURLConnectionOperation *connectionOperation = [notification object];
if (connectionOperation.request.URL) {
[self incrementActivityCount];
}
}
- (void)networkingOperationDidFinish:(NSNotification *)notification {
AFURLConnectionOperation *connectionOperation = [notification object];
if (connectionOperation.request.URL) {
[self decrementActivityCount];
}
}
@end

25
clients/ios/AFNetworking/AFNetworking.h Normal file → Executable file
View file

@ -24,21 +24,20 @@
#import <Availability.h>
#ifndef _AFNETWORKING_
#define _AFNETWORKING_
#define _AFNETWORKING_
#import "AFURLConnectionOperation.h"
#import "AFURLConnectionOperation.h"
#import "AFHTTPRequestOperation.h"
#import "AFJSONRequestOperation.h"
#import "AFXMLRequestOperation.h"
#import "AFPropertyListRequestOperation.h"
#import "AFHTTPClient.h"
#import "AFHTTPRequestOperation.h"
#import "AFJSONRequestOperation.h"
#import "AFXMLRequestOperation.h"
#import "AFPropertyListRequestOperation.h"
#import "AFHTTPClient.h"
#import "AFImageRequestOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import "AFNetworkActivityIndicatorManager.h"
#import "UIImageView+AFNetworking.h"
#endif
#import "AFImageRequestOperation.h"
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFNetworkActivityIndicatorManager.h"
#import "UIImageView+AFNetworking.h"
#endif
#endif /* _AFNETWORKING_ */

View file

@ -1,17 +1,17 @@
// AFPropertyListRequestOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -25,11 +25,11 @@
/**
`AFPropertyListRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and deserializing objects with property list (plist) response data.
## Acceptable Content Types
By default, `AFPropertyListRequestOperation` accepts the following MIME types:
- `application/x-plist`
*/
@interface AFPropertyListRequestOperation : AFHTTPRequestOperation
@ -41,7 +41,7 @@
/**
An object deserialized from a plist constructed using the response data.
*/
@property (readonly, nonatomic, retain) id responsePropertyList;
@property (readonly, nonatomic) id responsePropertyList;
///--------------------------------------
/// @name Managing Property List Behavior
@ -54,15 +54,15 @@
/**
Creates and returns an `AFPropertyListRequestOperation` object and sets the specified success and failure callbacks.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the object deserialized from a plist constructed using the response data.
@param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while deserializing the object from a property list. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@return A new property list request operation
*/
+ (AFPropertyListRequestOperation *)propertyListRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure;
+ (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure;
@end

View file

@ -1,17 +1,17 @@
// AFPropertyListRequestOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -22,19 +22,20 @@
#import "AFPropertyListRequestOperation.h"
static dispatch_queue_t af_property_list_request_operation_processing_queue;
static dispatch_queue_t property_list_request_operation_processing_queue() {
if (af_property_list_request_operation_processing_queue == NULL) {
af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", 0);
}
static dispatch_queue_t af_property_list_request_operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_property_list_request_operation_processing_queue;
}
@interface AFPropertyListRequestOperation ()
@property (readwrite, nonatomic, retain) id responsePropertyList;
@property (readwrite, nonatomic) id responsePropertyList;
@property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat;
@property (readwrite, nonatomic, retain) NSError *propertyListError;
@property (readwrite, nonatomic) NSError *propertyListError;
@end
@implementation AFPropertyListRequestOperation
@ -43,11 +44,11 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
@synthesize propertyListFormat = _propertyListFormat;
@synthesize propertyListError = _propertyListError;
+ (AFPropertyListRequestOperation *)propertyListRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure
+ (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure
{
AFPropertyListRequestOperation *requestOperation = [[[self alloc] initWithRequest:request] autorelease];
AFPropertyListRequestOperation *requestOperation = [(AFPropertyListRequestOperation *)[self alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(operation.request, operation.response, responseObject);
@ -57,7 +58,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
failure(operation.request, operation.response, error, [(AFPropertyListRequestOperation *)operation responsePropertyList]);
}
}];
return requestOperation;
}
@ -66,17 +67,12 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
if (!self) {
return nil;
}
self.propertyListReadOptions = NSPropertyListImmutable;
return self;
}
- (void)dealloc {
[_responsePropertyList release];
[_propertyListError release];
[super dealloc];
}
- (id)responsePropertyList {
if (!_responsePropertyList && [self.responseData length] > 0 && [self isFinished]) {
@ -86,7 +82,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
self.propertyListFormat = format;
self.propertyListError = error;
}
return _responsePropertyList;
}
@ -111,37 +107,37 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^ {
if ([self isCancelled]) {
return;
}
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
dispatch_async(property_list_request_operation_processing_queue(), ^(void) {
id propertyList = self.responsePropertyList;
if (self.propertyListError) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, propertyList);
});
}
}
}
});
}
};
};
#pragma clang diagnostic pop
}
@end

554
clients/ios/AFNetworking/AFURLConnectionOperation.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFURLConnectionOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -22,12 +22,344 @@
#import <Foundation/Foundation.h>
#import <Availability.h>
/**
Indicates an error occured in AFNetworking.
`AFURLConnectionOperation` is a subclass of `NSOperation` that implements `NSURLConnection` delegate methods.
## Subclassing Notes
This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors.
If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes.
## NSURLConnection Delegate Methods
`AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods:
- `connection:didReceiveResponse:`
- `connection:didReceiveData:`
- `connectionDidFinishLoading:`
- `connection:didFailWithError:`
- `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
- `connection:willCacheResponse:`
- `connectionShouldUseCredentialStorage:`
- `connection:needNewBodyStream:`
- `connection:willSendRequestForAuthenticationChallenge:`
If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first.
## Class Constructors
Class constructors, or methods that return an unowned instance, are the preferred way for subclasses to encapsulate any particular logic for handling the setup or parsing of response data. For instance, `AFJSONRequestOperation` provides `JSONRequestOperationWithRequest:success:failure:`, which takes block arguments, whose parameter on for a successful request is the JSON object initialized from the `response data`.
## Callbacks and Completion Blocks
The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this.
Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/).
@discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain.
## SSL Pinning
Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle.
SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice.
When `defaultSSLPinningMode` is defined on `AFHTTPClient` and the Security framework is linked, connections will be validated on all matching certificates with a `.cer` extension in the bundle root.
## NSCoding & NSCopying Conformance
`AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind:
### NSCoding Caveats
- Encoded operations do not include any block or stream properties. Be sure to set `completionBlock`, `outputStream`, and any callback blocks as necessary when using `-initWithCoder:` or `NSKeyedUnarchiver`.
- Operations are paused on `encodeWithCoder:`. If the operation was encoded while paused or still executing, its archived state will return `YES` for `isReady`. Otherwise, the state of an operation when encoding will remain unchanged.
### NSCopying Caveats
- `-copy` and `-copyWithZone:` return a new operation with the `NSURLRequest` of the original. So rather than an exact copy of the operation at that particular instant, the copying mechanism returns a completely new instance, which can be useful for retrying operations.
- A copy of an operation will not include the `outputStream` of the original.
- Operation copies do not include `completionBlock`. `completionBlock` often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied.
*/
typedef enum {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
} AFURLConnectionOperationSSLPinningMode;
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate,
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000) || \
(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
NSURLConnectionDataDelegate,
#endif
NSCoding, NSCopying>
///-------------------------------
/// @name Accessing Run Loop Modes
///-------------------------------
/**
The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`.
*/
@property (nonatomic, strong) NSSet *runLoopModes;
///-----------------------------------------
/// @name Getting URL Connection Information
///-----------------------------------------
/**
The request used by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSURLRequest *request;
/**
The last response received by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSURLResponse *response;
/**
The error, if any, that occurred in the lifecycle of the request.
*/
@property (readonly, nonatomic, strong) NSError *error;
/**
Whether the connection should accept an invalid SSL certificate.
If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is set, this property defaults to `YES` for backwards compatibility. Otherwise, this property defaults to `NO`.
*/
@property (nonatomic, assign) BOOL allowsInvalidSSLCertificate;
///----------------------------
/// @name Getting Response Data
///----------------------------
/**
The data received during the request.
*/
@property (readonly, nonatomic, strong) NSData *responseData;
/**
The string representation of the response data.
*/
@property (readonly, nonatomic, copy) NSString *responseString;
/**
The string encoding of the response.
If the response does not specify a valid string encoding, `responseStringEncoding` will return `NSUTF8StringEncoding`.
*/
@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding;
///-------------------------------
/// @name Managing URL Credentials
///-------------------------------
/**
Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`.
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
/**
The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
*/
@property (nonatomic, strong) NSURLCredential *credential;
/**
The pinning mode which will be used for SSL connections. `AFSSLPinningModePublicKey` by default.
SSL Pinning requires that the Security framework is linked with the binary. See the "SSL Pinning" section in the `AFURLConnectionOperation`" header for more information.
*/
@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode SSLPinningMode;
///------------------------
/// @name Accessing Streams
///------------------------
/**
The input stream used to read data to be sent during the request.
This property acts as a proxy to the `HTTPBodyStream` property of `request`.
*/
@property (nonatomic, strong) NSInputStream *inputStream;
/**
The output stream that is used to write data received until the request is finished.
By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set.
*/
@property (nonatomic, strong) NSOutputStream *outputStream;
///---------------------------------------------
/// @name Managing Request Operation Information
///---------------------------------------------
/**
The user info dictionary for the receiver.
*/
@property (nonatomic, strong) NSDictionary *userInfo;
///------------------------------------------------------
/// @name Initializing an AFURLConnectionOperation Object
///------------------------------------------------------
/**
Initializes and returns a newly allocated operation object with a url connection configured with the specified url request.
This is the designated initializer.
@param urlRequest The request object to be used by the operation connection.
*/
- (id)initWithRequest:(NSURLRequest *)urlRequest;
///----------------------------------
/// @name Pausing / Resuming Requests
///----------------------------------
/**
Pauses the execution of the request operation.
A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect.
*/
- (void)pause;
/**
Whether the request operation is currently paused.
@return `YES` if the operation is currently paused, otherwise `NO`.
*/
- (BOOL)isPaused;
/**
Resumes the execution of the paused request operation.
Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request.
*/
- (void)resume;
///----------------------------------------------
/// @name Configuring Backgrounding Task Behavior
///----------------------------------------------
/**
Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task.
@param handler A handler to be called shortly before the applications remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the applications suspension momentarily while the application is notified.
*/
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler;
#endif
///---------------------------------
/// @name Setting Progress Callbacks
///---------------------------------
/**
Sets a callback to be called when an undetermined number of bytes have been uploaded to the server.
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread.
*/
- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block;
/**
Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread.
*/
- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block;
///-------------------------------------------------
/// @name Setting NSURLConnection Delegate Callbacks
///-------------------------------------------------
/**
Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`.
@param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol).
If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates.
*/
- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
/**
Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequest:redirectResponse:`.
@param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect.
*/
- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block;
/**
Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`.
@param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request.
*/
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
@end
///----------------
/// @name Constants
///----------------
/**
## SSL Pinning Options
The following constants are provided by `AFURLConnectionOperation` as possible SSL Pinning options.
enum {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
}
`AFSSLPinningModeNone`
Do not pin SSL connections
`AFSSLPinningModePublicKey`
Pin SSL connections to certificate public key (SPKI).
`AFSSLPinningModeCertificate`
Pin SSL connections to exact certificate. This may cause problems when your certificate expires and needs re-issuance.
## User info dictionary keys
These keys may exist in the user info dictionary, in addition to those defined for NSError.
- `NSString * const AFNetworkingOperationFailingURLRequestErrorKey`
- `NSString * const AFNetworkingOperationFailingURLResponseErrorKey`
### Constants
`AFNetworkingOperationFailingURLRequestErrorKey`
The corresponding value is an `NSURLRequest` containing the request of the operation associated with an error. This key is only present in the `AFNetworkingErrorDomain`.
`AFNetworkingOperationFailingURLResponseErrorKey`
The corresponding value is an `NSURLResponse` containing the response of the operation associated with an error. This key is only present in the `AFNetworkingErrorDomain`.
## Error Domains
The following error domain is predefined.
- `NSString * const AFNetworkingErrorDomain`
### Constants
`AFNetworkingErrorDomain`
AFNetworking errors. Error codes for `AFNetworkingErrorDomain` correspond to codes in `NSURLErrorDomain`.
*/
extern NSString * const AFNetworkingErrorDomain;
extern NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
extern NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
///--------------------
/// @name Notifications
///--------------------
/**
Posted when an operation begins executing.
@ -38,215 +370,3 @@ extern NSString * const AFNetworkingOperationDidStartNotification;
Posted when an operation finishes.
*/
extern NSString * const AFNetworkingOperationDidFinishNotification;
/**
`AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods.
## Subclassing Notes
This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors.
If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes.
## NSURLConnection Delegate Methods
`AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods:
- `connection:didReceiveResponse:`
- `connection:didReceiveData:`
- `connectionDidFinishLoading:`
- `connection:didFailWithError:`
- `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
- `connection:willCacheResponse:`
- `connection:canAuthenticateAgainstProtectionSpace:`
- `connection:didReceiveAuthenticationChallenge:`
If any of these methods are overriden in a subclass, they _must_ call the `super` implementation first.
## Class Constructors
Class constructors, or methods that return an unowned (zero retain count) instance, are the preferred way for subclasses to encapsulate any particular logic for handling the setup or parsing of response data. For instance, `AFJSONRequestOperation` provides `JSONRequestOperationWithRequest:success:failure:`, which takes block arguments, whose parameter on for a successful request is the JSON object initialized from the `response data`.
## Callbacks and Completion Blocks
The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (e.g. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this.
@warning Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as "The Deallocation Problem" (See http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11)
@warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5.
*/
@interface AFURLConnectionOperation : NSOperation
///-------------------------------
/// @name Accessing Run Loop Modes
///-------------------------------
/**
The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`.
*/
@property (nonatomic, retain) NSSet *runLoopModes;
///-----------------------------------------
/// @name Getting URL Connection Information
///-----------------------------------------
/**
The request used by the operation's connection.
*/
@property (readonly, nonatomic, retain) NSURLRequest *request;
/**
The last response received by the operation's connection.
*/
@property (readonly, nonatomic, retain) NSURLResponse *response;
/**
The error, if any, that occured in the lifecycle of the request.
*/
@property (readonly, nonatomic, retain) NSError *error;
///----------------------------
/// @name Getting Response Data
///----------------------------
/**
The data received during the request.
*/
@property (readonly, nonatomic, retain) NSData *responseData;
/**
The string representation of the response data.
@discussion This method uses the string encoding of the response, or if UTF-8 if not specified, to construct a string from the response data.
*/
@property (readonly, nonatomic, copy) NSString *responseString;
///------------------------
/// @name Accessing Streams
///------------------------
/**
The input stream used to read data to be sent during the request.
@discussion This property acts as a proxy to the `HTTPBodyStream` property of `request`.
*/
@property (nonatomic, retain) NSInputStream *inputStream;
/**
The output stream that is used to write data received until the request is finished.
@discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set.
*/
@property (nonatomic, retain) NSOutputStream *outputStream;
///------------------------------------------------------
/// @name Initializing an AFURLConnectionOperation Object
///------------------------------------------------------
/**
Initializes and returns a newly allocated operation object with a url connection configured with the specified url request.
@param urlRequest The request object to be used by the operation connection.
@discussion This is the designated initializer.
*/
- (id)initWithRequest:(NSURLRequest *)urlRequest;
///----------------------------------
/// @name Pausing / Resuming Requests
///----------------------------------
/**
Pauses the execution of the request operation.
@discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished or cancelled operation has no effect.
*/
- (void)pause;
/**
Whether the request operation is currently paused.
@return `YES` if the operation is currently paused, otherwise `NO`.
*/
- (BOOL)isPaused;
/**
Resumes the execution of the paused request operation.
@discussion Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request.
*/
- (void)resume;
///----------------------------------------------
/// @name Configuring Backgrounding Task Behavior
///----------------------------------------------
/**
Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task.
@param handler A handler to be called shortly before the applications remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the applications suspension momentarily while the application is notified.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler;
#endif
///---------------------------------
/// @name Setting Progress Callbacks
///---------------------------------
/**
Sets a callback to be called when an undetermined number of bytes have been uploaded to the server.
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times.
@see setDownloadProgressBlock
*/
- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block;
/**
Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times.
@see setUploadProgressBlock
*/
- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block;
///-------------------------------------------------
/// @name Setting NSURLConnection Delegate Callbacks
///-------------------------------------------------
/**
Sets a block to be executed to determine whether the connection should be able to respond to a protection space's form of authentication, as handled by the `NSURLConnectionDelegate` method `connection:canAuthenticateAgainstProtectionSpace:`.
@param block A block object to be executed to determine whether the connection should be able to respond to a protection space's form of authentication. The block has a `BOOL` return type and takes two arguments: the URL connection object, and the protection space to authenticate against.
@discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:canAuthenticateAgainstProtectionSpace:` will accept invalid SSL certificates, returning `YES` if the protection space authentication method is `NSURLAuthenticationMethodServerTrust`.
*/
- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace))block;
/**
Sets a block to be executed when the connection must authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:didReceiveAuthenticationChallenge:`.
@param block A block object to be executed when the connection must authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated.
@discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:didReceiveAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates.
*/
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
/**
Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequest:redirectResponse:`.
@param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect.
*/
- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block;
/**
Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`.
@param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request.
*/
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
@end

677
clients/ios/AFNetworking/AFURLConnectionOperation.m Normal file → Executable file

File diff suppressed because it is too large Load diff

46
clients/ios/AFNetworking/AFXMLRequestOperation.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFXMLRequestOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -27,16 +27,16 @@
/**
`AFXMLRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with XML response data.
## Acceptable Content Types
By default, `AFXMLRequestOperation` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
- `application/xml`
- `text/xml`
## Use With AFHTTPClient
When `AFXMLRequestOperation` is registered with `AFHTTPClient`, the response object in the success callback of `HTTPRequestOperationWithRequest:success:failure:` will be an instance of `NSXMLParser`. On platforms that support `NSXMLDocument`, you have the option to ignore the response object, and simply use the `responseXMLDocument` property of the operation argument of the callback.
*/
@interface AFXMLRequestOperation : AFHTTPRequestOperation
@ -48,42 +48,42 @@
/**
An `NSXMLParser` object constructed from the response data.
*/
@property (readonly, nonatomic, retain) NSXMLParser *responseXMLParser;
@property (readonly, nonatomic, strong) NSXMLParser *responseXMLParser;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
/**
An `NSXMLDocument` object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error.
*/
@property (readonly, nonatomic, retain) NSXMLDocument *responseXMLDocument;
@property (readonly, nonatomic, strong) NSXMLDocument *responseXMLDocument;
#endif
/**
Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML parser constructed with the response data of request.
@param failure A block object to be executed when the operation finishes unsuccessfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network error that occurred.
@return A new XML request operation
*/
+ (AFXMLRequestOperation *)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParse))failure;
+ (instancetype)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
/**
Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML document created from the response data of request.
@param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as XML. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as XML. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@return A new XML request operation
*/
+ (AFXMLRequestOperation *)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure;
+ (instancetype)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure;
#endif
@end

92
clients/ios/AFNetworking/AFXMLRequestOperation.m Normal file → Executable file
View file

@ -1,17 +1,17 @@
// AFXMLRequestOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -24,35 +24,36 @@
#include <Availability.h>
static dispatch_queue_t af_xml_request_operation_processing_queue;
static dispatch_queue_t xml_request_operation_processing_queue() {
if (af_xml_request_operation_processing_queue == NULL) {
af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", 0);
}
static dispatch_queue_t af_xml_request_operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_xml_request_operation_processing_queue;
}
@interface AFXMLRequestOperation ()
@property (readwrite, nonatomic, retain) NSXMLParser *responseXMLParser;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
@property (readwrite, nonatomic, retain) NSXMLDocument *responseXMLDocument;
@property (readwrite, nonatomic, strong) NSXMLParser *responseXMLParser;
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
@property (readwrite, nonatomic, strong) NSXMLDocument *responseXMLDocument;
#endif
@property (readwrite, nonatomic, retain) NSError *XMLError;
@property (readwrite, nonatomic, strong) NSError *XMLError;
@end
@implementation AFXMLRequestOperation
@synthesize responseXMLParser = _responseXMLParser;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
@synthesize responseXMLDocument = _responseXMLDocument;
#endif
@synthesize XMLError = _XMLError;
+ (AFXMLRequestOperation *)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure
+ (instancetype)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure
{
AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
AFXMLRequestOperation *requestOperation = [(AFXMLRequestOperation *)[self alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(operation.request, operation.response, responseObject);
@ -62,19 +63,19 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
failure(operation.request, operation.response, error, [(AFXMLRequestOperation *)operation responseXMLParser]);
}
}];
return requestOperation;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED
+ (AFXMLRequestOperation *)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
+ (instancetype)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure
{
AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
AFXMLRequestOperation *requestOperation = [[self alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, __unused id responseObject) {
if (success) {
NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument];
NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument];
success(operation.request, operation.response, XMLDocument);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
@ -83,39 +84,28 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
failure(operation.request, operation.response, error, XMLDocument);
}
}];
return requestOperation;
}
#endif
- (void)dealloc {
[_responseXMLParser release];
#if __MAC_OS_X_VERSION_MIN_REQUIRED
[_responseXMLDocument release];
#endif
[_XMLError release];
[super dealloc];
}
- (NSXMLParser *)responseXMLParser {
if (!_responseXMLParser && [self.responseData length] > 0 && [self isFinished]) {
self.responseXMLParser = [[[NSXMLParser alloc] initWithData:self.responseData] autorelease];
self.responseXMLParser = [[NSXMLParser alloc] initWithData:self.responseData];
}
return _responseXMLParser;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
- (NSXMLDocument *)responseXMLDocument {
if (!_responseXMLDocument && [self.responseData length] > 0 && [self isFinished]) {
NSError *error = nil;
self.responseXMLDocument = [[[NSXMLDocument alloc] initWithData:self.responseData options:0 error:&error] autorelease];
self.responseXMLDocument = [[NSXMLDocument alloc] initWithData:self.responseData options:0 error:&error];
self.XMLError = error;
}
return _responseXMLDocument;
}
#endif
@ -132,7 +122,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
- (void)cancel {
[super cancel];
self.responseXMLParser.delegate = nil;
}
@ -149,29 +139,29 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^ {
if ([self isCancelled]) {
return;
}
dispatch_async(xml_request_operation_processing_queue(), ^(void) {
NSXMLParser *XMLParser = self.responseXMLParser;
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, XMLParser);
});
}
}
}
});
};
};
#pragma clang diagnostic pop
}
@end

28
clients/ios/AFNetworking/UIImageView+AFNetworking.h Normal file → Executable file
View file

@ -1,17 +1,17 @@
// UIImageView+AFNetworking.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -35,34 +35,36 @@
/**
Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL, and sets it the request is finished. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@discussion By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set to use HTTP pipelining, and not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
*/
- (void)setImageWithURL:(NSURL *)url;
/**
Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@discussion By default, URL requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set to use HTTP pipelining, and not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
*/
- (void)setImageWithURL:(NSURL *)url
*/
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage;
/**
Creates and enqueues an image request operation, which asynchronously downloads the image with the specified URL request object. Any previous image request for the receiver will be cancelled. If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is executed.
@param urlRequest The URL request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@param success A block to be executed when the image request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the request and response parameters will be `nil`.
@param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
*/
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
*/
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;

92
clients/ios/AFNetworking/UIImageView+AFNetworking.m Normal file → Executable file
View file

@ -1,17 +1,17 @@
// UIImageView+AFNetworking.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -23,7 +23,7 @@
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "UIImageView+AFNetworking.h"
@interface AFImageCache : NSCache
@ -37,7 +37,7 @@
static char kAFImageRequestOperationObjectKey;
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, retain, setter = af_setImageRequestOperation:) AFImageRequestOperation *af_imageRequestOperation;
@property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFImageRequestOperation *af_imageRequestOperation;
@end
@implementation UIImageView (_AFNetworking)
@ -58,12 +58,12 @@ static char kAFImageRequestOperationObjectKey;
+ (NSOperationQueue *)af_sharedImageRequestOperationQueue {
static NSOperationQueue *_af_imageRequestOperationQueue = nil;
if (!_af_imageRequestOperationQueue) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_imageRequestOperationQueue = [[NSOperationQueue alloc] init];
[_af_imageRequestOperationQueue setMaxConcurrentOperationCount:8];
}
[_af_imageRequestOperationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
});
return _af_imageRequestOperationQueue;
}
@ -73,7 +73,7 @@ static char kAFImageRequestOperationObjectKey;
dispatch_once(&oncePredicate, ^{
_af_imageCache = [[AFImageCache alloc] init];
});
return _af_imageCache;
}
@ -83,61 +83,65 @@ static char kAFImageRequestOperationObjectKey;
[self setImageWithURL:url placeholderImage:nil];
}
- (void)setImageWithURL:(NSURL *)url
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[request setHTTPShouldHandleCookies:NO];
[request setHTTPShouldUsePipelining:YES];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
[self cancelImageRequestOperation];
UIImage *cachedImage = [[[self class] af_sharedImageCache] cachedImageForRequest:urlRequest];
if (cachedImage) {
self.image = cachedImage;
self.af_imageRequestOperation = nil;
if (success) {
success(nil, nil, cachedImage);
} else {
self.image = cachedImage;
}
} else {
self.image = placeholderImage;
AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) {
self.image = responseObject;
self.af_imageRequestOperation = nil;
}
if (success) {
success(operation.request, operation.response, responseObject);
self.af_imageRequestOperation = nil;
} else {
if (placeholderImage) {
self.image = placeholderImage;
}
AFImageRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:urlRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([urlRequest isEqual:[self.af_imageRequestOperation request]]) {
if (success) {
success(operation.request, operation.response, responseObject);
} else if (responseObject) {
self.image = responseObject;
}
if (self.af_imageRequestOperation == operation) {
self.af_imageRequestOperation = nil;
}
}
[[[self class] af_sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) {
self.af_imageRequestOperation = nil;
}
if ([urlRequest isEqual:[self.af_imageRequestOperation request]]) {
if (failure) {
failure(operation.request, operation.response, error);
}
if (failure) {
failure(operation.request, operation.response, error);
if (self.af_imageRequestOperation == operation) {
self.af_imageRequestOperation = nil;
}
}
}];
self.af_imageRequestOperation = requestOperation;
[[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation];
}
}
@ -165,7 +169,7 @@ static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
default:
break;
}
return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];
}

View file

@ -4562,7 +4562,8 @@ static NSOperationQueue *sharedQueue = nil;
for (NSNumber *bytes in bandwidthUsageTracker) {
totalBytes += [bytes unsignedLongValue];
}
averageBandwidthUsedPerSecond = totalBytes/measurements;
if (measurements)
averageBandwidthUsedPerSecond = totalBytes/measurements;
}
+ (unsigned long)averageBandwidthUsedPerSecond
@ -4860,7 +4861,7 @@ static NSOperationQueue *sharedQueue = nil;
// RFC 2612 says max-age must override any Expires header
if (maxAge) {
return [[NSDate date] addTimeInterval:maxAge];
return [[NSDate date] dateByAddingTimeInterval:maxAge];
} else {
NSString *expires = [responseHeaders objectForKey:@"Expires"];
if (expires) {

View file

@ -24,6 +24,7 @@
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
activityLabel = nil;
faviconView = nil;
self.separatorInset = UIEdgeInsetsMake(0, 90, 0, 0);
// create favicon and label in view
UIImageView *favicon = [[UIImageView alloc] initWithFrame:CGRectZero];
@ -34,6 +35,7 @@
activity.backgroundColor = [UIColor whiteColor];
self.activityLabel = activity;
[self.contentView addSubview:activity];
topMargin = 15;
bottomMargin = 15;
@ -50,21 +52,21 @@
[super layoutSubviews];
// determine outer bounds
CGRect contentRect = self.contentView.bounds;
[self.activityLabel sizeToFit];
CGRect contentRect = self.frame;
CGRect labelFrame = self.activityLabel.frame;
// position label to bounds
CGRect labelRect = contentRect;
labelRect.origin.x = labelRect.origin.x + leftMargin + avatarSize + leftMargin;
labelRect.origin.y = labelRect.origin.y + topMargin - 1;
labelRect.size.width = contentRect.size.width - leftMargin - avatarSize - leftMargin - rightMargin;
labelRect.size.height = contentRect.size.height - topMargin - bottomMargin;
self.activityLabel.frame = labelRect;
[self.activityLabel sizeToFit];
labelFrame.origin.x = leftMargin*2 + avatarSize;
labelFrame.origin.y = topMargin - 1;
labelFrame.size.width = contentRect.size.width - leftMargin - avatarSize - leftMargin - rightMargin - 20;
labelFrame.size.height = contentRect.size.height - topMargin - bottomMargin;
self.activityLabel.frame = labelFrame;
}
- (int)setActivity:(NSDictionary *)activity withUserProfile:(NSDictionary *)userProfile withWidth:(int)width {
// must set the height again for dynamic height in heightForRowAtIndexPath in
CGRect activityLabelRect = self.activityLabel.bounds;
CGRect activityLabelRect = self.activityLabel.frame;
activityLabelRect.size.width = width - leftMargin - avatarSize - leftMargin - rightMargin;
self.activityLabel.frame = activityLabelRect;
@ -163,13 +165,12 @@
[attrStr addAttributes:@{NSForegroundColorAttributeName:UIColorFromRGB(0x666666)} range:[txtWithTime rangeOfString:comment]];
[attrStr addAttributes:@{NSForegroundColorAttributeName:UIColorFromRGB(0x999999)} range:[txtWithTime rangeOfString:time]];
[attrStr addAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"Helvetica" size:11]} range:[txtWithTime rangeOfString:time]];
NSMutableParagraphStyle* style= [NSMutableParagraphStyle new];
style.lineBreakMode = NSLineBreakByWordWrapping;
[attrStr addAttributes:@{NSParagraphStyleAttributeName: style} range:NSMakeRange(0, [txtWithTime length])];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
[attrStr addAttributes:@{NSParagraphStyleAttributeName: paragraphStyle} range:NSMakeRange(0, [txtWithTime length])];
NSRange commentRange = [txtWithTime rangeOfString:comment];
if (commentRange.location != NSNotFound) {
NSLog(@"Spacing: %@", comment);
commentRange.location -= 2;
commentRange.length = 1;
if ([[txtWithTime substringWithRange:commentRange] isEqualToString:@" "]) {

View file

@ -167,12 +167,12 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int activitesCount = [appDelegate.userActivitiesArray count];
NSInteger activitesCount = [appDelegate.userActivitiesArray count];
return activitesCount + 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int activitiesCount = [appDelegate.userActivitiesArray count];
NSInteger activitiesCount = [appDelegate.userActivitiesArray count];
int minimumHeight;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
minimumHeight = MINIMUM_ACTIVITY_HEIGHT_IPAD;
@ -193,9 +193,9 @@
NSMutableDictionary *userProfile = [appDelegate.dictSocialProfile mutableCopy];
[userProfile setValue:@"You" forKey:@"username"];
int height = [activityCell setActivity:[appDelegate.userActivitiesArray
objectAtIndex:(indexPath.row)]
NSDictionary *activity = [appDelegate.userActivitiesArray
objectAtIndex:(indexPath.row)];
int height = [activityCell setActivity:activity
withUserProfile:userProfile
withWidth:self.frame.size.width - 20];
return height;
@ -220,13 +220,17 @@
// add in loading cell
return [self makeLoadingCell];
} else {
NSMutableDictionary *userProfile = [appDelegate.dictSocialProfile mutableCopy];
[userProfile setValue:@"You" forKey:@"username"];
NSDictionary *activitiy = [appDelegate.userActivitiesArray
NSDictionary *activity = [appDelegate.userActivitiesArray
objectAtIndex:(indexPath.row)];
NSString *category = [activitiy objectForKey:@"category"];
[cell setActivity:activity
withUserProfile:userProfile
withWidth:self.frame.size.width - 20];
NSString *category = [activity objectForKey:@"category"];
if ([category isEqualToString:@"follow"]) {
cell.accessoryType = UITableViewCellAccessoryNone;
} else if ([category isEqualToString:@"signup"]){
@ -239,17 +243,13 @@
UIView *myBackView = [[UIView alloc] initWithFrame:self.frame];
myBackView.backgroundColor = UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR);
cell.selectedBackgroundView = myBackView;
// update the cell information
[cell setActivity: activitiy
withUserProfile:userProfile
withWidth:self.frame.size.width - 20];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int activitiesCount = [appDelegate.userActivitiesArray count];
NSInteger activitiesCount = [appDelegate.userActivitiesArray count];
if (indexPath.row < activitiesCount) {
NSDictionary *activity = [appDelegate.userActivitiesArray objectAtIndex:indexPath.row];
NSString *category = [activity objectForKey:@"category"];

View file

@ -64,17 +64,18 @@ static UIFont *indicatorFont = nil;
if (highlighted) {
textColor = UIColorFromRGB(0x686868); //0x686868
}
[textColor set];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
paragraphStyle.alignment = NSTextAlignmentLeft;
[self.siteTitle
[self.siteTitle
drawInRect:CGRectMake(leftMargin, 6, rect.size.width - rightMargin, 21)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
textColor = UIColorFromRGB(0x333333);
font = [UIFont fontWithName:@"Helvetica-Bold" size:12];
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
textColor = UIColorFromRGB(0x333333);
if (highlighted) {
textColor = UIColorFromRGB(0x686868);
}
@ -91,13 +92,13 @@ static UIFont *indicatorFont = nil;
if (highlighted) {
textColor = UIColorFromRGB(0x686868);
}
[textColor set];
paragraphStyle.alignment = NSTextAlignmentRight;
[self.siteSubscribers
drawInRect:CGRectMake(leftMargin + (rect.size.width - rightMargin) / 2 - 10, 42 + adjustForSocial, (rect.size.width - rightMargin) / 2 + 10, 15.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentRight];
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
// feed bar
CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor]));

View file

@ -53,18 +53,24 @@
}
- (void)viewDidLoad {
UIImageView *folderImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"g_icn_folder.png"]];
folderImage.frame = CGRectMake(0, 0, 16, 16);
UIImageView *folderImage = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"g_icn_folder.png"]];
folderImage.frame = CGRectMake(0, 0, 24, 16);
[folderImage setContentMode:UIViewContentModeRight];
[inFolderInput setLeftView:folderImage];
[inFolderInput setLeftViewMode:UITextFieldViewModeAlways];
UIImageView *folderImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"g_icn_folder_rss.png"]];
folderImage2.frame = CGRectMake(0, 0, 16, 16);
UIImageView *folderImage2 = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"g_icn_folder_rss.png"]];
folderImage2.frame = CGRectMake(0, 0, 24, 16);
[folderImage2 setContentMode:UIViewContentModeRight];
[addFolderInput setLeftView:folderImage2];
[addFolderInput setLeftViewMode:UITextFieldViewModeAlways];
UIImageView *urlImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"world.png"]];
urlImage.frame = CGRectMake(0, 0, 16, 16);
UIImageView *urlImage = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"world.png"]];
urlImage.frame = CGRectMake(0, 0, 24, 16);
[urlImage setContentMode:UIViewContentModeRight];
[siteAddressInput setLeftView:urlImage];
[siteAddressInput setLeftViewMode:UITextFieldViewModeAlways];
@ -185,7 +191,7 @@
return;
}
int periodLoc = [phrase rangeOfString:@"."].location;
NSInteger periodLoc = [phrase rangeOfString:@"."].location;
if (periodLoc != NSNotFound && siteAddressInput.returnKeyType != UIReturnKeyDone) {
// URL
[siteAddressInput setReturnKeyType:UIReturnKeyDone];
@ -302,11 +308,11 @@
- (NSString *)extractParentFolder {
NSString *parent_folder = [inFolderInput text];
int folder_loc = [parent_folder rangeOfString:@" - " options:NSBackwardsSearch].location;
NSInteger folder_loc = [parent_folder rangeOfString:@" - " options:NSBackwardsSearch].location;
if ([parent_folder length] && folder_loc != NSNotFound) {
parent_folder = [parent_folder substringFromIndex:(folder_loc + 3)];
}
int top_level_loc = [parent_folder rangeOfString:@" Top Level " options:NSBackwardsSearch].location;
NSInteger top_level_loc = [parent_folder rangeOfString:@" Top Level " options:NSBackwardsSearch].location;
if (parent_folder.length && top_level_loc != NSNotFound) {
parent_folder = @"";
}
@ -425,7 +431,7 @@ numberOfRowsInComponent:(NSInteger)component {
#pragma mark Autocomplete sites
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [autocompleteResults count];
}

View file

@ -66,7 +66,7 @@
action: @selector(doCancelButton)];
self.navigationItem.leftBarButtonItem = cancelButton;
self.view.frame = CGRectMake(0, 0, 320, 416);
self.contentSizeForViewInPopover = self.view.frame.size;
self.preferredContentSize = self.view.frame.size;
}
}

View file

@ -20,3 +20,4 @@
- (void)informLoadingMessage:(NSString *)message;
@end

View file

@ -19,7 +19,7 @@
ActivityModule *activitiesModule;
UIWebView *feedbackWebView;
UIToolbar *toolbar;
UIToolbar *topToolbar;
UINavigationBar *topToolbar;
UISegmentedControl *segmentedButton;
}
@ -28,7 +28,7 @@
@property (nonatomic) IBOutlet ActivityModule *activitiesModule;
@property (nonatomic) IBOutlet UIWebView *feedbackWebView;
@property (nonatomic) IBOutlet UIToolbar *topToolbar;
@property (nonatomic) IBOutlet UINavigationBar *topToolbar;
@property (nonatomic) IBOutlet UIToolbar *toolbar;
@property (nonatomic) IBOutlet UISegmentedControl *segmentedButton;

View file

@ -42,11 +42,14 @@
self.feedbackWebView.delegate = self;
self.segmentedButton.selectedSegmentIndex = 0;
self.topToolbar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9];
// preload feedback
self.feedbackWebView.scalesPageToFit = YES;
[self.segmentedButton
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
NSString *urlAddress = FEEDBACK_URL;
//Create a URL object.
NSURL *url = [NSURL URLWithString:urlAddress];
@ -54,7 +57,10 @@
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
//Load the request in the UIWebView.
[self.feedbackWebView loadRequest:requestObj];
CGRect topToolbarFrame = self.topToolbar.frame;
topToolbarFrame.size.height += 20;
self.topToolbar.frame = topToolbarFrame;
}
- (void)viewDidUnload {
@ -86,7 +92,7 @@
# pragma mark Navigation
- (IBAction)tapSegmentedButton:(id)sender {
int selectedSegmentIndex = [self.segmentedButton selectedSegmentIndex];
NSInteger selectedSegmentIndex = [self.segmentedButton selectedSegmentIndex];
if (selectedSegmentIndex == 0) {
self.interactionsModule.hidden = NO;

View file

@ -51,11 +51,23 @@
- (void)viewWillAppear:(BOOL)animated {
[self.menuTableView reloadData];
[orderSegmentedControl
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
[orderSegmentedControl setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:0];
[orderSegmentedControl setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:1];
[orderSegmentedControl setSelectedSegmentIndex:0];
if ([appDelegate.activeOrder isEqualToString:@"oldest"]) {
[orderSegmentedControl setSelectedSegmentIndex:1];
}
[readFilterSegmentedControl
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
[readFilterSegmentedControl setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:0];
[readFilterSegmentedControl setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:1];
[readFilterSegmentedControl setSelectedSegmentIndex:0];
if ([appDelegate.activeReadFilter isEqualToString:@"unread"]) {
[readFilterSegmentedControl setSelectedSegmentIndex:1];
@ -147,7 +159,7 @@
return cell;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return kMenuOptionHeight;
}
@ -185,6 +197,7 @@
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.frame = CGRectMake(0, 0, 240, kMenuOptionHeight);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.separatorInset = UIEdgeInsetsZero;
orderSegmentedControl.frame = CGRectMake(8, 7, cell.frame.size.width - 8*2,
kMenuOptionHeight - 7*2);
@ -200,6 +213,8 @@
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.frame = CGRectMake(0, 0, 240, kMenuOptionHeight);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.separatorInset = UIEdgeInsetsZero;
readFilterSegmentedControl.frame = CGRectMake(8, 7, cell.frame.size.width - 8*2,
kMenuOptionHeight - 7*2);
[readFilterSegmentedControl setTitle:[@"All stories" uppercaseString] forSegmentAtIndex:0];

View file

@ -8,15 +8,16 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "ABTableViewCell.h"
#import "NBSwipeableCell.h"
@interface FeedDetailTableCell : ABTableViewCell {
@interface FeedDetailTableCell : NBSwipeableCell {
NewsBlurAppDelegate *appDelegate;
// All views
NSString *storyTitle;
NSString *storyAuthor;
NSString *storyDate;
NSInteger storyTimestamp;
int storyScore;
BOOL isStarred;
BOOL isShared;
@ -31,18 +32,20 @@
UIColor *feedColorBar;
UIColor *feedColorBarTopBorder;
UIView *cellContent;
}
@property (nonatomic) NSString *siteTitle;
@property (nonatomic) UIImage *siteFavicon;
@property (readwrite) int storyScore;
@property (readwrite) BOOL isStarred;
@property (nonatomic, readwrite) BOOL isStarred;
@property (readwrite) BOOL isShared;
@property (nonatomic) NSString *storyTitle;
@property (nonatomic) NSString *storyAuthor;
@property (nonatomic) NSString *storyDate;
@property (nonatomic) NSInteger storyTimestamp;
@property (nonatomic) UIColor *feedColorBar;
@property (nonatomic) UIColor *feedColorBarTopBorder;
@ -52,6 +55,12 @@
@property (readwrite) BOOL isRiverOrSocial;
@property (readwrite) BOOL hasAlpha;
- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha;
- (void)setupGestures;
@end
@interface FeedDetailTableCellView : UIView
@property (nonatomic) FeedDetailTableCell *cell;
@end

View file

@ -11,22 +11,25 @@
#import "ABTableViewCell.h"
#import "UIView+TKCategory.h"
#import "Utilities.h"
#import "MCSwipeTableViewCell.h"
static UIFont *textFont = nil;
static UIFont *indicatorFont = nil;
@class FeedDetailViewController;
@implementation FeedDetailTableCell
@synthesize storyTitle;
@synthesize storyAuthor;
@synthesize storyDate;
@synthesize storyTimestamp;
@synthesize storyScore;
@synthesize siteTitle;
@synthesize siteFavicon;
@synthesize isRead;
@synthesize isStarred;
@synthesize isShared;
@synthesize isStarred;
@synthesize isShort;
@synthesize isRiverOrSocial;
@synthesize feedColorBar;
@ -45,35 +48,82 @@ static UIFont *indicatorFont = nil;
}
}
- (void)drawContentView:(CGRect)r highlighted:(BOOL)highlighted {
int adjustForSocial = 3;
if (self.isRiverOrSocial) {
adjustForSocial = 20;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
cellContent = [[FeedDetailTableCellView alloc] initWithFrame:self.frame];
cellContent.opaque = YES;
[self.contentView addSubview:cellContent];
}
return self;
}
- (void)drawRect:(CGRect)rect {
((FeedDetailTableCellView *)cellContent).cell = self;
cellContent.frame = rect;
[cellContent setNeedsDisplay];
}
- (void)setupGestures {
NSString *unreadIcon;
if (storyScore == -1) {
unreadIcon = @"g_icn_hidden.png";
} else if (storyScore == 1) {
unreadIcon = @"g_icn_focus.png";
} else {
unreadIcon = @"g_icn_unread.png";
}
UIColor *shareColor = self.isStarred ?
UIColorFromRGB(0xF69E89) :
UIColorFromRGB(0xA4D97B);
UIColor *readColor = self.isRead ?
UIColorFromRGB(0xBED49F) :
UIColorFromRGB(0xFFFFD2);
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
[self setDelegate:(FeedDetailViewController <MCSwipeTableViewCellDelegate> *)appDelegate.feedDetailViewController];
[self setFirstStateIconName:@"clock.png"
firstColor:shareColor
secondStateIconName:nil
secondColor:nil
thirdIconName:unreadIcon
thirdColor:readColor
fourthIconName:nil
fourthColor:nil];
self.mode = MCSwipeTableViewCellModeSwitch;
self.shouldAnimatesIcons = NO;
}
@end
@implementation FeedDetailTableCellView
@synthesize cell;
- (void)drawRect:(CGRect)r {
int adjustForSocial = 3;
if (cell.isRiverOrSocial) {
adjustForSocial = 20;
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectInset(r, 12, 12);
rect.size.width -= 18; // Scrollbar padding
if (!highlighted) {
UIColor *backgroundColor;
backgroundColor = UIColorFromRGB(0xf4f4f4);
[backgroundColor set];
}
UIColor *backgroundColor;
backgroundColor = cell.highlighted || cell.selected ?
UIColorFromRGB(0xFFFDEF) : UIColorFromRGB(0xf4f4f4);
[backgroundColor set];
CGContextFillRect(context, r);
if (highlighted) {
[NewsBlurAppDelegate fillGradient:r startColor:UIColorFromRGB(0xFFFDEF) endColor:UIColorFromRGB(0xFFFDDF)];
}
UIColor *textColor;
UIFont *font;
if (self.isRead) {
if (cell.isRead) {
font = [UIFont fontWithName:@"Helvetica" size:11];
textColor = UIColorFromRGB(0x808080);
} else {
@ -81,19 +131,21 @@ static UIFont *indicatorFont = nil;
textColor = UIColorFromRGB(0x606060);
}
if (highlighted) {
textColor = UIColorFromRGB(0x686868);
if (cell.highlighted || cell.selected) {
textColor = UIColorFromRGB(0x686868);
}
[textColor set];
if (self.isRiverOrSocial) {
[self.siteTitle
drawInRect:CGRectMake(leftMargin + 20, 7, rect.size.width - 20, 21)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = NSTextAlignmentLeft;
if (cell.isRiverOrSocial) {
[cell.siteTitle drawInRect:CGRectMake(leftMargin + 20, 7, rect.size.width - 20, 21)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
if (self.isRead) {
if (cell.isRead) {
font = [UIFont fontWithName:@"Helvetica" size:12];
textColor = UIColorFromRGB(0x606060);
@ -101,63 +153,65 @@ static UIFont *indicatorFont = nil;
textColor = UIColorFromRGB(0x333333);
font = [UIFont fontWithName:@"Helvetica-Bold" size:12];
}
if (highlighted) {
if (cell.highlighted || cell.selected) {
textColor = UIColorFromRGB(0x686868);
}
[textColor set];
}
// story title
CGSize theSize = [self.storyTitle sizeWithFont:font constrainedToSize:CGSizeMake(rect.size.width, 30.0) lineBreakMode:NSLineBreakByTruncatingTail];
// story title
CGSize theSize = [cell.storyTitle
boundingRectWithSize:CGSizeMake(rect.size.width, cell.isShort ? 15.0 : 30.0)
options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle}
context:nil].size;
int storyTitleY = 7 + adjustForSocial + ((30 - theSize.height)/2);
if (self.isShort) {
if (cell.isShort) {
storyTitleY = 7 + adjustForSocial + 2;
}
int storyTitleX = leftMargin;
if (self.isStarred) {
if (cell.isStarred) {
UIImage *savedIcon = [UIImage imageNamed:@"clock"];
[savedIcon drawInRect:CGRectMake(storyTitleX, storyTitleY - 1, 16, 16) blendMode:nil alpha:1];
storyTitleX += 20;
}
if (self.isShared) {
if (cell.isShared) {
UIImage *savedIcon = [UIImage imageNamed:@"menu_icn_share"];
[savedIcon drawInRect:CGRectMake(storyTitleX, storyTitleY - 1, 16, 16) blendMode:nil alpha:1];
storyTitleX += 20;
}
[self.storyTitle
drawInRect:CGRectMake(storyTitleX, storyTitleY, rect.size.width - storyTitleX + leftMargin, theSize.height)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
[cell.storyTitle drawWithRect:CGRectMake(storyTitleX, storyTitleY, rect.size.width - storyTitleX + leftMargin, theSize.height)
options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}
context:nil];
int storyAuthorDateY = 41 + adjustForSocial;
if (self.isShort) {
if (cell.isShort) {
storyAuthorDateY -= 13;
}
// story author style
if (self.isRead) {
textColor = UIColorFromRGB(0x808080);
if (cell.isRead) {
textColor = UIColorFromRGB(0x959595);
font = [UIFont fontWithName:@"Helvetica" size:10];
} else {
textColor = UIColorFromRGB(0x959595);
textColor = UIColorFromRGB(0xA6A8A2);
font = [UIFont fontWithName:@"Helvetica-Bold" size:10];
}
if (highlighted) {
textColor = UIColorFromRGB(0x686868);
if (cell.highlighted || cell.selected) {
textColor = UIColorFromRGB(0x959595);
}
[textColor set];
[self.storyAuthor
[cell.storyAuthor
drawInRect:CGRectMake(leftMargin, storyAuthorDateY, (rect.size.width) / 2 - 10, 15.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
// story date
if (self.isRead) {
if (cell.isRead) {
textColor = UIColorFromRGB(0xbabdd1);
font = [UIFont fontWithName:@"Helvetica" size:10];
} else {
@ -165,118 +219,108 @@ static UIFont *indicatorFont = nil;
font = [UIFont fontWithName:@"Helvetica-Bold" size:10];
}
if (highlighted) {
textColor = UIColorFromRGB(0x686868);
if (cell.highlighted || cell.selected) {
if (cell.isRead) {
textColor = UIColorFromRGB(0xaaadc1);
} else {
textColor = UIColorFromRGB(0x5a5d91);
}
}
[textColor set];
[self.storyDate
drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentRight];
paragraphStyle.alignment = NSTextAlignmentRight;
NSString *date = [Utilities formatShortDateFromTimestamp:cell.storyTimestamp];
[date
drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
// feed bar
CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBarTopBorder CGColor]));
if (self.isRead) {
CGContextSetStrokeColor(context, CGColorGetComponents([cell.feedColorBarTopBorder CGColor]));
if (cell.isRead) {
CGContextSetAlpha(context, 0.15);
}
CGContextSetLineWidth(context, 4.0f);
CGContextBeginPath(context);
CGContextMoveToPoint(context, 2.0f, 1.0f);
CGContextAddLineToPoint(context, 2.0f, self.frame.size.height - 1);
CGContextMoveToPoint(context, 2.0f, 0);
CGContextAddLineToPoint(context, 2.0f, cell.frame.size.height);
CGContextStrokePath(context);
CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor]));
CGContextSetStrokeColor(context, CGColorGetComponents([cell.feedColorBar CGColor]));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 6.0f, 1.0f);
CGContextAddLineToPoint(context, 6.0, self.frame.size.height - 1);
CGContextMoveToPoint(context, 6.0f, 0);
CGContextAddLineToPoint(context, 6.0, cell.frame.size.height);
CGContextStrokePath(context);
// reset for borders
UIColor *white = UIColorFromRGB(0xffffff);
CGContextSetAlpha(context, 1.0);
CGContextSetLineWidth(context, 1.0f);
if (highlighted) {
if (cell.highlighted || cell.selected) {
// top border
UIColor *blue = UIColorFromRGB(0xF9F8F4);
CGContextSetStrokeColor(context, CGColorGetComponents([white CGColor]));
CGContextSetLineWidth(context, 1.0f);
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0.5f);
CGContextAddLineToPoint(context, cell.bounds.size.width, 0.5f);
CGContextStrokePath(context);
CGFloat lineWidth = 0.5f;
CGContextSetLineWidth(context, lineWidth);
UIColor *blue = UIColorFromRGB(0xDFDDCF);
CGContextSetStrokeColor(context, CGColorGetComponents([blue CGColor]));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0.5f);
CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f);
CGContextMoveToPoint(context, 0, 1.0f + 0.5f*lineWidth);
CGContextAddLineToPoint(context, cell.bounds.size.width, 1.0f + 0.5f*lineWidth);
CGContextStrokePath(context);
// bottom border
// bottom border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, self.bounds.size.height - 1.5f);
CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height - 1.5f);
CGContextMoveToPoint(context, 0, cell.bounds.size.height - .5f*lineWidth);
CGContextAddLineToPoint(context, cell.bounds.size.width, cell.bounds.size.height - .5f*lineWidth);
CGContextStrokePath(context);
} else {
// top border
UIColor *white = UIColorFromRGB(0xffffff);
CGContextSetLineWidth(context, 1.0f);
CGContextSetStrokeColor(context, CGColorGetComponents([white CGColor]));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0.0f, 0.5f);
CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f);
CGContextAddLineToPoint(context, cell.bounds.size.width, 0.5f);
CGContextStrokePath(context);
}
// site favicon
if (self.isRead && !self.hasAlpha) {
if (self.isRiverOrSocial) {
self.siteFavicon = [self imageByApplyingAlpha:self.siteFavicon withAlpha:0.25];
if (cell.isRead && !cell.hasAlpha) {
if (cell.isRiverOrSocial) {
cell.siteFavicon = [cell imageByApplyingAlpha:cell.siteFavicon withAlpha:0.25];
}
self.hasAlpha = YES;
cell.hasAlpha = YES;
}
if (self.isRiverOrSocial) {
[self.siteFavicon drawInRect:CGRectMake(leftMargin, 6.0, 16.0, 16.0)];
if (cell.isRiverOrSocial) {
[cell.siteFavicon drawInRect:CGRectMake(leftMargin, 6.0, 16.0, 16.0)];
}
// story indicator
// story indicator
int storyIndicatorY = 4 + adjustForSocial;
if (self.isShort){
if (cell.isShort){
storyIndicatorY = 4 + adjustForSocial - 5 ;
}
UIImage *unreadIcon;
if (storyScore == -1) {
if (cell.storyScore == -1) {
unreadIcon = [UIImage imageNamed:@"g_icn_hidden"];
} else if (storyScore == 1) {
} else if (cell.storyScore == 1) {
unreadIcon = [UIImage imageNamed:@"g_icn_focus"];
} else {
unreadIcon = [UIImage imageNamed:@"g_icn_unread"];
}
[unreadIcon drawInRect:CGRectMake(15, storyIndicatorY + 14, 8, 8) blendMode:nil alpha:(self.isRead ? .15 : 1)];
[unreadIcon drawInRect:CGRectMake(15, storyIndicatorY + 14, 8, 8) blendMode:nil alpha:(cell.isRead ? .15 : 1)];
}
- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -area.size.height);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
CGContextSetAlpha(ctx, alpha);
CGContextDrawImage(ctx, area, image.CGImage);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end
@end

View file

@ -12,17 +12,19 @@
#import "BaseViewController.h"
#import "Utilities.h"
#import "WEPopoverController.h"
#import "TransparentToolbar.h"
#import "NBNotifier.h"
#import "MCSwipeTableViewCell.h"
@class NewsBlurAppDelegate;
@class FeedDetailTableCell;
@class MCSwipeTableViewCell;
@interface FeedDetailViewController : BaseViewController
<UITableViewDelegate, UITableViewDataSource,
UIActionSheetDelegate, UIAlertViewDelegate,
UIPopoverControllerDelegate, ASIHTTPRequestDelegate,
WEPopoverControllerDelegate> {
WEPopoverControllerDelegate, MCSwipeTableViewCellDelegate,
UIGestureRecognizerDelegate> {
NewsBlurAppDelegate *appDelegate;
int feedPage;
@ -45,10 +47,8 @@
@property (nonatomic) IBOutlet UIBarButtonItem * settingsBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * spacerBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * spacer2BarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * spacer3BarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * separatorBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * titleImageBarButton;
@property (nonatomic) IBOutlet TransparentToolbar * rightToolbar;
@property (nonatomic, retain) WEPopoverController *popoverController;
@property (nonatomic, retain) NBNotifier *notifier;
@ -76,6 +76,7 @@
- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation;
- (void)fadeSelectedCell;
- (void)loadStory:(FeedDetailTableCell *)cell atRow:(NSInteger)row;
- (void)redrawUnreadStory;
- (IBAction)doOpenMarkReadActionSheet:(id)sender;
- (IBAction)doOpenSettingsActionSheet:(id)sender;

View file

@ -24,11 +24,12 @@
#import "UIBarButtonItem+WEPopover.h"
#import "WEPopoverController.h"
#import "UIBarButtonItem+Image.h"
#import "TransparentToolbar.h"
#import "FeedDetailMenuViewController.h"
#import "NBNotifier.h"
#import "NBLoadingCell.h"
#import "FMDatabase.h"
#import "NBBarButtonItem.h"
#import "UIActivitiesControl.h"
#define kTableViewRowHeight 61;
#define kTableViewRiverRowHeight 81;
@ -49,8 +50,7 @@
@synthesize settingsBarButton;
@synthesize separatorBarButton;
@synthesize titleImageBarButton;
@synthesize spacerBarButton, spacer2BarButton, spacer3BarButton;
@synthesize rightToolbar;
@synthesize spacerBarButton, spacer2BarButton;
@synthesize appDelegate;
@synthesize feedPage;
@synthesize pageFetching;
@ -75,18 +75,12 @@
self.storyTitlesTable.backgroundColor = UIColorFromRGB(0xf4f4f4);
self.storyTitlesTable.separatorColor = UIColorFromRGB(0xE9E8E4);
rightToolbar = [[TransparentToolbar alloc]
initWithFrame:CGRectMake(0, 0, 76, 44)];
spacerBarButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacerBarButton.width = -12;
spacerBarButton.width = 0;
spacer2BarButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacer2BarButton.width = -10;
spacer3BarButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacer3BarButton.width = -10;
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacer2BarButton.width = 0;
UIImage *separatorImage = [UIImage imageNamed:@"bar-separator.png"];
separatorBarButton = [UIBarButtonItem barItemWithImage:separatorImage target:nil action:nil];
@ -94,12 +88,18 @@
UIImage *settingsImage = [UIImage imageNamed:@"nav_icn_settings.png"];
settingsBarButton = [UIBarButtonItem barItemWithImage:settingsImage target:self action:@selector(doOpenSettingsActionSheet:)];
UIImage *markreadImage = [UIImage imageNamed:@"markread.png"];
feedMarkReadButton = [UIBarButtonItem barItemWithImage:markreadImage target:self action:@selector(doOpenMarkReadActionSheet:)];
titleImageBarButton = [UIBarButtonItem alloc];
UILongPressGestureRecognizer *longpress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleLongPress:)];
longpress.minimumPressDuration = 1.0;
longpress.delegate = self;
[self.storyTitlesTable addGestureRecognizer:longpress];
self.notifier = [[NBNotifier alloc] initWithTitle:@"Fetching stories..." inView:self.view];
[self.view addSubview:self.notifier];
}
@ -122,39 +122,49 @@
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
[self setUserAvatarLayout:orientation];
self.finishedAnimatingIn = NO;
[MBProgressHUD hideHUDForView:self.view animated:YES];
// set center title
UILabel *titleLabel = (UILabel *)[appDelegate makeFeedTitle:appDelegate.activeFeed];
self.navigationItem.titleView = titleLabel;
[MBProgressHUD hideHUDForView:self.view animated:NO];
// set right avatar title image
spacerBarButton.width = 0;
spacer2BarButton.width = 0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
spacerBarButton.width = -6;
spacer2BarButton.width = 10;
}
if (appDelegate.isSocialView) {
UIButton *titleImageButton = [appDelegate makeRightFeedTitle:appDelegate.activeFeed];
[titleImageButton addTarget:self action:@selector(showUserProfile) forControlEvents:UIControlEventTouchUpInside];
titleImageBarButton.customView = titleImageButton;
[rightToolbar setItems: [NSArray arrayWithObjects:
spacerBarButton,
feedMarkReadButton,
spacer2BarButton,
separatorBarButton,
spacer3BarButton,
titleImageBarButton, nil]];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightToolbar];
titleImageBarButton.enabled = YES;
spacerBarButton.width = -6;
NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed objectForKey:@"id"]];
UIImage *titleImage = [Utilities getImage:feedIdStr isSocial:YES];
titleImage = [Utilities roundCorneredImage:titleImage radius:6];
[((UIButton *)titleImageBarButton.customView).imageView removeFromSuperview];
titleImageBarButton = [UIBarButtonItem barItemWithImage:titleImage
target:self
action:@selector(showUserProfile)];
titleImageBarButton.customView.frame = CGRectMake(0, 0, 32, 32);
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:
spacerBarButton,
titleImageBarButton,
spacer2BarButton,
separatorBarButton,
feedMarkReadButton, nil];
} else {
[rightToolbar setItems: [NSArray arrayWithObjects:
spacerBarButton,
feedMarkReadButton,
spacer2BarButton,
separatorBarButton,
spacer3BarButton,
settingsBarButton, nil]];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightToolbar];
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:
spacerBarButton,
settingsBarButton,
spacer2BarButton,
separatorBarButton,
feedMarkReadButton,
nil];
}
// set center title
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone &&
!self.navigationItem.titleView) {
self.navigationItem.titleView = [appDelegate makeFeedTitle:appDelegate.activeFeed];
}
NSMutableArray *indexPaths = [NSMutableArray array];
NSLog(@"appDelegate.recentlyReadStoryLocations: %d - %@", self.isOffline, appDelegate.recentlyReadStoryLocations);
// NSLog(@"appDelegate.recentlyReadStoryLocations: %d - %@", self.isOffline, appDelegate.recentlyReadStoryLocations);
for (id i in appDelegate.recentlyReadStoryLocations) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[i intValue]
inSection:0];
@ -164,17 +174,15 @@
}
}
if ([indexPaths count] > 0 && [self.storyTitlesTable numberOfRowsInSection:0]) {
[self.storyTitlesTable beginUpdates];
[self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.storyTitlesTable endUpdates];
//[self.storyTitlesTable reloadData];
// [self.storyTitlesTable beginUpdates];
// [self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
// [self.storyTitlesTable endUpdates];
[self.storyTitlesTable reloadData];
}
appDelegate.recentlyReadStoryLocations = [NSMutableArray array];
appDelegate.originalStoryCount = [appDelegate unreadCount];
[super viewWillAppear:animated];
if ((appDelegate.isSocialRiverView ||
appDelegate.isSocialView ||
[appDelegate.activeFolder isEqualToString:@"saved_stories"])) {
@ -192,7 +200,7 @@
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[self.storyTitlesTable reloadData];
int location = appDelegate.locationOfActiveStory;
NSInteger location = appDelegate.locationOfActiveStory;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:location inSection:0];
if (indexPath && location >= 0) {
[self.storyTitlesTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
@ -201,6 +209,7 @@
}
[self.notifier setNeedsLayout];
[appDelegate hideShareView:YES];
}
- (void)viewDidAppear:(BOOL)animated {
@ -215,19 +224,14 @@
[self testForTryFeed];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
- (void)viewDidDisappear:(BOOL)animated {
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
- (void)viewDidDisappear:(BOOL)animated {
}
- (void)fadeSelectedCell {
// have the selected cell deselect
int location = appDelegate.locationOfActiveStory;
NSInteger location = appDelegate.locationOfActiveStory;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:location inSection:0];
if (indexPath) {
@ -240,12 +244,12 @@
- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && appDelegate.isSocialView) {
if (UIInterfaceOrientationIsPortrait(orientation)) {
UIButton *avatar = (UIButton *)titleImageBarButton.customView;
NBBarButtonItem *avatar = (NBBarButtonItem *)titleImageBarButton.customView;
CGRect buttonFrame = avatar.frame;
buttonFrame.size = CGSizeMake(32, 32);
avatar.frame = buttonFrame;
} else {
UIButton *avatar = (UIButton *)titleImageBarButton.customView;
NBBarButtonItem *avatar = (NBBarButtonItem *)titleImageBarButton.customView;
CGRect buttonFrame = avatar.frame;
buttonFrame.size = CGSizeMake(28, 28);
avatar.frame = buttonFrame;
@ -253,12 +257,12 @@
}
}
#pragma mark -
#pragma mark Initialization
- (void)resetFeedDetail {
appDelegate.hasLoadedFeedDetail = NO;
self.navigationItem.titleView = nil;
self.pageFetching = NO;
self.pageFinished = NO;
self.isOffline = NO;
@ -294,7 +298,8 @@
- (void)beginOfflineTimer {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (!appDelegate.storyLocationsCount && self.feedPage == 1) {
if (!appDelegate.storyLocationsCount && !self.pageFinished &&
self.feedPage == 1 && !self.isOffline) {
self.isShowingOffline = YES;
self.isOffline = YES;
[self showLoadingNotifier];
@ -323,7 +328,7 @@
self.feedPage = page;
self.pageFetching = YES;
int storyCount = appDelegate.storyCount;
NSInteger storyCount = appDelegate.storyCount;
if (storyCount == 0) {
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
@ -374,13 +379,11 @@
if (request.isCancelled) {
NSLog(@"Cancelled");
return;
} else if (self.feedPage == 1) {
} else {
self.isOffline = YES;
self.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
} else {
[self informError:[request error]];
self.pageFinished = YES;
}
[self.storyTitlesTable reloadData];
}];
@ -403,8 +406,8 @@
(unsigned long)NULL), ^(void) {
[appDelegate.database inDatabase:^(FMDatabase *db) {
NSArray *feedIds;
int limit = 12;
int offset = (self.feedPage - 1) * limit;
NSInteger limit = 12;
NSInteger offset = (self.feedPage - 1) * limit;
if (appDelegate.isRiverView) {
feedIds = appDelegate.activeFolderFeeds;
@ -426,11 +429,11 @@
} else {
readFilterSql = @"";
}
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM stories s %@ WHERE s.story_feed_id IN (%@) ORDER BY s.story_timestamp %@ LIMIT %d OFFSET %d",
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM stories s %@ WHERE s.story_feed_id IN (%@) ORDER BY s.story_timestamp %@ LIMIT %ld OFFSET %ld",
readFilterSql,
[feedIds componentsJoinedByString:@","],
orderSql,
limit, offset];
(long)limit, (long)offset];
FMResultSet *cursor = [db executeQuery:sql];
NSMutableArray *offlineStories = [NSMutableArray array];
@ -441,6 +444,7 @@
dataUsingEncoding:NSUTF8StringEncoding]
options:nil error:nil]];
}
[cursor close];
if ([appDelegate.activeReadFilter isEqualToString:@"all"]) {
NSString *unreadHashSql = [NSString stringWithFormat:@"SELECT s.story_hash FROM stories s INNER JOIN unread_hashes uh ON s.story_hash = uh.story_hash WHERE s.story_feed_id IN (%@)",
@ -456,9 +460,10 @@
[unreadStoryHashes setObject:[NSNumber numberWithBool:YES] forKey:[unreadHashCursor objectForColumnName:@"story_hash"]];
}
appDelegate.unreadStoryHashes = unreadStoryHashes;
[unreadHashCursor close];
}
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isOffline) {
NSLog(@"Online before offline rendered. Tossing offline stories.");
return;
@ -498,7 +503,7 @@
if (!self.pageFetching && !self.pageFinished) {
self.feedPage = page;
self.pageFetching = YES;
int storyCount = appDelegate.storyCount;
NSInteger storyCount = appDelegate.storyCount;
if (storyCount == 0) {
[self.storyTitlesTable reloadData];
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
@ -565,15 +570,12 @@
if (request.isCancelled) {
NSLog(@"Cancelled");
return;
} else if (self.feedPage == 1) {
} else {
self.isOffline = YES;
self.isShowingOffline = NO;
self.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
} else {
[self informError:[request error]];
self.pageFinished = YES;
[self.storyTitlesTable reloadData];
}
}];
[request setCompletionBlock:^(void) {
@ -595,12 +597,11 @@
NSLog(@"Cancelled");
return;
} else if ([request responseStatusCode] >= 500) {
if (self.feedPage == 1) {
self.isOffline = YES;
self.isShowingOffline = NO;
[self loadOfflineStories];
[self showOfflineNotifier];
}
self.isOffline = YES;
self.isShowingOffline = NO;
self.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
if ([request responseStatusCode] == 503) {
[self informError:@"In maintenance mode"];
self.pageFinished = YES;
@ -645,7 +646,7 @@
for (id key in [newClassifiers allKeys]) {
[appDelegate.activeClassifiers setObject:[newClassifiers objectForKey:key] forKey:key];
}
} else {
} else if (newClassifiers) {
[appDelegate.activeClassifiers setObject:newClassifiers forKey:feedIdStr];
}
appDelegate.activePopularAuthors = [results objectForKey:@"feed_authors"];
@ -773,12 +774,12 @@
if ([storyIdStr isEqualToString:appDelegate.tryFeedStoryId]) {
NSDictionary *feed = [appDelegate.activeFeedStories objectAtIndex:i];
int score = [NewsBlurAppDelegate computeStoryScore:[feed objectForKey:@"intelligence"]];
NSInteger score = [NewsBlurAppDelegate computeStoryScore:[feed objectForKey:@"intelligence"]];
if (score < appDelegate.selectedIntelligence) {
[self changeIntelligence:score];
}
int locationOfStoryId = [appDelegate locationOfStoryId:storyIdStr];
NSInteger locationOfStoryId = [appDelegate locationOfStoryId:storyIdStr];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:locationOfStoryId inSection:0];
[self.storyTitlesTable selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
@ -810,7 +811,7 @@
}
- (UITableViewCell *)makeLoadingCell {
int height = 40;
NSInteger height = 40;
UITableViewCell *cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:@"NoReuse"];
@ -830,6 +831,7 @@
fleuron.frame = CGRectMake(0, 0, self.view.frame.size.width, height);
fleuron.contentMode = UIViewContentModeCenter;
[cell.contentView addSubview:fleuron];
cell.backgroundColor = [UIColor clearColor];
return cell;
} else {//if ([appDelegate.storyLocationsCount]) {
NBLoadingCell *loadingCell = [[NBLoadingCell alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, height)];
@ -843,7 +845,7 @@
#pragma mark Table View - Feed List
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
int storyCount = appDelegate.storyLocationsCount;
NSInteger storyCount = appDelegate.storyLocationsCount;
// The +1 is for the finished/loading bar.
return storyCount + 1;
@ -855,23 +857,23 @@
NSString *cellIdentifier;
NSDictionary *feed ;
if (indexPath.row >= appDelegate.storyLocationsCount) {
return [self makeLoadingCell];
}
if (appDelegate.isRiverView || appDelegate.isSocialView) {
cellIdentifier = @"FeedRiverDetailCellIdentifier";
} else {
cellIdentifier = @"FeedDetailCellIdentifier";
}
FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView
dequeueReusableCellWithIdentifier:cellIdentifier];
FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView
dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[FeedDetailTableCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:nil];
}
if (indexPath.row >= appDelegate.storyLocationsCount) {
return [self makeLoadingCell];
}
NSDictionary *story = [self getStoryAtRow:indexPath.row];
id feedId = [story objectForKey:@"story_feed_id"];
@ -892,10 +894,11 @@
NSString *title = [story objectForKey:@"story_title"];
cell.storyTitle = [title stringByDecodingHTMLEntities];
cell.storyDate = [story objectForKey:@"short_parsed_date"];
cell.isStarred = [story objectForKey:@"starred"];
cell.isShared = [story objectForKey:@"shared"];
cell.storyTimestamp = [[story objectForKey:@"story_timestamp"] integerValue];
cell.isStarred = [[story objectForKey:@"starred"] boolValue];
cell.isShared = [[story objectForKey:@"shared"] boolValue];
if ([[story objectForKey:@"story_authors"] class] != [NSNull class]) {
cell.storyAuthor = [[story objectForKey:@"story_authors"] uppercaseString];
@ -929,19 +932,15 @@
// undread indicator
int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]];
NSInteger score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]];
cell.storyScore = score;
if (!appDelegate.hasLoadedFeedDetail) {
cell.isRead = ([appDelegate.activeReadFilter isEqualToString:@"all"] &&
![[appDelegate.unreadStoryHashes objectForKey:[story objectForKey:@"story_hash"]] boolValue]) ||
[[appDelegate.recentlyReadStories objectForKey:[story objectForKey:@"story_hash"]] boolValue];
cell.isRead = ![appDelegate isStoryUnread:story];
// if (!appDelegate.hasLoadedFeedDetail) {
// NSLog(@"Offline: %d (%d/%d) - %@ - %@", cell.isRead, ![[appDelegate.unreadStoryHashes objectForKey:[story objectForKey:@"story_hash"]] boolValue], [[appDelegate.recentlyReadStories objectForKey:[story objectForKey:@"story_hash"]] boolValue], [story objectForKey:@"story_title"], [story objectForKey:@"story_hash"]);
} else {
cell.isRead = [[story objectForKey:@"read_status"] intValue] == 1 ||
[[appDelegate.recentlyReadStories objectForKey:[story objectForKey:@"story_hash"]] boolValue];
// } else {
// NSLog(@"Online: %d (%d/%d) - %@ - %@", cell.isRead, [[story objectForKey:@"read_status"] intValue] == 1, [[appDelegate.recentlyReadStories objectForKey:[story objectForKey:@"story_hash"]] boolValue], [story objectForKey:@"story_title"], [story objectForKey:@"story_hash"]);
}
// }
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
@ -954,36 +953,40 @@
cell.isRiverOrSocial = YES;
}
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
int rowIndex = [appDelegate locationOfActiveStory];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
NSInteger rowIndex = [appDelegate locationOfActiveStory];
if (rowIndex == indexPath.row) {
[self.storyTitlesTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
[cell setupGestures];
return cell;
return cell;
}
- (void)loadStory:(FeedDetailTableCell *)cell atRow:(int)row {
cell.isRead = YES;
[cell setNeedsLayout];
int storyIndex = [appDelegate indexFromLocation:row];
- (void)loadStory:(FeedDetailTableCell *)cell atRow:(NSInteger)row {
NSInteger storyIndex = [appDelegate indexFromLocation:row];
appDelegate.activeStory = [[appDelegate activeFeedStories] objectAtIndex:storyIndex];
if ([appDelegate isStoryUnread:appDelegate.activeStory]) {
[self markStoryAsRead:appDelegate.activeStory];
}
[appDelegate loadStoryDetailView];
[self redrawUnreadStory];
}
- (void)redrawUnreadStory {
int rowIndex = [appDelegate locationOfActiveStory];
NSInteger rowIndex = [appDelegate locationOfActiveStory];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
FeedDetailTableCell *cell = (FeedDetailTableCell*) [self.storyTitlesTable cellForRowAtIndexPath:indexPath];
cell.isRead = [[appDelegate.activeStory objectForKey:@"read_status"] boolValue];
cell.isRead = ![appDelegate isStoryUnread:appDelegate.activeStory];
cell.isShared = [[appDelegate.activeStory objectForKey:@"shared"] boolValue];
cell.isStarred = [[appDelegate.activeStory objectForKey:@"starred"] boolValue];
[cell setNeedsDisplay];
}
- (void)changeActiveStoryTitleCellLayout {
int rowIndex = [appDelegate locationOfActiveStory];
NSInteger rowIndex = [appDelegate locationOfActiveStory];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
FeedDetailTableCell *cell = (FeedDetailTableCell*) [self.storyTitlesTable cellForRowAtIndexPath:indexPath];
cell.isRead = YES;
@ -1011,12 +1014,12 @@
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
int storyCount = appDelegate.storyLocationsCount;
NSInteger storyCount = appDelegate.storyLocationsCount;
if (storyCount && indexPath.row == storyCount) {
return 40;
} else if (appDelegate.isRiverView || appDelegate.isSocialView || appDelegate.isSocialRiverView) {
int height = kTableViewRiverRowHeight;
NSInteger height = kTableViewRiverRowHeight;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
&& !appDelegate.masterContainerViewController.storyTitlesOnLeft
&& UIInterfaceOrientationIsPortrait(orientation)) {
@ -1024,7 +1027,7 @@
}
return height;
} else {
int height = kTableViewRowHeight;
NSInteger height = kTableViewRowHeight;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
&& !appDelegate.masterContainerViewController.storyTitlesOnLeft
&& UIInterfaceOrientationIsPortrait(orientation)) {
@ -1033,7 +1036,10 @@
return height;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
// This will create a "invisible" footer
return 0.01f;
}
- (void)scrollViewDidScroll: (UIScrollView *)scroll {
[self checkScroll];
}
@ -1072,16 +1078,80 @@
}
- (NSDictionary *)getStoryAtRow:(NSInteger)indexPathRow {
int row = [[[appDelegate activeFeedStoryLocations] objectAtIndex:indexPathRow] intValue];
NSInteger row = [[[appDelegate activeFeedStoryLocations] objectAtIndex:indexPathRow] intValue];
return [appDelegate.activeFeedStories objectAtIndex:row];
}
#pragma mark - MCSwipeTableViewCellDelegate
// When the user starts swiping the cell this method is called
- (void)swipeTableViewCellDidStartSwiping:(MCSwipeTableViewCell *)cell {
// NSLog(@"Did start swiping the cell!");
}
// When the user is dragging, this method is called and return the dragged percentage from the border
- (void)swipeTableViewCell:(MCSwipeTableViewCell *)cell didSwipWithPercentage:(CGFloat)percentage {
// NSLog(@"Did swipe with percentage : %f", percentage);
}
- (void)swipeTableViewCell:(MCSwipeTableViewCell *)cell didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state mode:(MCSwipeTableViewCellMode)mode {
NSIndexPath *indexPath = [self.storyTitlesTable indexPathForCell:cell];
if (!indexPath) {
// This can happen if the user swipes on a cell that is being refreshed.
return;
}
NSInteger storyIndex = [appDelegate indexFromLocation:indexPath.row];
NSDictionary *story = [[appDelegate activeFeedStories] objectAtIndex:storyIndex];
if (state == MCSwipeTableViewCellState1) {
// Saved
if ([[story objectForKey:@"starred"] boolValue]) {
[self markStoryAsUnsaved:story];
} else {
[self markStoryAsSaved:story];
}
[self.storyTitlesTable reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
} else if (state == MCSwipeTableViewCellState3) {
// Read
if ([[story objectForKey:@"read_status"] boolValue]) {
[self markStoryAsUnread:story];
} else {
[self markStoryAsRead:story];
}
[self.storyTitlesTable reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
}
#pragma mark -
#pragma mark Feed Actions
- (void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.storyTitlesTable];
NSIndexPath *indexPath = [self.storyTitlesTable indexPathForRowAtPoint:p];
FeedDetailTableCell *cell = (FeedDetailTableCell *)[self.storyTitlesTable cellForRowAtIndexPath:indexPath];
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) return;
if (indexPath == nil) return;
NSDictionary *story = [self getStoryAtRow:indexPath.row];
appDelegate.activeStory = story;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate.masterContainerViewController showSendToPopover:cell];
} else {
[self presentViewController:[UIActivitiesControl activityViewControllerForView:self]
animated:YES
completion:nil];
}
}
- (void)markFeedsReadWithAllStories:(BOOL)includeHidden {
if (!self.isOffline && appDelegate.isRiverView && includeHidden &&
if (appDelegate.isRiverView && includeHidden &&
[appDelegate.activeFolder isEqualToString:@"everything"]) {
// Mark folder as read
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_all_as_read",
@ -1092,7 +1162,7 @@
[request startAsynchronous];
[appDelegate markActiveFolderAllRead];
} else if (!self.isOffline && appDelegate.isRiverView && includeHidden) {
} else if (appDelegate.isRiverView && includeHidden) {
// Mark folder as read
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
NEWSBLUR_URL];
@ -1101,11 +1171,14 @@
for (id feed_id in [appDelegate.dictFolders objectForKey:appDelegate.activeFolder]) {
[request addPostValue:feed_id forKey:@"feed_id"];
}
[request setDelegate:nil];
[request setUserInfo:@{@"feeds": appDelegate.activeFolderFeeds}];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate markActiveFolderAllRead];
} else if (!self.isOffline && !appDelegate.isRiverView && includeHidden) {
} else if (!appDelegate.isRiverView && includeHidden) {
// Mark feed as read
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
NEWSBLUR_URL];
@ -1114,6 +1187,7 @@
[request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setUserInfo:@{@"feeds": @[[appDelegate.activeFeed objectForKey:@"id"]]}];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate markFeedAllRead:[appDelegate.activeFeed objectForKey:@"id"]];
@ -1126,49 +1200,12 @@
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[feedsStories JSONRepresentation] forKey:@"feeds_stories"];
[request setDelegate:self];
[request setUserInfo:feedsStories];
[request setUserInfo:@{@"stories": feedsStories}];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request startAsynchronous];
} else {
// Must be offline and marking all as read, so load all stories.
NSMutableDictionary *feedsStories = [NSMutableDictionary dictionary];
[appDelegate.database inDatabase:^(FMDatabase *db) {
NSArray *feedIds;
if (appDelegate.isRiverView) {
feedIds = appDelegate.activeFolderFeeds;
} else if (appDelegate.activeFeed) {
feedIds = @[[appDelegate.activeFeed objectForKey:@"id"]];
} else {
return;
}
NSString *sql = [NSString stringWithFormat:@"SELECT u.story_feed_id, u.story_hash "
"FROM unread_hashes u WHERE u.story_feed_id IN (%@)",
[feedIds componentsJoinedByString:@","]];
FMResultSet *cursor = [db executeQuery:sql];
while ([cursor next]) {
NSDictionary *story = [cursor resultDictionary];
NSString *feedIdStr = [story objectForKey:@"story_feed_id"];
NSString *storyHash = [story objectForKey:@"story_hash"];
if (![feedsStories objectForKey:feedIdStr]) {
[feedsStories setObject:[NSMutableArray array] forKey:feedIdStr];
}
NSMutableArray *stories = [feedsStories objectForKey:feedIdStr];
[stories addObject:storyHash];
}
}];
for (NSString *feedId in [feedsStories allKeys]) {
[appDelegate markFeedAllRead:feedId];
}
[appDelegate queueReadStories:feedsStories];
}
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate.navigationController popToRootViewControllerAnimated:YES];
[appDelegate.masterContainerViewController transitionFromFeedDetail];
@ -1182,23 +1219,20 @@
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request {
// [self informError:@"Failed to mark story as read"];
NSDictionary *feedsStories = request.userInfo;
[appDelegate queueReadStories:feedsStories];
[appDelegate markStoriesRead:[request.userInfo objectForKey:@"stories"]
inFeeds:[request.userInfo objectForKey:@"feeds"]
cutoffTimestamp:nil];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {
if (request.responseStatusCode != 200) {
[self requestFailedMarkStoryRead:request];
return;
}
// NSString *responseString = [request responseString];
// NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
// NSError *error;
// NSDictionary *results = [NSJSONSerialization
// JSONObjectWithData:responseData
// options:kNilOptions
// error:&error];
if ([request.userInfo objectForKey:@"feeds"]) {
[appDelegate markFeedReadInCache:@[[request.userInfo objectForKey:@"feeds"]]];
}
}
- (IBAction)doOpenMarkReadActionSheet:(id)sender {
@ -1227,8 +1261,8 @@
self.actionSheet_ = options;
[appDelegate calculateStoryLocations];
int visibleUnreadCount = appDelegate.visibleUnreadCount;
int totalUnreadCount = [appDelegate unreadCount];
NSInteger visibleUnreadCount = appDelegate.visibleUnreadCount;
NSInteger totalUnreadCount = [appDelegate unreadCount];
NSArray *buttonTitles = nil;
BOOL showVisible = YES;
BOOL showEntire = YES;
@ -1242,8 +1276,8 @@
@"this site"];
NSString *visibleText = [NSString stringWithFormat:@"Mark %@ read",
visibleUnreadCount == 1 ? @"this story as" :
[NSString stringWithFormat:@"these %d stories",
visibleUnreadCount]];
[NSString stringWithFormat:@"these %ld stories",
(long)visibleUnreadCount]];
if (showVisible && showEntire) {
buttonTitles = [NSArray arrayWithObjects:visibleText, entireText, nil];
options.destructiveButtonIndex = 1;
@ -1271,8 +1305,8 @@
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
// NSLog(@"Action option #%d on %d", buttonIndex, actionSheet.tag);
if (actionSheet.tag == 1) {
int visibleUnreadCount = appDelegate.visibleUnreadCount;
int totalUnreadCount = [appDelegate unreadCount];
NSInteger visibleUnreadCount = appDelegate.visibleUnreadCount;
NSInteger totalUnreadCount = [appDelegate unreadCount];
BOOL showVisible = YES;
BOOL showEntire = YES;
// if ([appDelegate.activeFolder isEqualToString:@"everything"]) showEntire = NO;
@ -1302,7 +1336,7 @@
} else if (buttonIndex == 2) {
[self instafetchFeed];
}
}
}
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
@ -1327,7 +1361,7 @@
if ([self.popoverController respondsToSelector:@selector(setContainerViewProperties:)]) {
[self.popoverController setContainerViewProperties:[self improvedContainerViewProperties]];
}
int menuCount = [appDelegate.feedDetailMenuViewController.menuOptions count] + 2;
NSInteger menuCount = [appDelegate.feedDetailMenuViewController.menuOptions count] + 2;
[self.popoverController setPopoverContentSize:CGSizeMake(260, 38 * menuCount)];
[self.popoverController presentPopoverFromBarButtonItem:self.settingsBarButton
permittedArrowDirections:UIPopoverArrowDirectionUp
@ -1428,11 +1462,11 @@
- (void)showUserProfile {
appDelegate.activeUserProfileId = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed objectForKey:@"user_id"]];
appDelegate.activeUserProfileName = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed objectForKey:@"username"]];
[appDelegate showUserProfileModal:self.navigationItem.rightBarButtonItem];
[appDelegate showUserProfileModal:titleImageBarButton];
}
- (void)changeActiveFeedDetailRow {
int rowIndex = [appDelegate locationOfActiveStory];
NSInteger rowIndex = [appDelegate locationOfActiveStory];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
NSIndexPath *offsetIndexPath = [NSIndexPath indexPathForRow:rowIndex - 1 inSection:0];
@ -1454,6 +1488,169 @@
}
}
#pragma mark -
#pragma mark Story Actions - read
- (void)markStoryAsRead:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_hashes_as_read",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[story objectForKey:@"story_hash"]
forKey:@"story_hash"];
[request setDidFinishSelector:@selector(finishMarkAsRead:)];
[request setDidFailSelector:@selector(failedMarkAsRead:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
if ([appDelegate.dictFeeds objectForKey:[NSString stringWithFormat:@"%@", [story objectForKey:@"story_feed_id"]]]) {
[appDelegate markStoryRead:[story objectForKey:@"story_hash"]
feedId:[story objectForKey:@"story_feed_id"]];
}
}
- (void)finishMarkAsRead:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsRead:request];
}
[appDelegate.storyPageControl refreshHeaders];
}
- (void)failedMarkAsRead:(ASIFormDataRequest *)request {
NSString *storyFeedId = [request.userInfo objectForKey:@"story_feed_id"];
NSString *storyHash = [request.userInfo objectForKey:@"story_hash"];
[appDelegate queueReadStories:@{storyFeedId: @[storyHash]}];
}
- (void)markStoryAsUnread:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_unread",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[story objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAsUnread:)];
[request setDidFailSelector:@selector(failedMarkAsUnread:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
[appDelegate markStoryUnread:[story objectForKey:@"story_hash"]
feedId:[story objectForKey:@"story_feed_id"]];
}
- (void)finishMarkAsUnread:(ASIFormDataRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
if ([request responseStatusCode] != 200 || [[results objectForKey:@"code"] integerValue] < 0) {
return [self failedMarkAsUnread:request];
}
appDelegate.storyPageControl.currentPage.isRecentlyUnread = YES;
[appDelegate.storyPageControl refreshHeaders];
}
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request {
NSString *storyFeedId = [request.userInfo objectForKey:@"story_feed_id"];
NSString *storyHash = [request.userInfo objectForKey:@"story_hash"];
BOOL dequeued = [appDelegate dequeueReadStoryHash:storyHash inFeed:storyFeedId];
if (!dequeued) {
[self informError:@"Failed to unread story"];
[appDelegate markStoryRead:storyHash feedId:storyFeedId];
[self.storyTitlesTable reloadData];
} else {
[appDelegate.unreadStoryHashes setObject:[NSNumber numberWithBool:YES] forKey:storyHash];
[self.storyTitlesTable reloadData];
}
}
#pragma mark -
#pragma mark Story Actions - save
- (void)markStoryAsSaved:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_starred",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[story objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAsSaved:)];
[request setDidFailSelector:@selector(failedMarkAsSaved:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
[appDelegate markStory:story asSaved:YES];
}
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsSaved:request];
}
[appDelegate.storyPageControl refreshHeaders];
}
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request {
[self informError:@"Failed to save story"];
[appDelegate markStory:request.userInfo asSaved:NO];
[self.storyTitlesTable reloadData];
}
- (void)markStoryAsUnsaved:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_unstarred",
NEWSBLUR_URL];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[story objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAsUnsaved:)];
[request setDidFailSelector:@selector(failedMarkAsUnsaved:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
[appDelegate markStory:story asSaved:NO];
}
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsUnsaved:request];
}
[appDelegate.storyPageControl refreshHeaders];
}
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request {
[self informError:@"Failed to unsave story"];
[appDelegate markStory:request.userInfo asSaved:YES];
[self.storyTitlesTable reloadData];
}
#pragma mark -
#pragma mark instafetchFeed

View file

@ -9,11 +9,9 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "UnreadCountView.h"
#import "ABTableViewCell.h"
#import "NBSwipeableCell.h"
@class NewsBlurAppDelegate;
@interface FeedTableCell : ABTableViewCell {
@interface FeedTableCell : NBSwipeableCell {
NewsBlurAppDelegate *appDelegate;
NSString *feedTitle;
@ -23,6 +21,8 @@
int _negativeCount;
NSString *_negativeCountStr;
BOOL isSocial;
UIView *cellContent;
UnreadCountView *unreadCount;
}
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@ -33,5 +33,17 @@
@property (assign, nonatomic) int negativeCount;
@property (assign, nonatomic) BOOL isSocial;
@property (nonatomic) NSString *negativeCountStr;
@property (nonatomic) UnreadCountView *unreadCount;
- (void)setupGestures;
- (void)redrawUnreadCounts;
@end
@interface FeedTableCellView : UIView
@property (nonatomic, weak) FeedTableCell *cell;
- (void)redrawUnreadCounts;
@end

View file

@ -23,36 +23,50 @@ static UIFont *textFont = nil;
@synthesize negativeCount = _negativeCount;
@synthesize negativeCountStr;
@synthesize isSocial;
@synthesize unreadCount;
+ (void) initialize{
if (self == [FeedTableCell class]) {
textFont = [UIFont boldSystemFontOfSize:18];
// UIColor *psGrad = UIColorFromRGB(0x559F4D);
// UIColor *ntGrad = UIColorFromRGB(0xE4AB00);
// UIColor *ngGrad = UIColorFromRGB(0x9B181B);
// const CGFloat* psTop = CGColorGetComponents(ps.CGColor);
// const CGFloat* psBot = CGColorGetComponents(psGrad.CGColor);
// CGFloat psGradient[] = {
// psTop[0], psTop[1], psTop[2], psTop[3],
// psBot[0], psBot[1], psBot[2], psBot[3]
// };
// psColors = psGradient;
}
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
unreadCount = [UnreadCountView alloc];
unreadCount.appDelegate = self.appDelegate;
self.unreadCount = unreadCount;
cellContent = [[FeedTableCellView alloc] initWithFrame:self.frame];
cellContent.opaque = YES;
[self.contentView addSubview:cellContent];
}
return self;
}
- (void)drawRect:(CGRect)rect {
((FeedTableCellView *)cellContent).cell = self;
cellContent.frame = rect;
[cellContent setNeedsDisplay];
[self setupGestures];
}
- (void) setPositiveCount:(int)ps {
if (ps == _positiveCount) return;
_positiveCount = ps;
[self setNeedsDisplay];
// [cellContent setNeedsDisplay];
}
- (void) setNeutralCount:(int)nt {
if (nt == _neutralCount) return;
_neutralCount = nt;
[self setNeedsDisplay];
// [cellContent setNeedsDisplay];
}
- (void) setNegativeCount:(int)ng {
@ -60,97 +74,124 @@ static UIFont *textFont = nil;
_negativeCount = ng;
_negativeCountStr = [NSString stringWithFormat:@"%d", ng];
[self setNeedsDisplay];
// [cellContent setNeedsDisplay];
}
- (void) drawContentView:(CGRect)r highlighted:(BOOL)highlighted {
- (void)setupGestures {
[self setDelegate:(NewsBlurViewController <MCSwipeTableViewCellDelegate> *)appDelegate.feedsViewController];
[self setFirstStateIconName:self.isSocial ? @"menu_icn_fetch_subscribers.png" : @"train.png"
firstColor:UIColorFromRGB(0xA4D97B)
secondStateIconName:nil
secondColor:nil
thirdIconName:@"g_icn_unread.png"
thirdColor:UIColorFromRGB(0xFFFFD2)
fourthIconName:nil
fourthColor:nil];
self.mode = MCSwipeTableViewCellModeSwitch;
self.shouldAnimatesIcons = NO;
}
- (void)redrawUnreadCounts {
[((FeedTableCellView *)cellContent) redrawUnreadCounts];
}
@end
@implementation FeedTableCellView
@synthesize cell;
- (void)drawRect:(CGRect)r {
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *backgroundColor;
backgroundColor = highlighted ?
UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR) :
self.isSocial ? UIColorFromRGB(0xE6ECE8) :
backgroundColor = cell.highlighted || cell.selected ?
UIColorFromRGB(0xFFFFD2) :
cell.isSocial ? UIColorFromRGB(0xE6ECE8) :
UIColorFromRGB(0xF7F8F5);
[backgroundColor set];
CGContextFillRect(context, r);
if (highlighted) {
[NewsBlurAppDelegate fillGradient:r startColor:UIColorFromRGB(0xFFFFD2) endColor:UIColorFromRGB(0xFDED8D)];
if (cell.highlighted || cell.selected) {
// [NewsBlurAppDelegate fillGradient:CGRectMake(r.origin.x, r.origin.y + 1, r.size.width, r.size.height - 1) startColor:UIColorFromRGB(0xFFFFD2) endColor:UIColorFromRGB(0xFDED8D)];
// top border
UIColor *highlightBorderColor = UIColorFromRGB(0xE3D0AE);
CGFloat lineWidth = 0.5f;
CGContextSetStrokeColor(context, CGColorGetComponents([highlightBorderColor CGColor]));
CGContextSetLineWidth(context, lineWidth);
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0.5f);
CGContextMoveToPoint(context, 0, lineWidth*0.5f);
CGContextAddLineToPoint(context, r.size.width, 0.5f);
CGContextStrokePath(context);
// bottom border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, r.size.height - .5f);
CGContextAddLineToPoint(context, r.size.width, r.size.height - .5f);
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, 0, r.size.height - .5f*lineWidth);
CGContextAddLineToPoint(context, r.size.width, r.size.height - .5f*lineWidth);
CGContextStrokePath(context);
}
UnreadCountView *unreadCount = [UnreadCountView alloc];
unreadCount.appDelegate = appDelegate;
[unreadCount drawInRect:r ps:_positiveCount nt:_neutralCount
listType:(isSocial ? NBFeedListSocial : NBFeedListFeed)];
[cell.unreadCount drawInRect:r ps:cell.positiveCount nt:cell.neutralCount
listType:(cell.isSocial ? NBFeedListSocial : NBFeedListFeed)];
UIColor *textColor = highlighted ?
UIColor *textColor = cell.highlighted || cell.selected ?
[UIColor blackColor]:
UIColorFromRGB(0x3a3a3a);
[textColor set];
UIFont *font;
if (self.negativeCount || self.neutralCount || self.positiveCount) {
if (cell.negativeCount || cell.neutralCount || cell.positiveCount) {
font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:13.0];
} else {
font = [UIFont fontWithName:@"Helvetica" size:12.6];
}
if (isSocial) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
paragraphStyle.alignment = NSTextAlignmentLeft;
if (cell.isSocial) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.feedFavicon drawInRect:CGRectMake(9.0, 2.0, 28.0, 28.0)];
[feedTitle
drawInRect:CGRectMake(46, 7, r.size.width - ([unreadCount offsetWidth] + 36) - 10 - 16, 20.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
[cell.feedFavicon drawInRect:CGRectMake(9.0, 2.0, 28.0, 28.0)];
[cell.feedTitle drawInRect:CGRectMake(46, 7, r.size.width - ([cell.unreadCount offsetWidth] + 36) - 10 - 16, 20.0)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
} else {
[self.feedFavicon drawInRect:CGRectMake(9.0, 3.0, 26.0, 26.0)];
[feedTitle
drawInRect:CGRectMake(42, 7, r.size.width - ([unreadCount offsetWidth] + 36) - 10 - 12, 20.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
[cell.feedFavicon drawInRect:CGRectMake(9.0, 3.0, 26.0, 26.0)];
[cell.feedTitle drawInRect:CGRectMake(42, 7, r.size.width - ([cell.unreadCount offsetWidth] + 36) - 10 - 12, 20.0)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
}
} else {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.feedFavicon drawInRect:CGRectMake(12.0, 7.0, 16.0, 16.0)];
[feedTitle
drawInRect:CGRectMake(36.0, 7.0, r.size.width - ([unreadCount offsetWidth] + 36) - 10, 20.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
[cell.feedFavicon drawInRect:CGRectMake(12.0, 7.0, 16.0, 16.0)];
[cell.feedTitle drawInRect:CGRectMake(36.0, 7.0, r.size.width - ([cell.unreadCount offsetWidth] + 36) - 10, 20.0)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
} else {
[self.feedFavicon drawInRect:CGRectMake(9.0, 7.0, 16.0, 16.0)];
[feedTitle
drawInRect:CGRectMake(34.0, 7.0, r.size.width - ([unreadCount offsetWidth] + 36) - 10, 20.0)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
[cell.feedFavicon drawInRect:CGRectMake(9.0, 7.0, 16.0, 16.0)];
[cell.feedTitle drawInRect:CGRectMake(34.0, 7.0, r.size.width - ([cell.unreadCount offsetWidth] + 36) - 10, 20.0)
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
}
}
}
- (void)redrawUnreadCounts {
// [cell.unreadCount drawInRect:self.frame ps:cell.positiveCount nt:cell.neutralCount
// listType:(cell.isSocial ? NBFeedListSocial : NBFeedListFeed)];
cell.unreadCount.psCount = cell.positiveCount;
cell.unreadCount.ntCount = cell.neutralCount;
[cell.unreadCount setNeedsLayout];
}
@end

View file

@ -102,7 +102,7 @@
return cell;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 38;
}

View file

@ -200,7 +200,7 @@
}
- (void)updateSites {
self.instructionLabel.text = [NSString stringWithFormat:@"You are subscribed to %d sites", [[appDelegate.dictFeeds allKeys] count]];
self.instructionLabel.text = [NSString stringWithFormat:@"You are subscribed to %lu sites", (unsigned long)[[appDelegate.dictFeeds allKeys] count]];
NSString *msg = [NSString stringWithFormat:@"Imported %i site%@",
self.importedFeedCount_,
self.importedFeedCount_ == 1 ? @"" : @"s"];
@ -366,8 +366,8 @@
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SiteCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"ActivityCell"];
SiteCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"ActivityCell"];
if (cell == nil) {
cell = [[SiteCell alloc]
initWithStyle:UITableViewCellStyleDefault

View file

@ -13,12 +13,17 @@
@class NewsBlurAppDelegate;
@interface FolderTitleView : UIView {
@interface FolderTitleView : UIView
<UIGestureRecognizerDelegate,
UIActionSheetDelegate> {
NewsBlurAppDelegate *appDelegate;
}
@property (assign, nonatomic) int section;
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@property (nonatomic) UnreadCountView *unreadCount;
@property (nonatomic) UIButton *invisibleHeaderButton;
- (void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer;
@end

View file

@ -8,6 +8,7 @@
#import <QuartzCore/QuartzCore.h>
#import "NewsBlurAppDelegate.h"
#import "NewsBlurViewController.h"
#import "FolderTitleView.h"
#import "UnreadCountView.h"
@ -16,6 +17,7 @@
@synthesize appDelegate;
@synthesize section;
@synthesize unreadCount;
@synthesize invisibleHeaderButton;
- (void)setNeedsDisplay {
[unreadCount setNeedsDisplay];
@ -25,7 +27,7 @@
- (void) drawRect:(CGRect)rect {
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
self.appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
CGContextRef context = UIGraphicsGetCurrentContext();
@ -84,21 +86,20 @@
CGContextSetStrokeColor(context, CGColorGetComponents([topColor CGColor]));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0.5f);
CGContextAddLineToPoint(context, rect.size.width, 0.5f);
CGContextMoveToPoint(context, 0, 0.25f);
CGContextAddLineToPoint(context, rect.size.width, 0.25f);
CGContextStrokePath(context);
// bottom border
UIColor *bottomColor = UIColorFromRGB(0xB7BBAA);
CGContextSetStrokeColor(context, CGColorGetComponents([bottomColor CGColor]));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, rect.size.height - .5f);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height - .5f);
CGContextMoveToPoint(context, 0, rect.size.height-0.25f);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height-0.25f);
CGContextStrokePath(context);
// Folder title
UIColor *textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0];
[textColor set];
UIFont *font = [UIFont boldSystemFontOfSize:11];
NSString *folderTitle;
if (section == 0) {
@ -114,14 +115,17 @@
}
UIColor *shadowColor = UIColorFromRGB(0xF0F2E9);
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 0, [shadowColor CGColor]);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
paragraphStyle.alignment = NSTextAlignmentLeft;
[folderTitle
drawInRect:CGRectMake(36.0, 10, rect.size.width - 36 - 36 - countWidth, 14)
withFont:font
lineBreakMode:NSLineBreakByTruncatingTail
alignment:NSTextAlignmentLeft];
withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle}];
UIButton *invisibleHeaderButton = [UIButton buttonWithType:UIButtonTypeCustom];
invisibleHeaderButton = [UIButton buttonWithType:UIButtonTypeCustom];
invisibleHeaderButton.frame = CGRectMake(0, 0, customView.frame.size.width, customView.frame.size.height);
invisibleHeaderButton.alpha = .1;
invisibleHeaderButton.tag = section;
@ -159,6 +163,7 @@
UIImage *folderImage;
int folderImageViewX = 10;
BOOL allowLongPress = NO;
if (section == 0) {
folderImage = [UIImage imageNamed:@"ak-icon-global.png"];
@ -198,6 +203,7 @@
} else {
folderImageViewX = 7;
}
allowLongPress = YES;
}
[folderImage drawInRect:CGRectMake(folderImageViewX, 6, 20, 20)];
@ -208,6 +214,55 @@
} else {
[self addSubview:customView];
}
if (allowLongPress) {
UILongPressGestureRecognizer *longpress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(handleLongPress:)];
longpress.minimumPressDuration = 1.0;
longpress.delegate = self;
[self addGestureRecognizer:longpress];
}
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) return;
if (section < 2) return;
NSString *folderTitle = [appDelegate.dictFoldersArray objectAtIndex:section];
UIActionSheet *markReadSheet = [[UIActionSheet alloc] initWithTitle:folderTitle
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:@"Mark folder as read"
otherButtonTitles:@"1 day", @"3 days", @"7 days", @"14 days", nil];
markReadSheet.accessibilityValue = folderTitle;
[markReadSheet showInView:appDelegate.feedsViewController.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *folderTitle = actionSheet.accessibilityValue;
NSArray *feedIds = [appDelegate.dictFolders objectForKey:folderTitle];
switch (buttonIndex) {
case 0:
[appDelegate.feedsViewController markFeedsRead:feedIds cutoffDays:0];
break;
case 1:
[appDelegate.feedsViewController markFeedsRead:feedIds cutoffDays:1];
break;
case 2:
[appDelegate.feedsViewController markFeedsRead:feedIds cutoffDays:3];
break;
case 3:
[appDelegate.feedsViewController markFeedsRead:feedIds cutoffDays:7];
break;
case 4:
[appDelegate.feedsViewController markFeedsRead:feedIds cutoffDays:14];
break;
}
[appDelegate.feedsViewController sectionUntappedOutside:invisibleHeaderButton];
}
@end

View file

@ -178,7 +178,7 @@
return cell;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return kMenuOptionHeight;
}
@ -231,11 +231,17 @@
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.frame = CGRectMake(0, 0, 240, kMenuOptionHeight);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.separatorInset = UIEdgeInsetsZero;
fontStyleSegment.frame = CGRectMake(8, 4, cell.frame.size.width - 8*2, kMenuOptionHeight - 4*2);
[fontStyleSegment setTitle:@"Helvetica" forSegmentAtIndex:0];
[fontStyleSegment setTitle:@"Georgia" forSegmentAtIndex:1];
[fontStyleSegment setTitle:[@"Helvetica" uppercaseString] forSegmentAtIndex:0];
[fontStyleSegment setTitle:[@"Georgia" uppercaseString] forSegmentAtIndex:1];
[fontStyleSegment setTintColor:UIColorFromRGB(0x738570)];
[fontStyleSegment
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
[fontStyleSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:0];
[fontStyleSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:1];
[cell addSubview:fontStyleSegment];
@ -246,6 +252,7 @@
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.frame = CGRectMake(0, 0, 240, kMenuOptionHeight);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.separatorInset = UIEdgeInsetsZero;
fontSizeSegment.frame = CGRectMake(8, 4, cell.frame.size.width - 8*2, kMenuOptionHeight - 4*2);
[fontSizeSegment setTitle:@"12pt" forSegmentAtIndex:0];
@ -254,6 +261,15 @@
[fontSizeSegment setTitle:@"15pt" forSegmentAtIndex:3];
[fontSizeSegment setTitle:@"17pt" forSegmentAtIndex:4];
[fontSizeSegment setTintColor:UIColorFromRGB(0x738570)];
[fontSizeSegment
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
[fontSizeSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:0];
[fontSizeSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:1];
[fontSizeSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:2];
[fontSizeSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:3];
[fontSizeSegment setContentOffset:CGSizeMake(0, 1) forSegmentAtIndex:4];
[cell addSubview:fontSizeSegment];

View file

@ -56,7 +56,7 @@
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
self.view.frame = CGRectMake(0, 0, 320, 416);
self.contentSizeForViewInPopover = self.view.frame.size;
self.preferredContentSize = self.view.frame.size;
}
- (void)viewDidUnload
@ -273,10 +273,10 @@ viewForHeaderInSection:(NSInteger)section {
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.inSearch_){
int userCount = [self.userProfiles count];
NSInteger userCount = [self.userProfiles count];
return userCount;
} else {
int userCount = [self.suggestedUserProfiles count];
NSInteger userCount = [self.suggestedUserProfiles count];
if (!userCount) {
return 3;
}
@ -304,7 +304,7 @@ viewForHeaderInSection:(NSInteger)section {
if (self.inSearch_){
int userProfileCount = [self.userProfiles count];
NSInteger userProfileCount = [self.userProfiles count];
if (userProfileCount) {
if (userProfileCount > indexPath.row) {
@ -379,7 +379,7 @@ viewForHeaderInSection:(NSInteger)section {
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
NSInteger currentRow = indexPath.row;
int row = currentRow;
NSInteger row = currentRow;
appDelegate.activeUserProfileId = [[self.userProfiles objectAtIndex:row] objectForKey:@"user_id"];
appDelegate.activeUserProfileName = [[self.userProfiles objectAtIndex:row] objectForKey:@"username"];
[self.friendSearchBar resignFirstResponder];

View file

@ -25,6 +25,7 @@
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
interactionLabel = nil;
avatarView = nil;
self.separatorInset = UIEdgeInsetsMake(0, 90, 0, 0);
// create the label and the avatar
UIImageView *avatar = [[UIImageView alloc] initWithFrame:CGRectZero];
@ -55,16 +56,19 @@
[super layoutSubviews];
// determine outer bounds
CGRect contentRect = self.contentView.bounds;
// position label to bounds
CGRect labelRect = contentRect;
labelRect.origin.x = labelRect.origin.x + leftMargin + avatarSize + leftMargin;
labelRect.origin.y = labelRect.origin.y + topMargin - 1;
labelRect.size.width = contentRect.size.width - leftMargin - avatarSize - leftMargin - rightMargin;
labelRect.size.height = contentRect.size.height - topMargin - bottomMargin;
self.interactionLabel.frame = labelRect;
[self.interactionLabel sizeToFit];
CGRect contentRect = self.frame;
CGRect labelFrame = self.interactionLabel.frame;
// position avatar to bounds
self.avatarView.frame = CGRectMake(leftMargin, topMargin, avatarSize, avatarSize);
// position label to bounds
labelFrame.origin.x = leftMargin*2 + avatarSize;
labelFrame.origin.y = topMargin - 1;
labelFrame.size.width = contentRect.size.width - leftMargin - avatarSize - leftMargin - rightMargin - 20;
labelFrame.size.height = contentRect.size.height - topMargin - bottomMargin;
self.interactionLabel.frame = labelFrame;
}
@ -113,11 +117,12 @@
NSString *txtWithTime = [NSString stringWithFormat:@"%@\n \n%@", txt, time];
NSMutableAttributedString* attrStr = [[NSMutableAttributedString alloc] initWithString:txtWithTime];
NSMutableParagraphStyle* style = [NSMutableParagraphStyle new];
style.lineBreakMode = NSLineBreakByWordWrapping;
style.alignment = NSTextAlignmentLeft;
style.lineSpacing = 1.0f;
[attrStr setAttributes:@{NSParagraphStyleAttributeName: style} range:NSMakeRange(0, [txtWithTime length])];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = NSTextAlignmentLeft;
paragraphStyle.lineSpacing = 1.0f;
[attrStr setAttributes:@{NSParagraphStyleAttributeName: paragraphStyle}
range:NSMakeRange(0, [txtWithTime length])];
[attrStr addAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"Helvetica" size:13]} range:NSMakeRange(0, [txtWithTime length])];
if (self.highlighted) {

View file

@ -180,7 +180,7 @@
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int userInteractions = [appDelegate.userInteractionsArray count];
NSInteger userInteractions = [appDelegate.userInteractionsArray count];
int minimumHeight;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
minimumHeight = MINIMUM_INTERACTION_HEIGHT_IPAD;
@ -210,7 +210,7 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int userInteractionsCount = [appDelegate.userInteractionsArray count];
NSInteger userInteractionsCount = [appDelegate.userInteractionsArray count];
return userInteractionsCount + 1;
}
@ -246,7 +246,7 @@
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int userInteractions = [appDelegate.userInteractionsArray count];
NSInteger userInteractions = [appDelegate.userInteractionsArray count];
if (indexPath.row < userInteractions) {
NSDictionary *interaction = [appDelegate.userInteractionsArray objectAtIndex:indexPath.row];
NSString *category = [interaction objectForKey:@"category"];

View file

@ -12,8 +12,6 @@
#define LANDSCAPE_MARGIN 128
@class NewsBlurAppDelegate;
@interface LoginViewController : UIViewController
<ASIHTTPRequestDelegate> {
NewsBlurAppDelegate *appDelegate;

View file

@ -7,10 +7,8 @@
//
#import "LoginViewController.h"
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import <QuartzCore/QuartzCore.h>
//#import <QuartzCore/QuartzCore.h>
@implementation LoginViewController
@ -50,7 +48,12 @@
self.emailInput.borderStyle = UITextBorderStyleRoundedRect;
self.signUpPasswordInput.borderStyle = UITextBorderStyleRoundedRect;
self.signUpUsernameInput.borderStyle = UITextBorderStyleRoundedRect;
[self.loginControl
setTitleTextAttributes:@{NSFontAttributeName:
[UIFont fontWithName:@"Helvetica-Bold" size:11.0f]}
forState:UIControlStateNormal];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
self.logInView.frame = CGRectMake(134, 180, 500, 300);
@ -220,6 +223,7 @@
[self.passwordInput setText:@""];
[self.signUpPasswordInput setText:@""];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
@ -280,7 +284,7 @@
[self.signUpPasswordInput setText:@""];
// [appDelegate showFirstTimeUser];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:NO completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
@ -359,6 +363,7 @@
// Login
usernameInput.frame = CGRectMake(20, 67, 280, 31);
usernameOrEmailLabel.alpha = 1.0;
usernameLabel.alpha = 0.0;
passwordInput.frame = CGRectMake(20, 129, 280, 31);
passwordLabel.frame = CGRectMake(21, 106, 212, 22);
@ -377,6 +382,7 @@
// Signup
usernameInput.frame = CGRectMake(20, 67, 130, 31);
usernameOrEmailLabel.alpha = 0.0;
usernameLabel.alpha = 1.0;
passwordInput.frame = CGRectMake(170, 67, 130, 31);
passwordLabel.frame = CGRectMake(171, 44, 212, 22);

View file

@ -21,6 +21,7 @@
self.textLabel.shadowColor = UIColorFromRGB(0xF0F0F0);
self.textLabel.shadowOffset = CGSizeMake(0, 1);
self.textLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:14.0];
[self setSeparatorInset:UIEdgeInsetsMake(0, 38, 0, 0)];
UIView *background = [[UIView alloc] init];
[background setBackgroundColor:UIColorFromRGB(0xFFFFFF)];
[self setBackgroundView:background];

View file

@ -29,7 +29,7 @@
- (NSArray *)pickerFolders;
@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate;
@property (nonatomic) IBOutlet FolderTextField *fromFolderInput;
@property (nonatomic) IBOutlet UITextField *fromFolderInput;
@property (nonatomic) IBOutlet FolderTextField *toFolderInput;
@property (nonatomic) IBOutlet UILabel *titleLabel;

View file

@ -36,15 +36,22 @@
- (void)viewDidLoad {
UIImageView *folderImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"g_icn_folder.png"]];
folderImage.frame = CGRectMake(-6, 0, 16, 16);
folderImage.frame = CGRectMake(0, 0, 24, 16);
[folderImage setContentMode:UIViewContentModeRight];
[toFolderInput setLeftView:folderImage];
[toFolderInput setLeftViewMode:UITextFieldViewModeAlways];
UIImageView *folderImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"g_icn_folder_rss.png"]];
folderImage2.frame = CGRectMake(-6, 0, 16, 16);
folderImage2.frame = CGRectMake(0, 0, 24, 16);
[folderImage2 setContentMode:UIViewContentModeRight];
[fromFolderInput setLeftView:folderImage2];
[fromFolderInput setLeftViewMode:UITextFieldViewModeAlways];
navBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9];
CGRect frame = self.navBar.frame;
frame.size.height += 20;
self.navBar.frame = frame;
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
@ -71,7 +78,7 @@
[subview removeFromSuperview];
}
UIView *label = [appDelegate makeFeedTitle:appDelegate.activeFeed];
label.frame = CGRectMake(0,
label.frame = CGRectMake(8,
0,
200,
20);
@ -89,7 +96,7 @@
- (void)reload {
BOOL isTopLevel = [[appDelegate.activeFolder trim] isEqualToString:@""] ||
[appDelegate.activeFolder isEqual:@"everything"];
int row = 0;
NSInteger row = 0;
[toFolderInput setText:@""];
if (appDelegate.isRiverView) {
@ -295,15 +302,11 @@ numberOfRowsInComponent:(NSInteger)component {
@implementation FolderTextField
- (CGRect)textRectForBounds:(CGRect)bounds {
int margin = 18;
CGRect inset = CGRectMake(bounds.origin.x + margin, bounds.origin.y, bounds.size.width - margin, bounds.size.height);
return inset;
return CGRectInset(bounds, 24, 0);
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
int margin = 18;
CGRect inset = CGRectMake(bounds.origin.x + margin, bounds.origin.y, bounds.size.width - margin, bounds.size.height);
return inset;
return CGRectInset(bounds, 24, 0);
}
@end

View file

@ -0,0 +1,15 @@
//
// NBBarButtonItem.h
// NewsBlur
//
// Created by Samuel Clay on 9/24/13.
// Copyright (c) 2013 NewsBlur. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface NBBarButtonItem : UIButton
@property (nonatomic, assign) BOOL onRightSide;
@end

View file

@ -0,0 +1,29 @@
//
// NBBarButtonItem.m
// NewsBlur
//
// Created by Samuel Clay on 9/24/13.
// Copyright (c) 2013 NewsBlur. All rights reserved.
//
#import "NBBarButtonItem.h"
@implementation NBBarButtonItem
@synthesize onRightSide;
- (UIEdgeInsets)alignmentRectInsets {
UIEdgeInsets insets;
if (![self isLeftButton] || self.onRightSide) {
insets = UIEdgeInsetsMake(0, 0, 0, 8.0f);
} else {
insets = UIEdgeInsetsMake(0, 8.0f, 0, 0);
}
return insets;
}
- (BOOL)isLeftButton {
return self.frame.origin.x < (self.window.frame.size.width / 2);
}
@end

View file

@ -16,6 +16,7 @@
#import "UserProfileViewController.h"
#import "InteractionCell.h"
#import "ActivityCell.h"
#import "FeedTableCell.h"
#import "FeedsMenuViewController.h"
#import "FeedDetailMenuViewController.h"
#import "FontSettingsViewController.h"
@ -25,8 +26,8 @@
#define NB_DEFAULT_MASTER_WIDTH 270
#define NB_DEFAULT_STORY_TITLE_HEIGHT 1004
#define NB_DEFAULT_SLIDER_INTERVAL 0.35
#define NB_DEFAULT_SLIDER_INTERVAL_OUT 0.35
#define NB_DEFAULT_SLIDER_INTERVAL 0.3
#define NB_DEFAULT_SLIDER_INTERVAL_OUT 0.3
#define NB_DEFAULT_SHARE_HEIGHT 144
#define NB_DEFAULT_STORY_TITLE_SNAP_THRESHOLD 60
@ -99,6 +100,8 @@
object:nil];
self.view.backgroundColor = UIColorFromRGB(0xC2C5BE);
self.navigationController.navigationBar.translucent = NO;
self.masterNavigationController.navigationBar.translucent = NO;
self.masterNavigationController = appDelegate.navigationController;
self.feedsViewController = appDelegate.feedsViewController;
@ -120,9 +123,11 @@
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.storyPageControl];
self.storyNavigationController = nav;
self.storyNavigationController.navigationBar.translucent = NO;
UINavigationController *shareNav = [[UINavigationController alloc] initWithRootViewController:self.shareViewController];
self.shareNavigationController = shareNav;
self.shareNavigationController.navigationBar.translucent = NO;
// set default y coordinate for feedDetailY from saved preferences
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
@ -140,12 +145,12 @@
// set up story titles stub
UIView * storyTitlesPlaceholder = [[UIView alloc] initWithFrame:CGRectZero];
storyTitlesPlaceholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;;
storyTitlesPlaceholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
storyTitlesPlaceholder.autoresizesSubviews = YES;
storyTitlesPlaceholder.backgroundColor = [UIColor whiteColor];
self.storyTitlesStub = storyTitlesPlaceholder;
[self.view addSubview:self.storyTitlesStub];
[self.view insertSubview:self.storyTitlesStub aboveSubview:self.storyNavigationController.view];
}
- (void)viewWillLayoutSubviews {
@ -202,10 +207,17 @@
[sender class] == [ActivityCell class]) {
InteractionCell *cell = (InteractionCell *)sender;
[popoverController presentPopoverFromRect:cell.bounds
inView:cell
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
[popoverController presentPopoverFromRect:cell.bounds
inView:cell
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else if ([sender class] == [FeedTableCell class]) {
FeedTableCell *cell = (FeedTableCell *)sender;
[popoverController presentPopoverFromRect:cell.bounds
inView:cell
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else if ([sender class] == [UIBarButtonItem class]) {
[popoverController presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
@ -246,7 +258,7 @@
popoverController = [[UIPopoverController alloc]
initWithContentViewController:appDelegate.feedsMenuViewController];
[popoverController setDelegate:self];
int menuCount = [appDelegate.feedsMenuViewController.menuOptions count];
NSInteger menuCount = [appDelegate.feedsMenuViewController.menuOptions count];
[popoverController setPopoverContentSize:CGSizeMake(200, 38 * menuCount)];
[popoverController presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
@ -266,7 +278,7 @@
[appDelegate.feedDetailMenuViewController buildMenuOptions];
popoverController.delegate = self;
int menuCount = [appDelegate.feedDetailMenuViewController.menuOptions count] + 2;
NSInteger menuCount = [appDelegate.feedDetailMenuViewController.menuOptions count] + 2;
[popoverController setPopoverContentSize:CGSizeMake(260, 38 * menuCount)];
[popoverController presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
@ -308,6 +320,12 @@
[popoverController presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:NO];
} else if ([sender class] == [FeedTableCell class]) {
FeedTableCell *cell = (FeedTableCell *)sender;
[popoverController presentPopoverFromRect:cell.bounds
inView:cell
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else {
CGRect frame = [sender CGRectValue];
[popoverController presentPopoverFromRect:frame
@ -361,7 +379,7 @@
- (void)adjustFeedDetailScreen {
CGRect vb = [self.view bounds];
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) {
// add the back button
@ -382,9 +400,6 @@
// remove the back button
self.storyPageControl.navigationItem.leftBarButtonItem = nil;
// remove center title
self.storyPageControl.navigationItem.titleView = nil;
if (![[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) {
[self.masterNavigationController pushViewController:self.feedDetailViewController animated:NO];
}
@ -396,6 +411,7 @@
- (void)adjustFeedDetailScreenForStoryTitles {
CGRect vb = [self.view bounds];
if (!self.storyTitlesOnLeft) {
if (self.storyTitlesYCoordinate > 890) {
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
@ -411,7 +427,7 @@
self.storyPageControl.navigationItem.leftBarButtonItem = nil;
// remove center title
self.storyPageControl.navigationItem.titleView = nil;
// self.storyPageControl.navigationItem.titleView = nil;
if (![[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) {
[self.masterNavigationController pushViewController:self.feedDetailViewController animated:NO];
@ -445,8 +461,8 @@
self.storyPageControl.navigationItem.leftBarButtonItem = self.storyPageControl.buttonBack;
// set center title
UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed];
self.storyPageControl.navigationItem.titleView = titleLabel;
// UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed];
// self.storyPageControl.navigationItem.titleView = titleLabel;
[UIView animateWithDuration:NB_DEFAULT_SLIDER_INTERVAL delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// self.masterNavigationController.view.frame = CGRectMake(-NB_DEFAULT_MASTER_WIDTH, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height);
@ -488,49 +504,50 @@
// reset the storyDetailViewController components
self.storyPageControl.currentPage.webView.hidden = YES;
self.storyPageControl.nextPage.webView.hidden = YES;
self.storyPageControl.bottomPlaceholderToolbar.hidden = NO;
self.storyPageControl.bottomSize.hidden = NO;
self.storyPageControl.navigationItem.rightBarButtonItems = nil;
[self.storyPageControl resetPages];
int unreadCount = appDelegate.unreadCount;
[self.storyPageControl hidePages];
NSInteger unreadCount = appDelegate.unreadCount;
if (unreadCount == 0) {
self.storyPageControl.circularProgressView.percentage = 1;
} else {
self.storyPageControl.circularProgressView.percentage = 0;
}
// UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
// if (NO && UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) {
// // CASE: story titles on bottom
// self.storyPageControl.navigationItem.leftBarButtonItem = self.storyPageControl.buttonBack;
//
// self.storyNavigationController.view.frame = CGRectMake(vb.size.width, 0, vb.size.width, storyTitlesYCoordinate);
// self.feedDetailViewController.view.frame = CGRectMake(vb.size.width,
// self.storyTitlesYCoordinate,
// vb.size.width,
// vb.size.height - storyTitlesYCoordinate);
// float largeTimeInterval = NB_DEFAULT_SLIDER_INTERVAL * ( vb.size.width - NB_DEFAULT_MASTER_WIDTH) / vb.size.width;
// float smallTimeInterval = NB_DEFAULT_SLIDER_INTERVAL * NB_DEFAULT_MASTER_WIDTH / vb.size.width;
//
// [UIView animateWithDuration:largeTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
// self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width, self.storyTitlesYCoordinate);
// self.feedDetailViewController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1,
// self.storyTitlesYCoordinate,
// vb.size.width,
// vb.size.height - storyTitlesYCoordinate);
// } completion:^(BOOL finished) {
// [UIView animateWithDuration:smallTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
// self.storyNavigationController.view.frame = CGRectMake(0, 0, vb.size.width, self.storyTitlesYCoordinate);
// self.feedDetailViewController.view.frame = CGRectMake(0, self.storyTitlesYCoordinate, vb.size.width, vb.size.height - storyTitlesYCoordinate);
// self.masterNavigationController.view.frame = CGRectMake(-NB_DEFAULT_MASTER_WIDTH, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height);
// } completion:^(BOOL finished) {
// [self.dashboardViewController.view removeFromSuperview];
// [self.masterNavigationController.view removeFromSuperview];
// }];
// }];
//
// UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed];
// self.storyPageControl.navigationItem.titleView = titleLabel;
// } else {
UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed];
self.storyPageControl.navigationItem.titleView = titleLabel;
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) {
// CASE: story titles on bottom
self.storyPageControl.navigationItem.leftBarButtonItem = self.storyPageControl.buttonBack;
self.storyPageControl.navigationItem.rightBarButtonItems = self.feedDetailViewController.navigationItem.rightBarButtonItems;
self.storyNavigationController.view.frame = CGRectMake(vb.size.width, 0, vb.size.width, storyTitlesYCoordinate);
self.feedDetailViewController.view.frame = CGRectMake(vb.size.width,
self.storyTitlesYCoordinate,
vb.size.width,
vb.size.height - storyTitlesYCoordinate);
float largeTimeInterval = NB_DEFAULT_SLIDER_INTERVAL * ( vb.size.width - NB_DEFAULT_MASTER_WIDTH) / vb.size.width;
float smallTimeInterval = NB_DEFAULT_SLIDER_INTERVAL * NB_DEFAULT_MASTER_WIDTH / vb.size.width;
[UIView animateWithDuration:largeTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width, self.storyTitlesYCoordinate);
self.feedDetailViewController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1,
self.storyTitlesYCoordinate,
vb.size.width,
vb.size.height - storyTitlesYCoordinate);
} completion:^(BOOL finished) {
[UIView animateWithDuration:smallTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.storyNavigationController.view.frame = CGRectMake(0, 0, vb.size.width, self.storyTitlesYCoordinate);
self.feedDetailViewController.view.frame = CGRectMake(0, self.storyTitlesYCoordinate, vb.size.width, vb.size.height - storyTitlesYCoordinate);
self.masterNavigationController.view.frame = CGRectMake(-NB_DEFAULT_MASTER_WIDTH, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height);
} completion:^(BOOL finished) {
[self.dashboardViewController.view removeFromSuperview];
[self.masterNavigationController.view removeFromSuperview];
}];
}];
} else {
// CASE: story titles on left
[self.masterNavigationController
pushViewController:self.feedDetailViewController
@ -548,9 +565,10 @@
[self.dashboardViewController.view removeFromSuperview];
}];
self.storyPageControl.navigationItem.titleView = nil;
// self.storyPageControl.navigationItem.titleView = nil;
self.storyPageControl.navigationItem.leftBarButtonItem = nil;
// }
self.storyPageControl.navigationItem.rightBarButtonItem = nil;
}
}
- (void)transitionFromFeedDetail {
@ -693,11 +711,12 @@
CGRect vb = [self.view bounds];
// account for top toolbar and status bar
yCoordinate = yCoordinate + 44 + 20;
yCoordinate = yCoordinate + 64 + 20;
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
if (yCoordinate > 344 && yCoordinate <= (vb.size.height)) {
if (yCoordinate <= (vb.size.height)) {
yCoordinate = MAX(yCoordinate, 384);
self.storyTitlesYCoordinate = yCoordinate;
[userPreferences setInteger:yCoordinate forKey:@"storyTitlesYCoordinate"];
[userPreferences synchronize];
@ -735,7 +754,23 @@
0);
}
}
UITableView *stories = appDelegate.feedDetailViewController.storyTitlesTable;
NSInteger location = appDelegate.locationOfActiveStory;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:location inSection:0];
NSArray *visible = [stories visibleCells];
for (UITableViewCell *cell in visible) {
if ([stories indexPathForCell:cell].row == indexPath.row) {
indexPath = nil;
break;
}
}
if (indexPath && location >= 0) {
[stories selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
[appDelegate.feedDetailViewController.notifier setNeedsLayout];
}
-(void)keyboardWillShowOrHide:(NSNotification*)notification {
@ -767,7 +802,7 @@
NB_DEFAULT_SHARE_HEIGHT);
CGRect shareViewFrame = self.shareNavigationController.view.frame;
if (self.keyboardIsShown) {
if (self.keyboardIsShown && self.isSharingStory) {
if (UIInterfaceOrientationIsPortrait(orientation)) {
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT - keyboardFrame.size.height + 44;
shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT - keyboardFrame.size.height;
@ -775,12 +810,12 @@
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT - keyboardFrame.size.width + 44;
shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT - keyboardFrame.size.width;
}
} else {
} else if (self.isSharingStory) {
if (UIInterfaceOrientationIsPortrait(orientation)) {
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 44;
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 64;
shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT;
} else {
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 44;
storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 64;
shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT;
}
}
@ -815,7 +850,7 @@
vb.size.height,
self.storyNavigationController.view.frame.size.width,
NB_DEFAULT_SHARE_HEIGHT);
if (UIInterfaceOrientationIsPortrait(orientation)) {
if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) {
self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x,
0,
self.storyNavigationController.view.frame.size.width,

View file

@ -195,6 +195,9 @@
- (void)showIn:(float)time {
showing = YES;
CGRect frame = self.frame;
frame.size.width = self.view.frame.size.width;
self.frame = frame;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:time];

View file

@ -0,0 +1,15 @@
//
// NBSwipeableCell.h
// NewsBlur
//
// Created by Samuel Clay on 9/27/13.
// Copyright (c) 2013 NewsBlur. All rights reserved.
//
#import "MCSwipeTableViewCell.h"
@interface NBSwipeableCell : MCSwipeTableViewCell
- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha;
@end

View file

@ -0,0 +1,65 @@
//
// NBSwipeableCell.m
// NewsBlur
//
// Created by Samuel Clay on 9/27/13.
// Copyright (c) 2013 NewsBlur. All rights reserved.
//
#import "NBSwipeableCell.h"
#import "MCSwipeTableViewCell.h"
@implementation NBSwipeableCell
- (void)setNeedsDisplay {
[super setNeedsDisplay];
for (UIView *view in self.contentView.subviews) {
[view setNeedsDisplay];
}
}
- (void)setNeedsLayout {
[super setNeedsLayout];
for (UIView *view in self.contentView.subviews) {
[view setNeedsLayout];
}
}
- (void) setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:NO];
if (animated) {
[CATransaction begin];
CATransition* animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.duration = 0.6;
[animation setTimingFunction:[CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionDefault]];
[self.contentView.layer addAnimation:animation forKey:@"deselectRow"];
[CATransaction commit];
}
}
- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -area.size.height);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
CGContextSetAlpha(ctx, alpha);
CGContextDrawImage(ctx, area, image.CGImage);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end

View file

@ -75,6 +75,7 @@
MoveSiteViewController *moveSiteViewController;
TrainerViewController *trainerViewController;
OriginalStoryViewController *originalStoryViewController;
UINavigationController *originalStoryViewNavController;
UserProfileViewController *userProfileViewController;
IASKAppSettingsViewController *preferencesViewController;
@ -155,6 +156,7 @@
@property (nonatomic) UINavigationController *shareNavigationController;
@property (nonatomic) UINavigationController *trainNavigationController;
@property (nonatomic) UINavigationController *userProfileNavigationController;
@property (nonatomic) UINavigationController *originalStoryViewNavController;
@property (nonatomic) IBOutlet NBContainerViewController *masterContainerViewController;
@property (nonatomic) IBOutlet DashboardViewController *dashboardViewController;
@property (nonatomic) IBOutlet NewsBlurViewController *feedsViewController;
@ -258,7 +260,7 @@
- (void)setupReachability;
// social
- (NSDictionary *)getUser:(int)userId;
- (NSDictionary *)getUser:(NSInteger)userId;
- (void)showUserProfileModal:(id)sender;
- (void)pushUserProfile;
- (void)hideUserProfileModal;
@ -267,6 +269,7 @@
- (void)showMoveSite;
- (void)openTrainSite;
- (void)openTrainSiteWithFeedLoaded:(BOOL)feedLoaded from:(id)sender;
- (void)openTrainStory:(id)sender;
- (void)loadFeedDetailView;
- (void)loadTryFeedDetailView:(NSString *)feedId withStory:(NSString *)contentId isSocial:(BOOL)social withUser:(NSDictionary *)user showFindingStory:(BOOL)showHUD;
@ -294,17 +297,18 @@
- (void)showConnectToService:(NSString *)serviceName;
- (void)refreshUserProfile:(void(^)())callback;
- (int)indexOfNextUnreadStory;
- (int)locationOfNextUnreadStory;
- (int)indexOfNextStory;
- (int)locationOfNextStory;
- (int)indexOfActiveStory;
- (int)indexOfStoryId:(id)storyId;
- (int)locationOfActiveStory;
- (int)indexFromLocation:(int)location;
- (BOOL)isStoryUnread:(NSDictionary *)story;
- (NSInteger)indexOfNextUnreadStory;
- (NSInteger)locationOfNextUnreadStory;
- (NSInteger)indexOfNextStory;
- (NSInteger)locationOfNextStory;
- (NSInteger)indexOfActiveStory;
- (NSInteger)indexOfStoryId:(id)storyId;
- (NSInteger)locationOfActiveStory;
- (NSInteger)indexFromLocation:(NSInteger)location;
- (void)pushReadStory:(id)storyId;
- (id)popReadStory;
- (int)locationOfStoryId:(id)storyId;
- (NSInteger)locationOfStoryId:(id)storyId;
- (NSString *)activeOrder;
- (NSString *)activeReadFilter;
@ -314,10 +318,10 @@
- (void)addFeedUserProfiles:(NSArray *)activeFeedUserProfilesValue;
- (void)populateDictUnreadCounts;
- (int)unreadCount;
- (int)allUnreadCount;
- (int)unreadCountForFeed:(NSString *)feedId;
- (int)unreadCountForFolder:(NSString *)folderName;
- (NSInteger)unreadCount;
- (NSInteger)allUnreadCount;
- (NSInteger)unreadCountForFeed:(NSString *)feedId;
- (NSInteger)unreadCountForFolder:(NSString *)folderName;
- (UnreadCounts *)splitUnreadCountForFeed:(NSString *)feedId;
- (UnreadCounts *)splitUnreadCountForFolder:(NSString *)folderName;
- (void)markActiveStoryRead;
@ -327,12 +331,19 @@
- (void)markStoryRead:(NSDictionary *)story feed:(NSDictionary *)feed;
- (void)markStoryUnread:(NSString *)storyId feedId:(id)feedId;
- (void)markStoryUnread:(NSDictionary *)story feed:(NSDictionary *)feed;
- (void)markActiveStorySaved:(BOOL)saved;
- (void)markActiveFeedAllRead;
- (void)markActiveFolderAllRead;
- (void)markFeedAllRead:(id)feedId;
- (void)markFeedReadInCache:(NSArray *)feedIds;
- (void)markFeedReadInCache:(NSArray *)feedIds cutoffTimestamp:(NSInteger)cutoff;
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds cutoffTimestamp:(NSInteger)cutoff;
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request;
- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request;
- (void)markStory:story asSaved:(BOOL)saved;
- (void)calculateStoryLocations;
+ (int)computeStoryScore:(NSDictionary *)intelligence;
+ (NSInteger)computeStoryScore:(NSDictionary *)intelligence;
- (NSString *)extractFolderName:(NSString *)folderName;
- (NSString *)extractParentFolderName:(NSString *)folderName;
- (NSDictionary *)getFeed:(NSString *)feedId;
@ -341,15 +352,14 @@
+ (UIView *)makeGradientView:(CGRect)rect startColor:(NSString *)start endColor:(NSString *)end;
- (UIView *)makeFeedTitleGradient:(NSDictionary *)feed withRect:(CGRect)rect;
- (UIView *)makeFeedTitle:(NSDictionary *)feed;
- (UIButton *)makeRightFeedTitle:(NSDictionary *)feed;
- (void)toggleAuthorClassifier:(NSString *)author feedId:(NSString *)feedId;
- (void)toggleTagClassifier:(NSString *)tag feedId:(NSString *)feedId;
- (void)toggleTitleClassifier:(NSString *)title feedId:(NSString *)feedId score:(int)score;
- (void)toggleTitleClassifier:(NSString *)title feedId:(NSString *)feedId score:(NSInteger)score;
- (void)toggleFeedClassifier:(NSString *)feedId;
- (void)requestClassifierResponse:(ASIHTTPRequest *)request withFeed:(NSString *)feedId;
- (int)databaseSchemaVersion:(FMDatabase *)db;
- (NSInteger)databaseSchemaVersion:(FMDatabase *)db;
- (void)createDatabaseConnection;
- (void)setupDatabase:(FMDatabase *)db;
- (void)cancelOfflineQueue;
@ -359,9 +369,11 @@
- (BOOL)isReachabileForOffline;
- (void)storeUserProfiles:(NSArray *)userProfiles;
- (void)queueReadStories:(NSDictionary *)feedsStories;
- (BOOL)dequeueReadStoryHash:(NSString *)storyHash inFeed:(NSString *)storyFeedId;
- (void)flushQueuedReadStories:(BOOL)forceCheck withCallback:(void(^)())callback;
- (void)syncQueuedReadStories:(FMDatabase *)db withStories:(NSDictionary *)hashes withCallback:(void(^)())callback;
- (void)prepareActiveCachedImages:(FMDatabase *)db;
- (void)cleanImageCache;
- (void)deleteAllCachedImages;
@end

File diff suppressed because it is too large Load diff

View file

@ -15,16 +15,20 @@
#import "WEPopoverController.h"
#import "NBNotifier.h"
#import "IASKAppSettingsViewController.h"
#import "MCSwipeTableViewCell.h"
@class NewsBlurAppDelegate;
@interface NewsBlurViewController : BaseViewController
@interface NewsBlurViewController : BaseViewController
<UITableViewDelegate, UITableViewDataSource,
UIAlertViewDelegate, PullToRefreshViewDelegate,
ASIHTTPRequestDelegate, NSCacheDelegate,
WEPopoverControllerDelegate,
UIPopoverControllerDelegate,
IASKSettingsDelegate> {
IASKSettingsDelegate,
MCSwipeTableViewCellDelegate,
UIGestureRecognizerDelegate,
UIActionSheetDelegate> {
NewsBlurAppDelegate *appDelegate;
NSMutableDictionary * activeFeedLocations;
@ -60,6 +64,12 @@ IASKSettingsDelegate> {
@property (nonatomic) IBOutlet UIBarButtonItem * addBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * settingsBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem * activitiesButton;
@property (nonatomic) IBOutlet UIBarButtonItem *userInfoBarButton;
@property (nonatomic) IBOutlet UIBarButtonItem *userAvatarButton;
@property (nonatomic) IBOutlet UILabel *neutralCount;
@property (nonatomic) IBOutlet UILabel *positiveCount;
@property (nonatomic) IBOutlet UILabel *userLabel;
@property (nonatomic) IBOutlet UIImageView *greenIcon;
@property (nonatomic) NSMutableDictionary *activeFeedLocations;
@property (nonatomic) NSMutableDictionary *stillVisibleFeeds;
@property (nonatomic) NSMutableDictionary *visibleFolders;
@ -71,6 +81,7 @@ IASKSettingsDelegate> {
@property (nonatomic) IBOutlet UISegmentedControl * intelligenceControl;
@property (nonatomic, retain) WEPopoverController *popoverController;
@property (nonatomic) NSIndexPath *currentRowAtIndexPath;
@property (nonatomic) NSInteger currentSection;
@property (strong, nonatomic) IBOutlet UIView *noFocusMessage;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *toolbarLeftMargin;
@property (nonatomic, retain) NBNotifier *notifier;
@ -82,9 +93,12 @@ IASKSettingsDelegate> {
- (void)finishLoadingFeedList:(ASIHTTPRequest *)request;
- (void)finishLoadingFeedListWithDict:(NSDictionary *)results;
- (void)finishRefreshingFeedList:(ASIHTTPRequest *)request;
- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation;
- (void)didSelectSectionHeader:(UIButton *)button;
- (IBAction)selectIntelligence;
- (void)markFeedRead:(NSString *)feedId cutoffDays:(NSInteger)days;
- (void)markFeedsRead:(NSArray *)feedIds cutoffDays:(NSInteger)days;
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request;
- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request;
- (void)didCollapseFolder:(UIButton *)button;
- (BOOL)isFeedVisible:(id)feedId;
- (void)changeToAllMode;
@ -102,7 +116,7 @@ IASKSettingsDelegate> {
- (void)refreshFeedList;
- (void)refreshFeedList:(id)feedId;
- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view;
- (void)loadOfflineFeeds;
- (void)loadOfflineFeeds:(BOOL)failed;
- (void)showUserProfile;
- (IBAction)showSettingsPopover:(id)sender;
- (IBAction)showInteractionsPopover:(id)sender;
@ -111,8 +125,8 @@ IASKSettingsDelegate> {
- (IBAction)tapAddSite:(id)sender;
- (void)resetToolbar;
- (void)layoutHeaderCounts:(UIInterfaceOrientation)orientation;
- (void)refreshHeaderCounts;
- (void)refreshHeaderCounts:(UIInterfaceOrientation)orientation;
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController*)sender;
- (void)settingDidChange:(NSNotification*)notification;

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