diff --git a/media/img/originals/Twitter and Facebook buttons.psd b/media/img/originals/Twitter and Facebook buttons.psd new file mode 100644 index 000000000..1c9ea09b3 Binary files /dev/null and b/media/img/originals/Twitter and Facebook buttons.psd differ diff --git a/media/img/originals/social-icons/16px/500px.png b/media/img/originals/social-icons/16px/500px.png new file mode 100644 index 000000000..8082d1e15 Binary files /dev/null and b/media/img/originals/social-icons/16px/500px.png differ diff --git a/media/img/originals/social-icons/16px/AddThis.png b/media/img/originals/social-icons/16px/AddThis.png new file mode 100644 index 000000000..9ac6c6add Binary files /dev/null and b/media/img/originals/social-icons/16px/AddThis.png differ diff --git a/media/img/originals/social-icons/16px/Behance.png b/media/img/originals/social-icons/16px/Behance.png new file mode 100644 index 000000000..173ed0d07 Binary files /dev/null and b/media/img/originals/social-icons/16px/Behance.png differ diff --git a/media/img/originals/social-icons/16px/Blogger.png b/media/img/originals/social-icons/16px/Blogger.png new file mode 100644 index 000000000..16fa222a9 Binary files /dev/null and b/media/img/originals/social-icons/16px/Blogger.png differ diff --git a/media/img/originals/social-icons/16px/Delicious.png b/media/img/originals/social-icons/16px/Delicious.png new file mode 100644 index 000000000..fe09249ee Binary files /dev/null and b/media/img/originals/social-icons/16px/Delicious.png differ diff --git a/media/img/originals/social-icons/16px/DeviantART.png b/media/img/originals/social-icons/16px/DeviantART.png new file mode 100644 index 000000000..2e8646220 Binary files /dev/null and b/media/img/originals/social-icons/16px/DeviantART.png differ diff --git a/media/img/originals/social-icons/16px/Digg.png b/media/img/originals/social-icons/16px/Digg.png new file mode 100644 index 000000000..bb1cc33e6 Binary files /dev/null and b/media/img/originals/social-icons/16px/Digg.png differ diff --git a/media/img/originals/social-icons/16px/Dopplr.png b/media/img/originals/social-icons/16px/Dopplr.png new file mode 100644 index 000000000..d6f38a245 Binary files /dev/null and b/media/img/originals/social-icons/16px/Dopplr.png differ diff --git a/media/img/originals/social-icons/16px/Dribbble.png b/media/img/originals/social-icons/16px/Dribbble.png new file mode 100644 index 000000000..295c360ff Binary files /dev/null and b/media/img/originals/social-icons/16px/Dribbble.png differ diff --git a/media/img/originals/social-icons/16px/Evernote.png b/media/img/originals/social-icons/16px/Evernote.png new file mode 100644 index 000000000..badc5627d Binary files /dev/null and b/media/img/originals/social-icons/16px/Evernote.png differ diff --git a/media/img/originals/social-icons/16px/FaceBook.png b/media/img/originals/social-icons/16px/FaceBook.png new file mode 100644 index 000000000..f2cd777e3 Binary files /dev/null and b/media/img/originals/social-icons/16px/FaceBook.png differ diff --git a/media/img/originals/social-icons/16px/Flickr.png b/media/img/originals/social-icons/16px/Flickr.png new file mode 100644 index 000000000..8a6c50158 Binary files /dev/null and b/media/img/originals/social-icons/16px/Flickr.png differ diff --git a/media/img/originals/social-icons/16px/Forrst.png b/media/img/originals/social-icons/16px/Forrst.png new file mode 100644 index 000000000..fc6143b44 Binary files /dev/null and b/media/img/originals/social-icons/16px/Forrst.png differ diff --git a/media/img/originals/social-icons/16px/GitHub.png b/media/img/originals/social-icons/16px/GitHub.png new file mode 100644 index 000000000..10c748c66 Binary files /dev/null and b/media/img/originals/social-icons/16px/GitHub.png differ diff --git a/media/img/originals/social-icons/16px/Google+.png b/media/img/originals/social-icons/16px/Google+.png new file mode 100644 index 000000000..e4e17b504 Binary files /dev/null and b/media/img/originals/social-icons/16px/Google+.png differ diff --git a/media/img/originals/social-icons/16px/Grooveshark.png b/media/img/originals/social-icons/16px/Grooveshark.png new file mode 100644 index 000000000..3437b8ed8 Binary files /dev/null and b/media/img/originals/social-icons/16px/Grooveshark.png differ diff --git a/media/img/originals/social-icons/16px/Instagram.png b/media/img/originals/social-icons/16px/Instagram.png new file mode 100644 index 000000000..a82fdffb5 Binary files /dev/null and b/media/img/originals/social-icons/16px/Instagram.png differ diff --git a/media/img/originals/social-icons/16px/Lastfm.png b/media/img/originals/social-icons/16px/Lastfm.png new file mode 100644 index 000000000..2e83e53fa Binary files /dev/null and b/media/img/originals/social-icons/16px/Lastfm.png differ diff --git a/media/img/originals/social-icons/16px/LinkedIn.png b/media/img/originals/social-icons/16px/LinkedIn.png new file mode 100644 index 000000000..2baaee6d8 Binary files /dev/null and b/media/img/originals/social-icons/16px/LinkedIn.png differ diff --git a/media/img/originals/social-icons/16px/Mail.png b/media/img/originals/social-icons/16px/Mail.png new file mode 100644 index 000000000..a0c23e82b Binary files /dev/null and b/media/img/originals/social-icons/16px/Mail.png differ diff --git a/media/img/originals/social-icons/16px/MySpace.png b/media/img/originals/social-icons/16px/MySpace.png new file mode 100644 index 000000000..752ffc6a1 Binary files /dev/null and b/media/img/originals/social-icons/16px/MySpace.png differ diff --git a/media/img/originals/social-icons/16px/Path.png b/media/img/originals/social-icons/16px/Path.png new file mode 100644 index 000000000..87ab5f821 Binary files /dev/null and b/media/img/originals/social-icons/16px/Path.png differ diff --git a/media/img/originals/social-icons/16px/PayPal.png b/media/img/originals/social-icons/16px/PayPal.png new file mode 100644 index 000000000..aec80e5bf Binary files /dev/null and b/media/img/originals/social-icons/16px/PayPal.png differ diff --git a/media/img/originals/social-icons/16px/Picasa.png b/media/img/originals/social-icons/16px/Picasa.png new file mode 100644 index 000000000..b045d2764 Binary files /dev/null and b/media/img/originals/social-icons/16px/Picasa.png differ diff --git a/media/img/originals/social-icons/16px/Posterous.png b/media/img/originals/social-icons/16px/Posterous.png new file mode 100644 index 000000000..f42436443 Binary files /dev/null and b/media/img/originals/social-icons/16px/Posterous.png differ diff --git a/media/img/originals/social-icons/16px/RSS.png b/media/img/originals/social-icons/16px/RSS.png new file mode 100644 index 000000000..e3365b0fb Binary files /dev/null and b/media/img/originals/social-icons/16px/RSS.png differ diff --git a/media/img/originals/social-icons/16px/Reddit.png b/media/img/originals/social-icons/16px/Reddit.png new file mode 100644 index 000000000..4620c5f1b Binary files /dev/null and b/media/img/originals/social-icons/16px/Reddit.png differ diff --git a/media/img/originals/social-icons/16px/ShareThis.png b/media/img/originals/social-icons/16px/ShareThis.png new file mode 100644 index 000000000..979743ed5 Binary files /dev/null and b/media/img/originals/social-icons/16px/ShareThis.png differ diff --git a/media/img/originals/social-icons/16px/Skype.png b/media/img/originals/social-icons/16px/Skype.png new file mode 100644 index 000000000..9b4110c34 Binary files /dev/null and b/media/img/originals/social-icons/16px/Skype.png differ diff --git a/media/img/originals/social-icons/16px/SoundCloud.png b/media/img/originals/social-icons/16px/SoundCloud.png new file mode 100644 index 000000000..ac6a69aa6 Binary files /dev/null and b/media/img/originals/social-icons/16px/SoundCloud.png differ diff --git a/media/img/originals/social-icons/16px/Spotify.png b/media/img/originals/social-icons/16px/Spotify.png new file mode 100644 index 000000000..6e9377a52 Binary files /dev/null and b/media/img/originals/social-icons/16px/Spotify.png differ diff --git a/media/img/originals/social-icons/16px/Stumbleupon.png b/media/img/originals/social-icons/16px/Stumbleupon.png new file mode 100644 index 000000000..48ed1e1f7 Binary files /dev/null and b/media/img/originals/social-icons/16px/Stumbleupon.png differ diff --git a/media/img/originals/social-icons/16px/Tumblr.png b/media/img/originals/social-icons/16px/Tumblr.png new file mode 100644 index 000000000..00c426063 Binary files /dev/null and b/media/img/originals/social-icons/16px/Tumblr.png differ diff --git a/media/img/originals/social-icons/16px/Twitter.png b/media/img/originals/social-icons/16px/Twitter.png new file mode 100644 index 000000000..5854b1bdd Binary files /dev/null and b/media/img/originals/social-icons/16px/Twitter.png differ diff --git a/media/img/originals/social-icons/16px/Viddler.png b/media/img/originals/social-icons/16px/Viddler.png new file mode 100644 index 000000000..df1a31661 Binary files /dev/null and b/media/img/originals/social-icons/16px/Viddler.png differ diff --git a/media/img/originals/social-icons/16px/Vimeo.png b/media/img/originals/social-icons/16px/Vimeo.png new file mode 100644 index 000000000..b51c4b266 Binary files /dev/null and b/media/img/originals/social-icons/16px/Vimeo.png differ diff --git a/media/img/originals/social-icons/16px/Virb.png b/media/img/originals/social-icons/16px/Virb.png new file mode 100644 index 000000000..50cbef35b Binary files /dev/null and b/media/img/originals/social-icons/16px/Virb.png differ diff --git a/media/img/originals/social-icons/16px/Windows.png b/media/img/originals/social-icons/16px/Windows.png new file mode 100644 index 000000000..c958e1781 Binary files /dev/null and b/media/img/originals/social-icons/16px/Windows.png differ diff --git a/media/img/originals/social-icons/16px/WordPress.png b/media/img/originals/social-icons/16px/WordPress.png new file mode 100644 index 000000000..31c91ac1f Binary files /dev/null and b/media/img/originals/social-icons/16px/WordPress.png differ diff --git a/media/img/originals/social-icons/16px/Youtube.png b/media/img/originals/social-icons/16px/Youtube.png new file mode 100644 index 000000000..208a99e50 Binary files /dev/null and b/media/img/originals/social-icons/16px/Youtube.png differ diff --git a/media/img/originals/social-icons/16px/Zerply.png b/media/img/originals/social-icons/16px/Zerply.png new file mode 100644 index 000000000..54ef1151f Binary files /dev/null and b/media/img/originals/social-icons/16px/Zerply.png differ diff --git a/media/img/originals/social-icons/32px/500px.png b/media/img/originals/social-icons/32px/500px.png new file mode 100644 index 000000000..b0b56519a Binary files /dev/null and b/media/img/originals/social-icons/32px/500px.png differ diff --git a/media/img/originals/social-icons/32px/AddThis.png b/media/img/originals/social-icons/32px/AddThis.png new file mode 100644 index 000000000..b4908db6e Binary files /dev/null and b/media/img/originals/social-icons/32px/AddThis.png differ diff --git a/media/img/originals/social-icons/32px/Behance.png b/media/img/originals/social-icons/32px/Behance.png new file mode 100644 index 000000000..601d25e3e Binary files /dev/null and b/media/img/originals/social-icons/32px/Behance.png differ diff --git a/media/img/originals/social-icons/32px/Blogger.png b/media/img/originals/social-icons/32px/Blogger.png new file mode 100644 index 000000000..3ac734311 Binary files /dev/null and b/media/img/originals/social-icons/32px/Blogger.png differ diff --git a/media/img/originals/social-icons/32px/Deliciou.png b/media/img/originals/social-icons/32px/Deliciou.png new file mode 100644 index 000000000..ac1d8a337 Binary files /dev/null and b/media/img/originals/social-icons/32px/Deliciou.png differ diff --git a/media/img/originals/social-icons/32px/DeviantART.png b/media/img/originals/social-icons/32px/DeviantART.png new file mode 100644 index 000000000..db6961576 Binary files /dev/null and b/media/img/originals/social-icons/32px/DeviantART.png differ diff --git a/media/img/originals/social-icons/32px/Digg.png b/media/img/originals/social-icons/32px/Digg.png new file mode 100644 index 000000000..b4943ba66 Binary files /dev/null and b/media/img/originals/social-icons/32px/Digg.png differ diff --git a/media/img/originals/social-icons/32px/Dopplr.png b/media/img/originals/social-icons/32px/Dopplr.png new file mode 100644 index 000000000..af86614dd Binary files /dev/null and b/media/img/originals/social-icons/32px/Dopplr.png differ diff --git a/media/img/originals/social-icons/32px/Dribbble.png b/media/img/originals/social-icons/32px/Dribbble.png new file mode 100644 index 000000000..961de0b04 Binary files /dev/null and b/media/img/originals/social-icons/32px/Dribbble.png differ diff --git a/media/img/originals/social-icons/32px/Evernote.png b/media/img/originals/social-icons/32px/Evernote.png new file mode 100644 index 000000000..562303b5f Binary files /dev/null and b/media/img/originals/social-icons/32px/Evernote.png differ diff --git a/media/img/originals/social-icons/32px/Facebook.png b/media/img/originals/social-icons/32px/Facebook.png new file mode 100644 index 000000000..e012b82f3 Binary files /dev/null and b/media/img/originals/social-icons/32px/Facebook.png differ diff --git a/media/img/originals/social-icons/32px/Flickr.png b/media/img/originals/social-icons/32px/Flickr.png new file mode 100644 index 000000000..405968b0b Binary files /dev/null and b/media/img/originals/social-icons/32px/Flickr.png differ diff --git a/media/img/originals/social-icons/32px/Forrst.png b/media/img/originals/social-icons/32px/Forrst.png new file mode 100644 index 000000000..d9dab97ee Binary files /dev/null and b/media/img/originals/social-icons/32px/Forrst.png differ diff --git a/media/img/originals/social-icons/32px/GitHub.png b/media/img/originals/social-icons/32px/GitHub.png new file mode 100644 index 000000000..560ea6eea Binary files /dev/null and b/media/img/originals/social-icons/32px/GitHub.png differ diff --git a/media/img/originals/social-icons/32px/Google+.png b/media/img/originals/social-icons/32px/Google+.png new file mode 100644 index 000000000..77c895632 Binary files /dev/null and b/media/img/originals/social-icons/32px/Google+.png differ diff --git a/media/img/originals/social-icons/32px/Grooveshark.png b/media/img/originals/social-icons/32px/Grooveshark.png new file mode 100644 index 000000000..074d8dd88 Binary files /dev/null and b/media/img/originals/social-icons/32px/Grooveshark.png differ diff --git a/media/img/originals/social-icons/32px/Instagram.png b/media/img/originals/social-icons/32px/Instagram.png new file mode 100644 index 000000000..384290595 Binary files /dev/null and b/media/img/originals/social-icons/32px/Instagram.png differ diff --git a/media/img/originals/social-icons/32px/Lastfm.png b/media/img/originals/social-icons/32px/Lastfm.png new file mode 100644 index 000000000..343191250 Binary files /dev/null and b/media/img/originals/social-icons/32px/Lastfm.png differ diff --git a/media/img/originals/social-icons/32px/LinkedIn.png b/media/img/originals/social-icons/32px/LinkedIn.png new file mode 100644 index 000000000..499d64d0d Binary files /dev/null and b/media/img/originals/social-icons/32px/LinkedIn.png differ diff --git a/media/img/originals/social-icons/32px/Mail.png b/media/img/originals/social-icons/32px/Mail.png new file mode 100644 index 000000000..e3c3f4846 Binary files /dev/null and b/media/img/originals/social-icons/32px/Mail.png differ diff --git a/media/img/originals/social-icons/32px/MySpace.png b/media/img/originals/social-icons/32px/MySpace.png new file mode 100644 index 000000000..875dc974e Binary files /dev/null and b/media/img/originals/social-icons/32px/MySpace.png differ diff --git a/media/img/originals/social-icons/32px/Path.png b/media/img/originals/social-icons/32px/Path.png new file mode 100644 index 000000000..0787bea1a Binary files /dev/null and b/media/img/originals/social-icons/32px/Path.png differ diff --git a/media/img/originals/social-icons/32px/Paypal.png b/media/img/originals/social-icons/32px/Paypal.png new file mode 100644 index 000000000..b6ef1c260 Binary files /dev/null and b/media/img/originals/social-icons/32px/Paypal.png differ diff --git a/media/img/originals/social-icons/32px/Picasa.png b/media/img/originals/social-icons/32px/Picasa.png new file mode 100644 index 000000000..2a68ce02f Binary files /dev/null and b/media/img/originals/social-icons/32px/Picasa.png differ diff --git a/media/img/originals/social-icons/32px/Posterous.png b/media/img/originals/social-icons/32px/Posterous.png new file mode 100644 index 000000000..c769b7e85 Binary files /dev/null and b/media/img/originals/social-icons/32px/Posterous.png differ diff --git a/media/img/originals/social-icons/32px/RSS.png b/media/img/originals/social-icons/32px/RSS.png new file mode 100644 index 000000000..ca00b9364 Binary files /dev/null and b/media/img/originals/social-icons/32px/RSS.png differ diff --git a/media/img/originals/social-icons/32px/Reddit.png b/media/img/originals/social-icons/32px/Reddit.png new file mode 100644 index 000000000..dfbd24670 Binary files /dev/null and b/media/img/originals/social-icons/32px/Reddit.png differ diff --git a/media/img/originals/social-icons/32px/ShareThis.png b/media/img/originals/social-icons/32px/ShareThis.png new file mode 100644 index 000000000..1e23f1b3a Binary files /dev/null and b/media/img/originals/social-icons/32px/ShareThis.png differ diff --git a/media/img/originals/social-icons/32px/Skype.png b/media/img/originals/social-icons/32px/Skype.png new file mode 100644 index 000000000..a4b8ede7f Binary files /dev/null and b/media/img/originals/social-icons/32px/Skype.png differ diff --git a/media/img/originals/social-icons/32px/Soundcloud.png b/media/img/originals/social-icons/32px/Soundcloud.png new file mode 100644 index 000000000..5c7f178a0 Binary files /dev/null and b/media/img/originals/social-icons/32px/Soundcloud.png differ diff --git a/media/img/originals/social-icons/32px/Spotify.png b/media/img/originals/social-icons/32px/Spotify.png new file mode 100644 index 000000000..8870cf552 Binary files /dev/null and b/media/img/originals/social-icons/32px/Spotify.png differ diff --git a/media/img/originals/social-icons/32px/StumbleUpon.png b/media/img/originals/social-icons/32px/StumbleUpon.png new file mode 100644 index 000000000..099ca009b Binary files /dev/null and b/media/img/originals/social-icons/32px/StumbleUpon.png differ diff --git a/media/img/originals/social-icons/32px/Tumblr.png b/media/img/originals/social-icons/32px/Tumblr.png new file mode 100644 index 000000000..bc35d5a27 Binary files /dev/null and b/media/img/originals/social-icons/32px/Tumblr.png differ diff --git a/media/img/originals/social-icons/32px/Twitter.png b/media/img/originals/social-icons/32px/Twitter.png new file mode 100644 index 000000000..2b2335b14 Binary files /dev/null and b/media/img/originals/social-icons/32px/Twitter.png differ diff --git a/media/img/originals/social-icons/32px/Twitter.psd b/media/img/originals/social-icons/32px/Twitter.psd new file mode 100644 index 000000000..4b46b42d3 Binary files /dev/null and b/media/img/originals/social-icons/32px/Twitter.psd differ diff --git a/media/img/originals/social-icons/32px/Twitter_Off.psd b/media/img/originals/social-icons/32px/Twitter_Off.psd new file mode 100644 index 000000000..8a7bfa528 Binary files /dev/null and b/media/img/originals/social-icons/32px/Twitter_Off.psd differ diff --git a/media/img/originals/social-icons/32px/Viddler.png b/media/img/originals/social-icons/32px/Viddler.png new file mode 100644 index 000000000..9a68867cf Binary files /dev/null and b/media/img/originals/social-icons/32px/Viddler.png differ diff --git a/media/img/originals/social-icons/32px/Vimeo.png b/media/img/originals/social-icons/32px/Vimeo.png new file mode 100644 index 000000000..57fe99f94 Binary files /dev/null and b/media/img/originals/social-icons/32px/Vimeo.png differ diff --git a/media/img/originals/social-icons/32px/Virb.png b/media/img/originals/social-icons/32px/Virb.png new file mode 100644 index 000000000..02f5e5647 Binary files /dev/null and b/media/img/originals/social-icons/32px/Virb.png differ diff --git a/media/img/originals/social-icons/32px/Windows.png b/media/img/originals/social-icons/32px/Windows.png new file mode 100644 index 000000000..42027066f Binary files /dev/null and b/media/img/originals/social-icons/32px/Windows.png differ diff --git a/media/img/originals/social-icons/32px/WordPress.png b/media/img/originals/social-icons/32px/WordPress.png new file mode 100644 index 000000000..9b72d371a Binary files /dev/null and b/media/img/originals/social-icons/32px/WordPress.png differ diff --git a/media/img/originals/social-icons/32px/YouTube.png b/media/img/originals/social-icons/32px/YouTube.png new file mode 100644 index 000000000..d6ea08def Binary files /dev/null and b/media/img/originals/social-icons/32px/YouTube.png differ diff --git a/media/img/originals/social-icons/32px/Zerply.png b/media/img/originals/social-icons/32px/Zerply.png new file mode 100644 index 000000000..04f93f7e7 Binary files /dev/null and b/media/img/originals/social-icons/32px/Zerply.png differ diff --git a/media/img/originals/social-icons/preview.jpg b/media/img/originals/social-icons/preview.jpg new file mode 100644 index 000000000..96eaf6ad9 Binary files /dev/null and b/media/img/originals/social-icons/preview.jpg differ diff --git a/media/img/originals/social-icons/readme.txt b/media/img/originals/social-icons/readme.txt new file mode 100644 index 000000000..158d89b37 --- /dev/null +++ b/media/img/originals/social-icons/readme.txt @@ -0,0 +1,19 @@ + +------------------------------------------------------------ +This resource has been created by Orman Clark for PremiumPixels.com +------------------------------------------------------------ + +TERMS OF USE: + +All resources made available on Premium Pixels, including but not limited to, icons, images, brushes, shapes, layer styles, layered PSDs, patterns, textures, web elements and themes are free for use in both personal and commercial projects. + +You may freely use our resources, without restriction, in software programs, web templates and other materials intended for sale or distribution. No attribution or backlinks are required, but any form of spreading the word is always appreciated! + +You are not permitted to make the resources found on Premium Pixels available for distribution elsewhere as is without prior consent. + + +Orman Clark for PremiumPixels.com +_________________________ +www.premiumpixels.com +www.ormanclark.com + diff --git a/media/ios/AFNetworking/AFHTTPClient.h b/media/ios/AFNetworking/AFHTTPClient.h new file mode 100644 index 000000000..6d11918f1 --- /dev/null +++ b/media/ios/AFNetworking/AFHTTPClient.h @@ -0,0 +1,516 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@class AFHTTPRequestOperation; +@protocol AFHTTPClientOperation; +@protocol AFMultipartFormData; + +/** + 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 ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +extern NSString * const AFNetworkingReachabilityDidChangeNotification; +#endif + +/** + Specifies network reachability of the client to its `baseURL` domain. + */ +#ifdef _SYSTEMCONFIGURATION_H +typedef enum { + AFNetworkReachabilityStatusUnknown = -1, + AFNetworkReachabilityStatusNotReachable = 0, + AFNetworkReachabilityStatusReachableViaWWAN = 1, + AFNetworkReachabilityStatusReachableViaWiFi = 2, +} AFNetworkReachabilityStatus; +#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); + +/** + 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 + +///--------------------------------------- +/// @name Accessing HTTP Client Properties +///--------------------------------------- + +/** + The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure` + */ +@property (readonly, nonatomic, retain) NSURL *baseURL; + +/** + The string encoding used in constructing url requests. This is `NSUTF8StringEncoding` by default. + */ +@property (nonatomic, assign) NSStringEncoding stringEncoding; + +/** + 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. + */ +@property (nonatomic, assign) AFHTTPClientParameterEncoding parameterEncoding; + +/** + The operation queue which manages operations enqueued by the HTTP client. + */ +@property (readonly, nonatomic, retain) 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 ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; +#endif + +///--------------------------------------------- +/// @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. + + @return The newly-initialized HTTP client + */ ++ (AFHTTPClient *)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. + + @return The newly-initialized HTTP client + */ +- (id)initWithBaseURL:(NSURL *)url; + +///----------------------------------- +/// @name Managing Reachability Status +///----------------------------------- + +/** + 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 ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; +#endif + +///------------------------------- +/// @name Managing HTTP Operations +///------------------------------- + +/** + Attempts to register a subclass of `AFHTTPRequestOperation`, adding it to a chain to automatically generate request operations from a URL request. + + @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` + */ +- (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. + */ +- (void)unregisterHTTPOperationClass:(Class)operationClass; + +///---------------------------------- +/// @name Managing HTTP Header Values +///---------------------------------- + +/** + 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; + +/** + 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; + +/** + 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 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. + @param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body. + + @return An `NSMutableURLRequest` object + */ +- (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 + + @param method The HTTP method for the request. Must be either `POST`, `PUT`, or `DELETE`. + @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 + path:(NSString *)path + parameters:(NSDictionary *)parameters + constructingBodyWithBlock:(void (^)(id formData))block; + +///------------------------------- +/// @name Creating HTTP Operations +///------------------------------- + +/** + 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 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. + */ +- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +///---------------------------------------- +/// @name Managing Enqueued HTTP Operations +///---------------------------------------- + +/** + Enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue. + + @param operation The HTTP request operation to be enqueued. + */ +- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation; + +/** + 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. + */ +- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path; + +///--------------------------------------- +/// @name Batching HTTP Request Operations +///--------------------------------------- + +/** + 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. + + @param requests 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. + */ +- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, 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. + */ +- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock; + +///--------------------------- +/// @name Making HTTP Requests +///--------------------------- + +/** + 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 + */ +- (void)getPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +/** + 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 + */ +- (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 + */ +- (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 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 + */ +- (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 + */ +- (void)patchPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; +@end + +#pragma mark - + +/** + The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`. + + @see `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 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)appendPartWithFormData:(NSData *)data name:(NSString *)name; + +/** + 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 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`. + + @discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively. + */ +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error; + +/** + Appends encoded data to the form data. + + @param data The data to be encoded and appended to the form data. + */ +- (void)appendData:(NSData *)data; + +/** + Appends a string to the form data. + + @param string The string to be encoded and appended to the form data. + */ +- (void)appendString:(NSString *)string; + +@end + diff --git a/media/ios/AFNetworking/AFHTTPClient.m b/media/ios/AFNetworking/AFHTTPClient.m new file mode 100644 index 000000000..b116f3c36 --- /dev/null +++ b/media/ios/AFNetworking/AFHTTPClient.m @@ -0,0 +1,856 @@ +// AFHTTPClient.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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "AFHTTPClient.h" +#import "AFHTTPRequestOperation.h" +#import "AFJSONUtilities.h" + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import +#endif + +#ifdef _SYSTEMCONFIGURATION_H +#import +#import +#import +#import +#import +#import +#endif + +NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; + +@interface AFMultipartFormData : NSObject + +- (id)initWithURLRequest:(NSMutableURLRequest *)request + stringEncoding:(NSStringEncoding)encoding; + +- (NSMutableURLRequest *)requestByFinalizingMultipartFormData; + +@end + +#pragma mark - + +#ifdef _SYSTEMCONFIGURATION_H +typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; +typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); +#else +typedef id AFNetworkReachabilityRef; +#endif + +typedef void (^AFCompletionBlock)(void); + +static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4; + +static NSString * AFBase64EncodedStringFromString(NSString *string) { + NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + NSUInteger length = [data length]; + NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; + + uint8_t *input = (uint8_t *)[data bytes]; + uint8_t *output = (uint8_t *)[mutableData mutableBytes]; + + for (NSUInteger i = 0; i < length; i += 3) { + NSUInteger value = 0; + for (NSUInteger j = i; j < (i + 3); j++) { + value <<= 8; + if (j < length) { + value |= (0xFF & input[j]); + } + } + + static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + NSUInteger idx = (i / 3) * 4; + output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; + output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; + output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; + output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; + } + + return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease]; +} + +NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { + static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; + + return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease]; +} + +#pragma mark - + +@interface AFQueryStringComponent : NSObject { +@private + NSString *_key; + NSString *_value; +} + +@property (readwrite, nonatomic, retain) id key; +@property (readwrite, nonatomic, retain) id value; + +- (id)initWithKey:(id)key value:(id)value; +- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding; + +@end + +@implementation AFQueryStringComponent +@synthesize key = _key; +@synthesize value = _value; + +- (id)initWithKey:(id)key value:(id)value { + self = [super init]; + if (!self) { + return nil; + } + + self.key = key; + self.value = value; + + return self; +} + +- (void)dealloc { + [_key release]; + [_value release]; + [super dealloc]; +} + +- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { + return [NSString stringWithFormat:@"%@=%@", self.key, AFURLEncodedStringFromStringWithEncoding([self.value description], stringEncoding)]; +} + +@end + +#pragma mark - + +extern NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value); +extern NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value); +extern NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value); + +NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) { + NSMutableArray *mutableComponents = [NSMutableArray array]; + for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { + [mutableComponents addObject:[component URLEncodedStringValueWithEncoding:stringEncoding]]; + } + + return [mutableComponents componentsJoinedByString:@"&"]; +} + +NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value) { + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + if([value isKindOfClass:[NSDictionary class]]) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndDictionaryValue(key, value)]; + } else if([value isKindOfClass:[NSArray class]]) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndArrayValue(key, value)]; + } else { + [mutableQueryStringComponents addObject:[[[AFQueryStringComponent alloc] initWithKey:key value:value] autorelease]]; + } + + return mutableQueryStringComponents; +} + +NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value){ + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + }]; + + return mutableQueryStringComponents; +} + +NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value) { + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + + [value enumerateObjectsUsingBlock:^(id nestedValue, NSUInteger idx, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; + }]; + + return mutableQueryStringComponents; +} + +static NSString * AFJSONStringFromParameters(NSDictionary *parameters) { + NSError *error = nil; + NSData *JSONData = AFJSONEncode(parameters, &error); + + if (!error) { + return [[[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding] autorelease]; + } else { + return nil; + } +} + +static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { + NSString *propertyListString = nil; + NSError *error = nil; + + NSData *propertyListData = [NSPropertyListSerialization dataWithPropertyList:parameters format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; + if (!error) { + propertyListString = [[[NSString alloc] initWithData:propertyListData encoding:NSUTF8StringEncoding] autorelease]; + } + + return propertyListString; +} + +@interface AFHTTPClient () +@property (readwrite, nonatomic, retain) NSURL *baseURL; +@property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames; +@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders; +@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue; +#ifdef _SYSTEMCONFIGURATION_H +@property (readwrite, nonatomic, assign) AFNetworkReachabilityRef networkReachability; +@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; +@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; +#endif + +#ifdef _SYSTEMCONFIGURATION_H +- (void)startMonitoringNetworkReachability; +- (void)stopMonitoringNetworkReachability; +#endif +@end + +@implementation AFHTTPClient +@synthesize baseURL = _baseURL; +@synthesize stringEncoding = _stringEncoding; +@synthesize parameterEncoding = _parameterEncoding; +@synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames; +@synthesize defaultHeaders = _defaultHeaders; +@synthesize operationQueue = _operationQueue; +#ifdef _SYSTEMCONFIGURATION_H +@synthesize networkReachability = _networkReachability; +@synthesize networkReachabilityStatus = _networkReachabilityStatus; +@synthesize networkReachabilityStatusBlock = _networkReachabilityStatusBlock; +#endif + ++ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url { + return [[[self alloc] initWithBaseURL:url] autorelease]; +} + +- (id)initWithBaseURL:(NSURL *)url { + self = [super init]; + if (!self) { + return nil; + } + + self.baseURL = url; + + self.stringEncoding = NSUTF8StringEncoding; + self.parameterEncoding = AFFormURLParameterEncoding; + + self.registeredHTTPOperationClassNames = [NSMutableArray array]; + + self.defaultHeaders = [NSMutableDictionary dictionary]; + + // Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 + [self setDefaultHeader:@"Accept-Encoding" value:@"gzip"]; + + // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 + NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "]; + [self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]]; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]]; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown"]]; +#endif + +#ifdef _SYSTEMCONFIGURATION_H + self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; + [self startMonitoringNetworkReachability]; +#endif + + self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; + [self.operationQueue setMaxConcurrentOperationCount:kAFHTTPClientDefaultMaxConcurrentOperationCount]; + + return self; +} + +- (void)dealloc { +#ifdef _SYSTEMCONFIGURATION_H + [self stopMonitoringNetworkReachability]; + [_networkReachabilityStatusBlock release]; +#endif + + [_baseURL release]; + [_registeredHTTPOperationClassNames release]; + [_defaultHeaders release]; + [_operationQueue release]; + + [super dealloc]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, defaultHeaders: %@, registeredOperationClasses: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.defaultHeaders, self.registeredHTTPOperationClassNames, self.operationQueue]; +} + +#pragma mark - + +#ifdef _SYSTEMCONFIGURATION_H +static BOOL AFURLHostIsIPAddress(NSURL *url) { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + + return [url host] && (inet_pton(AF_INET, [[url host] UTF8String], &sa_in) == 1 || inet_pton(AF_INET6, [[url host] UTF8String], &sa_in6) == 1); +} + +static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { + BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); + BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); + BOOL isNetworkReachable = (isReachable && !needsConnection); + + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; + if(isNetworkReachable == NO){ + status = AFNetworkReachabilityStatusNotReachable; + } +#if TARGET_OS_IPHONE + else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFNetworkReachabilityStatusReachableViaWWAN; + } +#endif + else { + status = AFNetworkReachabilityStatusReachableViaWiFi; + } + + return status; +} + +static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; + if (block) { + block(status); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; +} + +static const void * AFNetworkReachabilityRetainCallback(const void *info) { + return [(AFNetworkReachabilityStatusBlock)info copy]; +} + +static void AFNetworkReachabilityReleaseCallback(const void *info) { + [(AFNetworkReachabilityStatusBlock)info release]; +} + +- (void)startMonitoringNetworkReachability { + [self stopMonitoringNetworkReachability]; + + if (!self.baseURL) { + return; + } + + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); + + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (self.networkReachabilityStatusBlock) { + self.networkReachabilityStatusBlock(status); + } + }; + + SCNetworkReachabilityContext context = {0, callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; + SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + + /* Network reachability monitoring does not establish a baseline for IP addresses as it does for hostnames, so if the base URL host is an IP address, the initial reachability callback is manually triggered. + */ + if (AFURLHostIsIPAddress(self.baseURL)) { + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(self.networkReachability, &flags); + dispatch_async(dispatch_get_main_queue(), ^{ + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + callback(status); + }); + } +} + +- (void)stopMonitoringNetworkReachability { + if (_networkReachability) { + SCNetworkReachabilityUnscheduleFromRunLoop(_networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + CFRelease(_networkReachability); + } +} + +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { + self.networkReachabilityStatusBlock = block; +} +#endif + +#pragma mark - + +- (BOOL)registerHTTPOperationClass:(Class)operationClass { + if (![operationClass isSubclassOfClass:[AFHTTPRequestOperation class]]) { + return NO; + } + + NSString *className = NSStringFromClass(operationClass); + [self.registeredHTTPOperationClassNames removeObject:className]; + [self.registeredHTTPOperationClassNames insertObject:className atIndex:0]; + + return YES; +} + +- (void)unregisterHTTPOperationClass:(Class)operationClass { + NSString *className = NSStringFromClass(operationClass); + [self.registeredHTTPOperationClassNames removeObject:className]; +} + +#pragma mark - + +- (NSString *)defaultValueForHeader:(NSString *)header { + return [self.defaultHeaders valueForKey:header]; +} + +- (void)setDefaultHeader:(NSString *)header value:(NSString *)value { + [self.defaultHeaders setValue:value forKey:header]; +} + +- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password { + NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password]; + [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)]]; +} + +- (void)setAuthorizationHeaderWithToken:(NSString *)token { + [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]]; +} + +- (void)clearAuthorizationHeader { + [self.defaultHeaders removeObjectForKey:@"Authorization"]; +} + +#pragma mark - + +- (NSMutableURLRequest *)requestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters +{ + NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url] autorelease]; + [request setHTTPMethod:method]; + [request setAllHTTPHeaderFields:self.defaultHeaders]; + + if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"]) { + [request setHTTPShouldUsePipelining:YES]; + } + + if (parameters) { + if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"] || [method isEqualToString:@"DELETE"]) { + url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding)]]; + [request setURL:url]; + } else { + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding)); + switch (self.parameterEncoding) { + case AFFormURLParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding) dataUsingEncoding:self.stringEncoding]]; + break; + case AFJSONParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/json; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFJSONStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]]; + break; + case AFPropertyListParameterEncoding:; + [request setValue:[NSString stringWithFormat:@"application/x-plist; charset=%@", charset] forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:[AFPropertyListStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]]; + break; + } + } + } + + return request; +} + +- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters + constructingBodyWithBlock:(void (^)(id formData))block +{ + NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil]; + __block AFMultipartFormData *formData = [[[AFMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding] autorelease]; + + if (parameters) { + for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { + NSData *data = nil; + if ([component.value isKindOfClass:[NSData class]]) { + data = component.value; + } else { + data = [[component.value description] dataUsingEncoding:self.stringEncoding]; + } + + if (data) { + [formData appendPartWithFormData:data name:[component.key description]]; + } + } + } + + if (block) { + block(formData); + } + + return [formData requestByFinalizingMultipartFormData]; +} + +- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + AFHTTPRequestOperation *operation = nil; + NSString *className = nil; + NSEnumerator *enumerator = [self.registeredHTTPOperationClassNames reverseObjectEnumerator]; + while (!operation && (className = [enumerator nextObject])) { + Class op_class = NSClassFromString(className); + if (op_class && [op_class canProcessRequest:urlRequest]) { + operation = [[(AFHTTPRequestOperation *)[op_class alloc] initWithRequest:urlRequest] autorelease]; + } + } + + if (!operation) { + operation = [[[AFHTTPRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + } + + [operation setCompletionBlockWithSuccess:success failure:failure]; + + return operation; +} + +#pragma mark - + +- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation { + [self.operationQueue addOperation:operation]; +} + +- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path { + for (NSOperation *operation in [self.operationQueue operations]) { + if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) { + continue; + } + + if ((!method || [method isEqualToString:[[(AFHTTPRequestOperation *)operation request] HTTPMethod]]) && [path isEqualToString:[[[(AFHTTPRequestOperation *)operation request] URL] path]]) { + [operation cancel]; + } + } +} + +- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock +{ + NSMutableArray *mutableOperations = [NSMutableArray array]; + for (NSURLRequest *request in requests) { + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:nil failure:nil]; + [mutableOperations addObject:operation]; + } + + [self enqueueBatchOfHTTPRequestOperations:mutableOperations progressBlock:progressBlock completionBlock:completionBlock]; +} + +- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations + progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + completionBlock:(void (^)(NSArray *operations))completionBlock +{ + __block dispatch_group_t dispatchGroup = dispatch_group_create(); + NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{ + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if (completionBlock) { + completionBlock(operations); + } + }); + dispatch_release(dispatchGroup); + }]; + + NSPredicate *finishedOperationPredicate = [NSPredicate predicateWithFormat:@"isFinished == YES"]; + + for (AFHTTPRequestOperation *operation in operations) { + AFCompletionBlock originalCompletionBlock = [[operation.completionBlock copy] autorelease]; + operation.completionBlock = ^{ + dispatch_queue_t queue = operation.successCallbackQueue ? operation.successCallbackQueue : dispatch_get_main_queue(); + dispatch_group_async(dispatchGroup, queue, ^{ + if (originalCompletionBlock) { + originalCompletionBlock(); + } + + if (progressBlock) { + progressBlock([[operations filteredArrayUsingPredicate:finishedOperationPredicate] count], [operations count]); + } + + dispatch_group_leave(dispatchGroup); + }); + }; + + dispatch_group_enter(dispatchGroup); + [batchedOperation addDependency:operation]; + + [self enqueueHTTPRequestOperation:operation]; + } + [self.operationQueue addOperation:batchedOperation]; +} + +#pragma mark - + +- (void)getPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)postPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)putPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)deletePath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +- (void)patchPath:(NSString *)path + parameters:(NSDictionary *)parameters + success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + NSURLRequest *request = [self requestWithMethod:@"PATCH" path:path parameters:parameters]; + AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; + [self enqueueHTTPRequestOperation:operation]; +} + +@end + +#pragma mark - + +static NSString * const kAFMultipartTemporaryFileDirectoryName = @"com.alamofire.uploads"; + +static NSString * AFMultipartTemporaryFileDirectoryPath() { + static NSString *multipartTemporaryFilePath = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + multipartTemporaryFilePath = [[NSTemporaryDirectory() stringByAppendingPathComponent:kAFMultipartTemporaryFileDirectoryName] copy]; + + NSError *error = nil; + if(![[NSFileManager defaultManager] createDirectoryAtPath:multipartTemporaryFilePath withIntermediateDirectories:YES attributes:nil error:&error]) { + NSLog(@"Failed to create multipary temporary file directory at %@", multipartTemporaryFilePath); + } + }); + + return multipartTemporaryFilePath; +} + +static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY"; + +static NSString * const kAFMultipartFormCRLF = @"\r\n"; + +static inline NSString * AFMultipartFormInitialBoundary() { + return [NSString stringWithFormat:@"--%@%@", kAFMultipartFormBoundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormEncapsulationBoundary() { + return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, kAFMultipartFormBoundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormFinalBoundary() { + return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, kAFMultipartFormBoundary, kAFMultipartFormCRLF]; +} + +@interface AFMultipartFormData () +@property (readwrite, nonatomic, retain) NSMutableURLRequest *request; +@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; +@property (readwrite, nonatomic, retain) NSOutputStream *outputStream; +@property (readwrite, nonatomic, copy) NSString *temporaryFilePath; +@end + +@implementation AFMultipartFormData +@synthesize request = _request; +@synthesize stringEncoding = _stringEncoding; +@synthesize outputStream = _outputStream; +@synthesize temporaryFilePath = _temporaryFilePath; + +- (id)initWithURLRequest:(NSMutableURLRequest *)request + stringEncoding:(NSStringEncoding)encoding +{ + self = [super init]; + if (!self) { + return nil; + } + + self.request = request; + self.stringEncoding = encoding; + + self.temporaryFilePath = [AFMultipartTemporaryFileDirectoryPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", [[self.request URL] hash]]]; + self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:NO]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + [self.outputStream scheduleInRunLoop:runLoop forMode:NSRunLoopCommonModes]; + [self.outputStream open]; + + return self; +} + +- (void)dealloc { + [_request release]; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + _outputStream = nil; + } + + [_temporaryFilePath release]; + [super dealloc]; +} + +- (NSMutableURLRequest *)requestByFinalizingMultipartFormData { + // Close the stream and return the original request if no data has been written + if ([[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] integerValue] == 0) { + [self.outputStream close]; + + return self.request; + } + + [self appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]]; + + [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; + [self.request setValue:[[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] stringValue] forHTTPHeaderField:@"Content-Length"]; + [self.request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:self.temporaryFilePath]]; + + [self.outputStream close]; + + return self.request; +} + +#pragma mark - AFMultipartFormData + +- (void)appendBoundary { + if ([[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] integerValue] == 0) { + [self appendString:AFMultipartFormInitialBoundary()]; + } else { + [self appendString:AFMultipartFormEncapsulationBoundary()]; + } +} + +- (void)appendPartWithHeaders:(NSDictionary *)headers + body:(NSData *)body +{ + [self appendBoundary]; + + for (NSString *field in [headers allKeys]) { + [self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]]; + } + + [self appendString:kAFMultipartFormCRLF]; + [self appendData:body]; +} + +- (void)appendPartWithFormData:(NSData *)data + name:(NSString *)name +{ + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; + + [self appendPartWithHeaders:mutableHeaders body:data]; +} + +- (void)appendPartWithFileData:(NSData *)data + name:(NSString *)name + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType +{ + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + + [self appendPartWithHeaders:mutableHeaders body:data]; +} + +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL + name:(NSString *)name + error:(NSError **)error +{ + if (![fileURL isFileURL]) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + [userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey]; + [userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey]; + if (error != NULL) { + *error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease]; + } + + return NO; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + + NSURLResponse *response = nil; + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error]; + + if (data && response) { + [self appendPartWithFileData:data name:name fileName:[response suggestedFilename] mimeType:[response MIMEType]]; + + return YES; + } else { + return NO; + } +} + +- (void)appendString:(NSString *)string { + [self appendData:[string dataUsingEncoding:self.stringEncoding]]; +} + +- (void)appendData:(NSData *)data { + if ([data length] == 0) { + return; + } + + if ([self.outputStream hasSpaceAvailable]) { + const uint8_t *dataBuffer = (uint8_t *) [data bytes]; + [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; + } +} + +@end diff --git a/media/ios/AFNetworking/AFHTTPRequestOperation.h b/media/ios/AFNetworking/AFHTTPRequestOperation.h new file mode 100644 index 000000000..bda2d17f9 --- /dev/null +++ b/media/ios/AFNetworking/AFHTTPRequestOperation.h @@ -0,0 +1,154 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#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. + */ +@interface AFHTTPRequestOperation : AFURLConnectionOperation + +///---------------------------------------------- +/// @name Getting HTTP URL Connection Information +///---------------------------------------------- + +/** + 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; + + +///---------------------------------------------------------- +/// @name Managing And Checking For Acceptable HTTP Responses +///---------------------------------------------------------- + +/** + 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; + +/** + 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; + +/** + 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 +///------------------------------------------------------------- + +/** + 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. + + @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 + + 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. + + @param contentTypes The content types to be added to the set of acceptable MIME types + */ ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes; + + +///----------------------------------------------------- +/// @name Determining Whether A Request Can Be Processed +///----------------------------------------------------- + +/** + 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; + +///----------------------------------------------------------- +/// @name Setting Completion Block Success / Failure Callbacks +///----------------------------------------------------------- + +/** + 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. + + @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. + */ +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; + +@end diff --git a/media/ios/AFNetworking/AFHTTPRequestOperation.m b/media/ios/AFNetworking/AFHTTPRequestOperation.m new file mode 100644 index 000000000..0e8e909c8 --- /dev/null +++ b/media/ios/AFNetworking/AFHTTPRequestOperation.m @@ -0,0 +1,335 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFHTTPRequestOperation.h" +#import + +NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete"; + +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]; + } + + if (contentType) { + [mutableContentTypes addObject:contentType]; + } + } + + 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 NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { + NSMutableString *string = [NSMutableString string]; + + NSRange range = NSMakeRange([indexSet firstIndex], 1); + while (range.location != NSNotFound) { + NSUInteger nextIndex = [indexSet indexGreaterThanIndex:range.location]; + while (nextIndex == range.location + range.length) { + range.length++; + nextIndex = [indexSet indexGreaterThanIndex:nextIndex]; + } + + if (string.length) { + [string appendString:@","]; + } + + if (range.length == 1) { + [string appendFormat:@"%u", range.location]; + } else { + NSUInteger firstIndex = range.location; + NSUInteger lastIndex = firstIndex + range.length - 1; + [string appendFormat:@"%u-%u", firstIndex, lastIndex]; + } + + range.location = nextIndex; + range.length = 1; + } + + 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; +} + +#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; +@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) { + dispatch_release(_successCallbackQueue); + _successCallbackQueue = NULL; + } + + if (_failureCallbackQueue) { + dispatch_release(_failureCallbackQueue); + _failureCallbackQueue = NULL; + } + + [super dealloc]; +} + +- (NSError *)error { + if (self.response && !self.HTTPError) { + if (![self hasAcceptableStatusCode]) { + 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.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]; + } + } + + if (self.HTTPError) { + return self.HTTPError; + } else { + return [super error]; + } +} + +- (void)pause { + 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"]) { + [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]]; +} + +- (BOOL)hasAcceptableContentType { + return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]]; +} + +- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue { + if (successCallbackQueue != _successCallbackQueue) { + if (_successCallbackQueue) { + dispatch_release(_successCallbackQueue); + _successCallbackQueue = NULL; + } + + if (successCallbackQueue) { + dispatch_retain(successCallbackQueue); + _successCallbackQueue = successCallbackQueue; + } + } +} + +- (void)setFailureCallbackQueue:(dispatch_queue_t)failureCallbackQueue { + if (failureCallbackQueue != _failureCallbackQueue) { + if (_failureCallbackQueue) { + dispatch_release(_failureCallbackQueue); + _failureCallbackQueue = NULL; + } + + if (failureCallbackQueue) { + dispatch_retain(failureCallbackQueue); + _failureCallbackQueue = failureCallbackQueue; + } + } +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? 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 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]; + [mutableStatusCodes addIndexes:statusCodes]; + SEL selector = @selector(acceptableStatusCodes); + AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation); + objc_setAssociatedObject([self class], selector, mutableStatusCodes, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + ++ (NSSet *)acceptableContentTypes { + return nil; +} + ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes { + NSMutableSet *mutableContentTypes = [[[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES] autorelease]; + [mutableContentTypes unionSet:contentTypes]; + SEL selector = @selector(acceptableContentTypes); + AFSwizzleClassMethodWithImplementation([self class], selector, (IMP)AFStaticClassValueImplementation); + objc_setAssociatedObject([self class], selector, mutableContentTypes, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + ++ (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 diff --git a/media/ios/AFNetworking/AFImageRequestOperation.h b/media/ios/AFNetworking/AFImageRequestOperation.h new file mode 100644 index 000000000..f6de58649 --- /dev/null +++ b/media/ios/AFNetworking/AFImageRequestOperation.h @@ -0,0 +1,112 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import +#elif __MAC_OS_X_VERSION_MIN_REQUIRED +#import +#endif + +/** + `AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading an 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` + - `image/png` + - `image/ico` + - `image/x-icon` + - `image/bmp` + - `image/x-bmp` + - `image/x-xbitmap` + - `image/x-win-bitmap` + */ +@interface AFImageRequestOperation : AFHTTPRequestOperation + +/** + 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; +#endif + +#if __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. + */ +@property (nonatomic, assign) CGFloat imageScale; +#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. + + @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; +#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; +#endif + +@end diff --git a/media/ios/AFNetworking/AFImageRequestOperation.m b/media/ios/AFNetworking/AFImageRequestOperation.m new file mode 100644 index 000000000..beeae6948 --- /dev/null +++ b/media/ios/AFNetworking/AFImageRequestOperation.m @@ -0,0 +1,236 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#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); + } + + return af_image_request_operation_processing_queue; +} + +@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; +#endif +@end + +@implementation AFImageRequestOperation +@synthesize responseImage = _responseImage; +#if __IPHONE_OS_VERSION_MIN_REQUIRED +@synthesize imageScale = _imageScale; +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)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) { + success(image); + } + } failure:nil]; +} +#elif __MAC_OS_X_VERSION_MIN_REQUIRED ++ (AFImageRequestOperation *)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) { + success(image); + } + } failure:nil]; +} +#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 +{ + AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + [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) { + success(operation.request, operation.response, processedImage); + }); + }); + } else { + success(operation.request, operation.response, image); + } + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + 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 +{ + AFImageRequestOperation *requestOperation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + NSImage *image = responseObject; + if (imageProcessingBlock) { + dispatch_async(image_request_operation_processing_queue(), ^(void) { + NSImage *processedImage = imageProcessingBlock(image); + + dispatch_async(requestOperation.successCallbackQueue ? requestOperation.successCallbackQueue : dispatch_get_main_queue(), ^(void) { + success(operation.request, operation.response, processedImage); + }); + }); + } else { + success(operation.request, operation.response, image); + } + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error); + } + }]; + + return requestOperation; +} +#endif + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super initWithRequest:urlRequest]; + if (!self) { + return nil; + } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + self.imageScale = [[UIScreen mainScreen] scale]; +#endif + + return self; +} + +- (void)dealloc { + [_responseImage release]; + [super dealloc]; +} + +#if __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]; + } + + return _responseImage; +} + +- (void)setImageScale:(CGFloat)imageScale { + if (imageScale == _imageScale) { + return; + } + + _imageScale = imageScale; + + self.responseImage = nil; +} +#elif __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 addRepresentation:bitimage]; + [bitimage release]; + } + + return _responseImage; +} +#endif + +#pragma mark - AFHTTPClientOperation + ++ (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]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + static NSSet * _acceptablePathExtension = nil; + static dispatch_once_t onceToken; + 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]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + 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(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + UIImage *image = nil; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + NSImage *image = nil; +#endif + + image = self.responseImage; + + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, image); + }); + } + } + }); + }; +} + +@end diff --git a/media/ios/AFNetworking/AFJSONRequestOperation.h b/media/ios/AFNetworking/AFJSONRequestOperation.h new file mode 100644 index 000000000..a1191f91a --- /dev/null +++ b/media/ios/AFNetworking/AFJSONRequestOperation.h @@ -0,0 +1,66 @@ +// AFJSONRequestOperation.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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +/** + `AFJSONRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with JSON response data. + + ## Acceptable Content Types + + By default, `AFJSONRequestOperation` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types: + + - `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. + */ +@interface AFJSONRequestOperation : AFHTTPRequestOperation + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + 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; + +///---------------------------------- +/// @name Creating Request Operations +///---------------------------------- + +/** + Creates and returns an `AFJSONRequestOperation` 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 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. + + @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; + +@end diff --git a/media/ios/AFNetworking/AFJSONRequestOperation.m b/media/ios/AFNetworking/AFJSONRequestOperation.m new file mode 100644 index 000000000..bcdcb7f8c --- /dev/null +++ b/media/ios/AFNetworking/AFJSONRequestOperation.m @@ -0,0 +1,138 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// 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); + } + + return af_json_request_operation_processing_queue; +} + +@interface AFJSONRequestOperation () +@property (readwrite, nonatomic, retain) id responseJSON; +@property (readwrite, nonatomic, retain) NSError *JSONError; +@end + +@implementation AFJSONRequestOperation +@synthesize responseJSON = _responseJSON; +@synthesize JSONError = _JSONError; + ++ (AFJSONRequestOperation *)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]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error, [(AFJSONRequestOperation *)operation responseJSON]); + } + }]; + + return requestOperation; +} + +- (void)dealloc { + [_responseJSON release]; + [_JSONError release]; + [super dealloc]; +} + +- (id)responseJSON { + 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); + } + + self.JSONError = error; + } + + return _responseJSON; +} + +- (NSError *)error { + if (_JSONError) { + return _JSONError; + } else { + return [super error]; + } +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"json"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? 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 (failure) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, JSON); + }); + } + } + }); + } + }; +} + +@end diff --git a/media/ios/AFNetworking/AFJSONUtilities.h b/media/ios/AFNetworking/AFJSONUtilities.h new file mode 100644 index 000000000..ece26a075 --- /dev/null +++ b/media/ios/AFNetworking/AFJSONUtilities.h @@ -0,0 +1,26 @@ +// AFJSONUtilities.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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +extern NSData * AFJSONEncode(id object, NSError **error); +extern id AFJSONDecode(NSData *data, NSError **error); diff --git a/media/ios/AFNetworking/AFJSONUtilities.m b/media/ios/AFNetworking/AFJSONUtilities.m new file mode 100644 index 000000000..3377fc4e3 --- /dev/null +++ b/media/ios/AFNetworking/AFJSONUtilities.m @@ -0,0 +1,217 @@ +// AFJSONUtilities.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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFJSONUtilities.h" + +NSData * AFJSONEncode(id object, NSError **error) { + NSData *data = nil; + + SEL _JSONKitSelector = NSSelectorFromString(@"JSONDataWithOptions:error:"); + SEL _YAJLSelector = NSSelectorFromString(@"yajl_JSONString"); + + id _SBJsonWriterClass = NSClassFromString(@"SBJsonWriter"); + SEL _SBJsonWriterSelector = NSSelectorFromString(@"dataWithObject:"); + + id _NXJsonSerializerClass = NSClassFromString(@"NXJsonSerializer"); + SEL _NXJsonSerializerSelector = NSSelectorFromString(@"serialize:"); + + id _NSJSONSerializationClass = NSClassFromString(@"NSJSONSerialization"); + SEL _NSJSONSerializationSelector = NSSelectorFromString(@"dataWithJSONObject:options:error:"); + +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { + goto _af_nsjson_encode; + } +#endif + + if (_JSONKitSelector && [object respondsToSelector:_JSONKitSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[object methodSignatureForSelector:_JSONKitSelector]]; + invocation.target = object; + invocation.selector = _JSONKitSelector; + + NSUInteger serializeOptionFlags = 0; + [invocation setArgument:&serializeOptionFlags atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&data]; + } else if (_SBJsonWriterClass && [_SBJsonWriterClass instancesRespondToSelector:_SBJsonWriterSelector]) { + id writer = [[_SBJsonWriterClass alloc] init]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[writer methodSignatureForSelector:_SBJsonWriterSelector]]; + invocation.target = writer; + invocation.selector = _SBJsonWriterSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&data]; + [writer release]; + } else if (_YAJLSelector && [object respondsToSelector:_YAJLSelector]) { + @try { + NSString *JSONString = nil; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[object methodSignatureForSelector:_YAJLSelector]]; + invocation.target = object; + invocation.selector = _YAJLSelector; + + [invocation invoke]; + [invocation getReturnValue:&JSONString]; + + data = [JSONString dataUsingEncoding:NSUTF8StringEncoding]; + } + @catch (NSException *exception) { + *error = [[[NSError alloc] initWithDomain:NSStringFromClass([exception class]) code:0 userInfo:[exception userInfo]] autorelease]; + } + } else if (_NXJsonSerializerClass && [_NXJsonSerializerClass respondsToSelector:_NXJsonSerializerSelector]) { + NSString *JSONString = nil; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NXJsonSerializerClass methodSignatureForSelector:_NXJsonSerializerSelector]]; + invocation.target = _NXJsonSerializerClass; + invocation.selector = _NXJsonSerializerSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&JSONString]; + data = [JSONString dataUsingEncoding:NSUTF8StringEncoding]; + } else if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + _af_nsjson_encode:; +#endif + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NSJSONSerializationClass methodSignatureForSelector:_NSJSONSerializationSelector]]; + invocation.target = _NSJSONSerializationClass; + invocation.selector = _NSJSONSerializationSelector; + + [invocation setArgument:&object atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + NSUInteger writeOptions = 0; + [invocation setArgument:&writeOptions atIndex:3]; + if (error != NULL) { + [invocation setArgument:error atIndex:4]; + } + + [invocation invoke]; + [invocation getReturnValue:&data]; + } else { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Please either target a platform that supports NSJSONSerialization or add one of the following libraries to your project: JSONKit, SBJSON, or YAJL", nil) forKey:NSLocalizedRecoverySuggestionErrorKey]; + [[NSException exceptionWithName:NSInternalInconsistencyException reason:NSLocalizedString(@"No JSON generation functionality available", nil) userInfo:userInfo] raise]; + } + + return data; +} + +id AFJSONDecode(NSData *data, NSError **error) { + id JSON = nil; + + SEL _JSONKitSelector = NSSelectorFromString(@"objectFromJSONDataWithParseOptions:error:"); + SEL _YAJLSelector = NSSelectorFromString(@"yajl_JSONWithOptions:error:"); + + id _SBJSONParserClass = NSClassFromString(@"SBJsonParser"); + SEL _SBJSONParserSelector = NSSelectorFromString(@"objectWithData:"); + + id _NSJSONSerializationClass = NSClassFromString(@"NSJSONSerialization"); + SEL _NSJSONSerializationSelector = NSSelectorFromString(@"JSONObjectWithData:options:error:"); + + id _NXJsonParserClass = NSClassFromString(@"NXJsonParser"); + SEL _NXJsonParserSelector = NSSelectorFromString(@"parseData:error:ignoreNulls:"); + + +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { + goto _af_nsjson_decode; + } +#endif + + if (_JSONKitSelector && [data respondsToSelector:_JSONKitSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[data methodSignatureForSelector:_JSONKitSelector]]; + invocation.target = data; + invocation.selector = _JSONKitSelector; + + NSUInteger parseOptionFlags = 0; + [invocation setArgument:&parseOptionFlags atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_SBJSONParserClass && [_SBJSONParserClass instancesRespondToSelector:_SBJSONParserSelector]) { + id parser = [[_SBJSONParserClass alloc] init]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[parser methodSignatureForSelector:_SBJSONParserSelector]]; + invocation.target = parser; + invocation.selector = _SBJSONParserSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + [parser release]; + } else if (_YAJLSelector && [data respondsToSelector:_YAJLSelector]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[data methodSignatureForSelector:_YAJLSelector]]; + invocation.target = data; + invocation.selector = _YAJLSelector; + + NSUInteger yajlParserOptions = 0; + [invocation setArgument:&yajlParserOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_NXJsonParserClass && [_NXJsonParserClass respondsToSelector:_NXJsonParserSelector]) { + NSNumber *nullOption = [NSNumber numberWithBool:YES]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NXJsonParserClass methodSignatureForSelector:_NXJsonParserSelector]]; + invocation.target = _NXJsonParserClass; + invocation.selector = _NXJsonParserSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + if (error != NULL) { + [invocation setArgument:&error atIndex:3]; + } + [invocation setArgument:&nullOption atIndex:4]; + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else if (_NSJSONSerializationClass && [_NSJSONSerializationClass respondsToSelector:_NSJSONSerializationSelector]) { +#ifdef _AFNETWORKING_PREFER_NSJSONSERIALIZATION_ + _af_nsjson_decode:; +#endif + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[_NSJSONSerializationClass methodSignatureForSelector:_NSJSONSerializationSelector]]; + invocation.target = _NSJSONSerializationClass; + invocation.selector = _NSJSONSerializationSelector; + + [invocation setArgument:&data atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + NSUInteger readOptions = 0; + [invocation setArgument:&readOptions atIndex:3]; + if (error != NULL) { + [invocation setArgument:&error atIndex:4]; + } + + [invocation invoke]; + [invocation getReturnValue:&JSON]; + } else { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Please either target a platform that supports NSJSONSerialization or add one of the following libraries to your project: JSONKit, SBJSON, or YAJL", nil) forKey:NSLocalizedRecoverySuggestionErrorKey]; + [[NSException exceptionWithName:NSInternalInconsistencyException reason:NSLocalizedString(@"No JSON parsing functionality available", nil) userInfo:userInfo] raise]; + } + + return JSON; +} diff --git a/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.h b/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.h new file mode 100644 index 000000000..929c48a89 --- /dev/null +++ b/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.h @@ -0,0 +1,68 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import + +/** + `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. + */ +@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. + */ +@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; + +/** + Returns the shared network activity indicator manager object for the system. + + @return The systemwide network activity indicator manager. + */ ++ (AFNetworkActivityIndicatorManager *)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. + */ +- (void)incrementActivityCount; + +/** + Decrements the number of active network requests. If this number becomes zero before decrementing, this will stop animating the status bar network activity indicator. + */ +- (void)decrementActivityCount; + +@end + +#endif diff --git a/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.m b/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.m new file mode 100644 index 000000000..b14132373 --- /dev/null +++ b/media/ios/AFNetworking/AFNetworkActivityIndicatorManager.m @@ -0,0 +1,134 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFNetworkActivityIndicatorManager.h" + +#import "AFHTTPRequestOperation.h" + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25; + +@interface AFNetworkActivityIndicatorManager () +@property (readwrite, atomic, assign) NSInteger activityCount; +@property (readwrite, nonatomic, retain) NSTimer *activityIndicatorVisibilityTimer; +@property (readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; + +- (void)updateNetworkActivityIndicatorVisibility; +@end + +@implementation AFNetworkActivityIndicatorManager +@synthesize activityCount = _activityCount; +@synthesize activityIndicatorVisibilityTimer = _activityIndicatorVisibilityTimer; +@synthesize enabled = _enabled; +@dynamic networkActivityIndicatorVisible; + ++ (AFNetworkActivityIndicatorManager *)sharedManager { + static AFNetworkActivityIndicatorManager *_sharedManager = nil; + static dispatch_once_t oncePredicate; + dispatch_once(&oncePredicate, ^{ + _sharedManager = [[self alloc] init]; + }); + + return _sharedManager; +} + +- (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]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_activityIndicatorVisibilityTimer invalidate]; + [_activityIndicatorVisibilityTimer release]; _activityIndicatorVisibilityTimer = nil; + + [super dealloc]; +} + +- (void)updateNetworkActivityIndicatorVisibilityDelayed { + if (self.enabled) { + // Delay hiding of activity indicator for a short interval, to avoid flickering + 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]; + } else { + [self updateNetworkActivityIndicatorVisibility]; + } + } +} + +- (BOOL)isNetworkActivityIndicatorVisible { + return _activityCount > 0; +} + +- (void)updateNetworkActivityIndicatorVisibility { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; + }); +} + +// Not exposed, but used if activityCount is set via KVC. +- (NSInteger)activityCount { + return _activityCount; +} + +- (void)setActivityCount:(NSInteger)activityCount { + @synchronized(self) { + _activityCount = activityCount; + } + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + +- (void)incrementActivityCount { + [self willChangeValueForKey:@"activityCount"]; + @synchronized(self) { + _activityCount++; + } + [self didChangeValueForKey:@"activityCount"]; + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + +- (void)decrementActivityCount { + [self willChangeValueForKey:@"activityCount"]; + @synchronized(self) { + _activityCount--; + } + [self didChangeValueForKey:@"activityCount"]; + [self updateNetworkActivityIndicatorVisibilityDelayed]; +} + ++ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { + return [NSSet setWithObject:@"activityCount"]; +} + +@end + +#endif diff --git a/media/ios/AFNetworking/AFNetworking.h b/media/ios/AFNetworking/AFNetworking.h new file mode 100644 index 000000000..49e596cc0 --- /dev/null +++ b/media/ios/AFNetworking/AFNetworking.h @@ -0,0 +1,44 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +#ifndef _AFNETWORKING_ +#define _AFNETWORKING_ + +#import "AFURLConnectionOperation.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 + +#endif /* _AFNETWORKING_ */ diff --git a/media/ios/AFNetworking/AFPropertyListRequestOperation.h b/media/ios/AFNetworking/AFPropertyListRequestOperation.h new file mode 100644 index 000000000..aa6e471f7 --- /dev/null +++ b/media/ios/AFNetworking/AFPropertyListRequestOperation.h @@ -0,0 +1,68 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +/** + `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 + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + An object deserialized from a plist constructed using the response data. + */ +@property (readonly, nonatomic, retain) id responsePropertyList; + +///-------------------------------------- +/// @name Managing Property List Behavior +///-------------------------------------- + +/** + One of the `NSPropertyListMutabilityOptions` options, specifying the mutability of objects deserialized from the property list. By default, this is `NSPropertyListImmutable`. + */ +@property (nonatomic, assign) NSPropertyListReadOptions propertyListReadOptions; + +/** + 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; + +@end diff --git a/media/ios/AFNetworking/AFPropertyListRequestOperation.m b/media/ios/AFNetworking/AFPropertyListRequestOperation.m new file mode 100644 index 000000000..ecca1af22 --- /dev/null +++ b/media/ios/AFNetworking/AFPropertyListRequestOperation.m @@ -0,0 +1,147 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#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); + } + + return af_property_list_request_operation_processing_queue; +} + +@interface AFPropertyListRequestOperation () +@property (readwrite, nonatomic, retain) id responsePropertyList; +@property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; +@property (readwrite, nonatomic, retain) NSError *propertyListError; +@end + +@implementation AFPropertyListRequestOperation +@synthesize responsePropertyList = _responsePropertyList; +@synthesize propertyListReadOptions = _propertyListReadOptions; +@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 +{ + AFPropertyListRequestOperation *requestOperation = [[[self alloc] initWithRequest:request] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, operation.response, error, [(AFPropertyListRequestOperation *)operation responsePropertyList]); + } + }]; + + return requestOperation; +} + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super initWithRequest:urlRequest]; + 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]) { + NSPropertyListFormat format; + NSError *error = nil; + self.responsePropertyList = [NSPropertyListSerialization propertyListWithData:self.responseData options:self.propertyListReadOptions format:&format error:&error]; + self.propertyListFormat = format; + self.propertyListError = error; + } + + return _responsePropertyList; +} + +- (NSError *)error { + if (_propertyListError) { + return _propertyListError; + } else { + return [super error]; + } +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/x-plist", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if ([self isCancelled]) { + return; + } + + if (self.error) { + if (failure) { + dispatch_async(self.failureCallbackQueue ? 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(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, propertyList); + }); + } + } + }); + } + }; +} + +@end diff --git a/media/ios/AFNetworking/AFURLConnectionOperation.h b/media/ios/AFNetworking/AFURLConnectionOperation.h new file mode 100644 index 000000000..0da353ae3 --- /dev/null +++ b/media/ios/AFNetworking/AFURLConnectionOperation.h @@ -0,0 +1,252 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +/** + Indicates an error occured in AFNetworking. + + @discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain. + */ +extern NSString * const AFNetworkingErrorDomain; + +/** + Posted when an operation begins executing. + */ +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 application’s 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 application’s 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 diff --git a/media/ios/AFNetworking/AFURLConnectionOperation.m b/media/ios/AFNetworking/AFURLConnectionOperation.m new file mode 100644 index 000000000..968658070 --- /dev/null +++ b/media/ios/AFNetworking/AFURLConnectionOperation.m @@ -0,0 +1,601 @@ +// AFURLConnectionOperation.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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFURLConnectionOperation.h" +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import +#endif + +typedef enum { + AFHTTPOperationPausedState = -1, + AFHTTPOperationReadyState = 1, + AFHTTPOperationExecutingState = 2, + AFHTTPOperationFinishedState = 3, +} _AFOperationState; + +typedef signed short AFOperationState; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier; +#else +typedef id AFBackgroundTaskIdentifier; +#endif + +static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; +static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; + +static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; + +NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; + +NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; +NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; + +typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected); +typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace); +typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); +typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); +typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse); + +static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { + switch (state) { + case AFHTTPOperationReadyState: + return @"isReady"; + case AFHTTPOperationExecutingState: + return @"isExecuting"; + case AFHTTPOperationFinishedState: + return @"isFinished"; + case AFHTTPOperationPausedState: + return @"isPaused"; + default: + return @"state"; + } +} + +static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { + switch (fromState) { + case AFHTTPOperationReadyState: + switch (toState) { + case AFHTTPOperationPausedState: + case AFHTTPOperationExecutingState: + return YES; + case AFHTTPOperationFinishedState: + return isCancelled; + default: + return NO; + } + case AFHTTPOperationExecutingState: + switch (toState) { + case AFHTTPOperationPausedState: + case AFHTTPOperationFinishedState: + return YES; + default: + return NO; + } + case AFHTTPOperationFinishedState: + return NO; + case AFHTTPOperationPausedState: + return toState == AFHTTPOperationReadyState; + default: + return YES; + } +} + +@interface AFURLConnectionOperation () +@property (readwrite, nonatomic, assign) AFOperationState state; +@property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled; +@property (readwrite, nonatomic, retain) NSRecursiveLock *lock; +@property (readwrite, nonatomic, retain) NSURLConnection *connection; +@property (readwrite, nonatomic, retain) NSURLRequest *request; +@property (readwrite, nonatomic, retain) NSURLResponse *response; +@property (readwrite, nonatomic, retain) NSError *error; +@property (readwrite, nonatomic, retain) NSData *responseData; +@property (readwrite, nonatomic, copy) NSString *responseString; +@property (readwrite, nonatomic, assign) long long totalBytesRead; +@property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; +@property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse; + +- (void)operationDidStart; +- (void)finish; +@end + +@implementation AFURLConnectionOperation +@synthesize state = _state; +@synthesize cancelled = _cancelled; +@synthesize connection = _connection; +@synthesize runLoopModes = _runLoopModes; +@synthesize request = _request; +@synthesize response = _response; +@synthesize error = _error; +@synthesize responseData = _responseData; +@synthesize responseString = _responseString; +@synthesize totalBytesRead = _totalBytesRead; +@dynamic inputStream; +@synthesize outputStream = _outputStream; +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; +@synthesize uploadProgress = _uploadProgress; +@synthesize downloadProgress = _downloadProgress; +@synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; +@synthesize authenticationChallenge = _authenticationChallenge; +@synthesize cacheResponse = _cacheResponse; +@synthesize redirectResponse = _redirectResponse; +@synthesize lock = _lock; + ++ (void)networkRequestThreadEntryPoint:(id)__unused object { + do { + NSAutoreleasePool *runLoopPool = [[NSAutoreleasePool alloc] init]; + [[NSRunLoop currentRunLoop] run]; + [runLoopPool drain]; + } while (YES); +} + ++ (NSThread *)networkRequestThread { + static NSThread *_networkRequestThread = nil; + static dispatch_once_t oncePredicate; + + dispatch_once(&oncePredicate, ^{ + _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; + [_networkRequestThread start]; + }); + + return _networkRequestThread; +} + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + self = [super init]; + if (!self) { + return nil; + } + + self.lock = [[[NSRecursiveLock alloc] init] autorelease]; + self.lock.name = kAFNetworkingLockName; + + self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; + + self.request = urlRequest; + + self.outputStream = [NSOutputStream outputStreamToMemory]; + + self.state = AFHTTPOperationReadyState; + + return self; +} + +- (void)dealloc { + [_lock release]; + + [_runLoopModes release]; + + [_request release]; + [_response release]; + [_error release]; + + [_responseData release]; + [_responseString release]; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + _outputStream = nil; + } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + if (_backgroundTaskIdentifier) { + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier]; + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } +#endif + + [_uploadProgress release]; + [_downloadProgress release]; + [_authenticationChallenge release]; + [_authenticationAgainstProtectionSpace release]; + [_cacheResponse release]; + [_redirectResponse release]; + + [_connection release]; + + [super dealloc]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response]; +} + +- (void)setCompletionBlock:(void (^)(void))block { + [self.lock lock]; + if (!block) { + [super setCompletionBlock:nil]; + } else { + __block id _blockSelf = self; + [super setCompletionBlock:^ { + block(); + [_blockSelf setCompletionBlock:nil]; + }]; + } + [self.lock unlock]; +} + +- (NSInputStream *)inputStream { + return self.request.HTTPBodyStream; +} + +- (void)setInputStream:(NSInputStream *)inputStream { + [self willChangeValueForKey:@"inputStream"]; + NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease]; + mutableRequest.HTTPBodyStream = inputStream; + self.request = mutableRequest; + [self didChangeValueForKey:@"inputStream"]; +} + +- (void)setOutputStream:(NSOutputStream *)outputStream { + [self willChangeValueForKey:@"outputStream"]; + [outputStream retain]; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + } + _outputStream = outputStream; + [self didChangeValueForKey:@"outputStream"]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { + [self.lock lock]; + if (!self.backgroundTaskIdentifier) { + UIApplication *application = [UIApplication sharedApplication]; + self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ + if (handler) { + handler(); + } + + [self cancel]; + + [application endBackgroundTask:self.backgroundTaskIdentifier]; + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + }]; + } + [self.lock unlock]; +} +#endif + +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { + self.uploadProgress = block; +} + +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { + self.downloadProgress = block; +} + +- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *, NSURLProtectionSpace *))block { + self.authenticationAgainstProtectionSpace = block; +} + +- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { + self.authenticationChallenge = block; +} + +- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { + self.cacheResponse = block; +} + +- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block { + self.redirectResponse = block; +} + +- (void)setState:(AFOperationState)state { + [self.lock lock]; + if (AFStateTransitionIsValid(self.state, state, [self isCancelled])) { + NSString *oldStateKey = AFKeyPathFromOperationState(self.state); + NSString *newStateKey = AFKeyPathFromOperationState(state); + + [self willChangeValueForKey:newStateKey]; + [self willChangeValueForKey:oldStateKey]; + _state = state; + [self didChangeValueForKey:oldStateKey]; + [self didChangeValueForKey:newStateKey]; + + switch (state) { + case AFHTTPOperationExecutingState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; + break; + case AFHTTPOperationFinishedState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + break; + default: + break; + } + } + [self.lock unlock]; +} + +- (NSString *)responseString { + [self.lock lock]; + if (!_responseString && self.response && self.responseData) { + NSStringEncoding textEncoding = NSUTF8StringEncoding; + if (self.response.textEncodingName) { + textEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.response.textEncodingName)); + } + + self.responseString = [[[NSString alloc] initWithData:self.responseData encoding:textEncoding] autorelease]; + } + [self.lock unlock]; + + return _responseString; +} + +- (void)pause { + if ([self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationPausedState; + + [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + [self.lock unlock]; +} + +- (BOOL)isPaused { + return self.state == AFHTTPOperationPausedState; +} + +- (void)resume { + if (![self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationReadyState; + + [self start]; + [self.lock unlock]; +} + +#pragma mark - NSOperation + +- (BOOL)isReady { + return self.state == AFHTTPOperationReadyState && [super isReady]; +} + +- (BOOL)isExecuting { + return self.state == AFHTTPOperationExecutingState; +} + +- (BOOL)isFinished { + return self.state == AFHTTPOperationFinishedState; +} + +- (BOOL)isConcurrent { + return YES; +} + +- (void)start { + [self.lock lock]; + if ([self isReady]) { + self.state = AFHTTPOperationExecutingState; + + [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + } + [self.lock unlock]; +} + +- (void)operationDidStart { + [self.lock lock]; + if ([self isCancelled]) { + [self finish]; + } else { + self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } + + [self.connection start]; + } + [self.lock unlock]; +} + +- (void)finish { + self.state = AFHTTPOperationFinishedState; +} + +- (void)cancel { + [self.lock lock]; + if (![self isFinished] && ![self isCancelled]) { + [self willChangeValueForKey:@"isCancelled"]; + _cancelled = YES; + [super cancel]; + [self didChangeValueForKey:@"isCancelled"]; + + // Cancel the connection on the thread it runs on to prevent race conditions + [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + } + [self.lock unlock]; +} + +- (void)cancelConnection { + if (self.connection) { + [self.connection cancel]; + + // Manually send this delegate message since `[self.connection cancel]` causes the connection to never send another message to its delegate + NSDictionary *userInfo = nil; + if ([self.request URL]) { + userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + } + [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]]; + } +} + +#pragma mark - NSURLConnectionDelegate + +- (BOOL)connection:(NSURLConnection *)connection +canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace +{ +#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ + if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + return YES; + } +#endif + + if (self.authenticationAgainstProtectionSpace) { + return self.authenticationAgainstProtectionSpace(connection, protectionSpace); + } else if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) { + return NO; + } else { + return YES; + } +} + +- (void)connection:(NSURLConnection *)connection +didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge +{ +#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; + return; + } +#endif + + if (self.authenticationChallenge) { + self.authenticationChallenge(connection, challenge); + } else { + if ([challenge previousFailureCount] == 0) { + NSURLCredential *credential = nil; + + NSString *username = [(NSString *)CFURLCopyUserName((CFURLRef)[self.request URL]) autorelease]; + NSString *password = [(NSString *)CFURLCopyPassword((CFURLRef)[self.request URL]) autorelease]; + + if (username && password) { + credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceNone]; + } else if (username) { + credential = [[[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:[challenge protectionSpace]] objectForKey:username]; + } else { + credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:[challenge protectionSpace]]; + } + + if (credential) { + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } +} + +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse; +{ + if (self.redirectResponse) { + return self.redirectResponse(connection, request, redirectResponse); + } else { + return request; + } +} + +- (void)connection:(NSURLConnection *)__unused connection + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten +totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite +{ + if (self.uploadProgress) { + self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } +} + +- (void)connection:(NSURLConnection *)__unused connection +didReceiveResponse:(NSURLResponse *)response +{ + self.response = response; + + [self.outputStream open]; +} + +- (void)connection:(NSURLConnection *)__unused connection + didReceiveData:(NSData *)data +{ + self.totalBytesRead += [data length]; + + if ([self.outputStream hasSpaceAvailable]) { + const uint8_t *dataBuffer = (uint8_t *) [data bytes]; + [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; + } + + if (self.downloadProgress) { + self.downloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength); + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { + self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + + [self.outputStream close]; + + [self finish]; + + self.connection = nil; +} + +- (void)connection:(NSURLConnection *)__unused connection + didFailWithError:(NSError *)error +{ + self.error = error; + + [self.outputStream close]; + + [self finish]; + + self.connection = nil; +} + +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection + willCacheResponse:(NSCachedURLResponse *)cachedResponse +{ + if (self.cacheResponse) { + return self.cacheResponse(connection, cachedResponse); + } else { + if ([self isCancelled]) { + return nil; + } + + return cachedResponse; + } +} + +@end diff --git a/media/ios/AFNetworking/AFXMLRequestOperation.h b/media/ios/AFNetworking/AFXMLRequestOperation.h new file mode 100644 index 000000000..0beb67766 --- /dev/null +++ b/media/ios/AFNetworking/AFXMLRequestOperation.h @@ -0,0 +1,89 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPRequestOperation.h" + +#import + +/** + `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 + +///---------------------------- +/// @name Getting Response Data +///---------------------------- + +/** + An `NSXMLParser` object constructed from the response data. + */ +@property (readonly, nonatomic, retain) NSXMLParser *responseXMLParser; + +#if __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; +#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; + + +#if __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. + + @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; +#endif + +@end diff --git a/media/ios/AFNetworking/AFXMLRequestOperation.m b/media/ios/AFNetworking/AFXMLRequestOperation.m new file mode 100644 index 000000000..f40cacbce --- /dev/null +++ b/media/ios/AFNetworking/AFXMLRequestOperation.m @@ -0,0 +1,177 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFXMLRequestOperation.h" + +#include + +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); + } + + 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; +#endif +@property (readwrite, nonatomic, retain) NSError *XMLError; +@end + +@implementation AFXMLRequestOperation +@synthesize responseXMLParser = _responseXMLParser; +#if __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 +{ + AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, operation.response, responseObject); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + 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 +{ + AFXMLRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease]; + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, __unused id responseObject) { + if (success) { + NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; + success(operation.request, operation.response, XMLDocument); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; + 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]; + } + + return _responseXMLParser; +} + +#if __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.XMLError = error; + } + + return _responseXMLDocument; +} +#endif + +- (NSError *)error { + if (_XMLError) { + return _XMLError; + } else { + return [super error]; + } +} + +#pragma mark - NSOperation + +- (void)cancel { + [super cancel]; + + self.responseXMLParser.delegate = nil; +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"xml"] || [super canProcessRequest:request]; +} + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + 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(), ^{ + failure(self, self.error); + }); + } + } else { + if (success) { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, XMLParser); + }); + } + } + }); + }; +} + +@end diff --git a/media/ios/AFNetworking/UIImageView+AFNetworking.h b/media/ios/AFNetworking/UIImageView+AFNetworking.h new file mode 100644 index 000000000..1c8bfb81b --- /dev/null +++ b/media/ios/AFNetworking/UIImageView+AFNetworking.h @@ -0,0 +1,76 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFImageRequestOperation.h" + +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import + +/** + This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL. + */ +@interface UIImageView (AFNetworking) + +/** + 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:` + + @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. + + @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 + 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. + + @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 + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; + +/** + Cancels any executing image request operation for the receiver, if one exists. + */ +- (void)cancelImageRequestOperation; + +@end + +#endif diff --git a/media/ios/AFNetworking/UIImageView+AFNetworking.m b/media/ios/AFNetworking/UIImageView+AFNetworking.m new file mode 100644 index 000000000..ac1f708dc --- /dev/null +++ b/media/ios/AFNetworking/UIImageView+AFNetworking.m @@ -0,0 +1,182 @@ +// 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 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +#import "UIImageView+AFNetworking.h" + +@interface AFImageCache : NSCache +- (UIImage *)cachedImageForRequest:(NSURLRequest *)request; +- (void)cacheImage:(UIImage *)image + forRequest:(NSURLRequest *)request; +@end + +#pragma mark - + +static char kAFImageRequestOperationObjectKey; + +@interface UIImageView (_AFNetworking) +@property (readwrite, nonatomic, retain, setter = af_setImageRequestOperation:) AFImageRequestOperation *af_imageRequestOperation; +@end + +@implementation UIImageView (_AFNetworking) +@dynamic af_imageRequestOperation; +@end + +#pragma mark - + +@implementation UIImageView (AFNetworking) + +- (AFHTTPRequestOperation *)af_imageRequestOperation { + return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, &kAFImageRequestOperationObjectKey); +} + +- (void)af_setImageRequestOperation:(AFImageRequestOperation *)imageRequestOperation { + objc_setAssociatedObject(self, &kAFImageRequestOperationObjectKey, imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + ++ (NSOperationQueue *)af_sharedImageRequestOperationQueue { + static NSOperationQueue *_af_imageRequestOperationQueue = nil; + + if (!_af_imageRequestOperationQueue) { + _af_imageRequestOperationQueue = [[NSOperationQueue alloc] init]; + [_af_imageRequestOperationQueue setMaxConcurrentOperationCount:8]; + } + + return _af_imageRequestOperationQueue; +} + ++ (AFImageCache *)af_sharedImageCache { + static AFImageCache *_af_imageCache = nil; + static dispatch_once_t oncePredicate; + dispatch_once(&oncePredicate, ^{ + _af_imageCache = [[AFImageCache alloc] init]; + }); + + return _af_imageCache; +} + +#pragma mark - + +- (void)setImageWithURL:(NSURL *)url { + [self setImageWithURL:url placeholderImage:nil]; +} + +- (void)setImageWithURL:(NSURL *)url + placeholderImage:(UIImage *)placeholderImage +{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + [request setHTTPShouldHandleCookies:NO]; + [request setHTTPShouldUsePipelining:YES]; + + [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; +} + +- (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 = 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 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 (failure) { + failure(operation.request, operation.response, error); + } + + }]; + + self.af_imageRequestOperation = requestOperation; + + [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation]; + } +} + +- (void)cancelImageRequestOperation { + [self.af_imageRequestOperation cancel]; + self.af_imageRequestOperation = nil; +} + +@end + +#pragma mark - + +static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) { + return [[request URL] absoluteString]; +} + +@implementation AFImageCache + +- (UIImage *)cachedImageForRequest:(NSURLRequest *)request { + switch ([request cachePolicy]) { + case NSURLRequestReloadIgnoringCacheData: + case NSURLRequestReloadIgnoringLocalAndRemoteCacheData: + return nil; + default: + break; + } + + return [self objectForKey:AFImageCacheKeyFromURLRequest(request)]; +} + +- (void)cacheImage:(UIImage *)image + forRequest:(NSURLRequest *)request +{ + if (image && request) { + [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)]; + } +} + +@end + +#endif diff --git a/media/iphone/ASI/ASIAuthenticationDialog.h b/media/ios/ASI/ASIAuthenticationDialog.h similarity index 100% rename from media/iphone/ASI/ASIAuthenticationDialog.h rename to media/ios/ASI/ASIAuthenticationDialog.h diff --git a/media/iphone/ASI/ASIAuthenticationDialog.m b/media/ios/ASI/ASIAuthenticationDialog.m similarity index 100% rename from media/iphone/ASI/ASIAuthenticationDialog.m rename to media/ios/ASI/ASIAuthenticationDialog.m diff --git a/media/iphone/ASI/ASICacheDelegate.h b/media/ios/ASI/ASICacheDelegate.h similarity index 100% rename from media/iphone/ASI/ASICacheDelegate.h rename to media/ios/ASI/ASICacheDelegate.h diff --git a/media/iphone/ASI/ASIDataCompressor.h b/media/ios/ASI/ASIDataCompressor.h similarity index 100% rename from media/iphone/ASI/ASIDataCompressor.h rename to media/ios/ASI/ASIDataCompressor.h diff --git a/media/iphone/ASI/ASIDataCompressor.m b/media/ios/ASI/ASIDataCompressor.m similarity index 100% rename from media/iphone/ASI/ASIDataCompressor.m rename to media/ios/ASI/ASIDataCompressor.m diff --git a/media/iphone/ASI/ASIDataDecompressor.h b/media/ios/ASI/ASIDataDecompressor.h similarity index 100% rename from media/iphone/ASI/ASIDataDecompressor.h rename to media/ios/ASI/ASIDataDecompressor.h diff --git a/media/iphone/ASI/ASIDataDecompressor.m b/media/ios/ASI/ASIDataDecompressor.m similarity index 100% rename from media/iphone/ASI/ASIDataDecompressor.m rename to media/ios/ASI/ASIDataDecompressor.m diff --git a/media/iphone/ASI/ASIDownloadCache.h b/media/ios/ASI/ASIDownloadCache.h similarity index 100% rename from media/iphone/ASI/ASIDownloadCache.h rename to media/ios/ASI/ASIDownloadCache.h diff --git a/media/iphone/ASI/ASIDownloadCache.m b/media/ios/ASI/ASIDownloadCache.m similarity index 100% rename from media/iphone/ASI/ASIDownloadCache.m rename to media/ios/ASI/ASIDownloadCache.m diff --git a/media/iphone/ASI/ASIFormDataRequest.h b/media/ios/ASI/ASIFormDataRequest.h similarity index 100% rename from media/iphone/ASI/ASIFormDataRequest.h rename to media/ios/ASI/ASIFormDataRequest.h diff --git a/media/iphone/ASI/ASIFormDataRequest.m b/media/ios/ASI/ASIFormDataRequest.m similarity index 100% rename from media/iphone/ASI/ASIFormDataRequest.m rename to media/ios/ASI/ASIFormDataRequest.m diff --git a/media/iphone/ASI/ASIHTTPRequest.h b/media/ios/ASI/ASIHTTPRequest.h similarity index 100% rename from media/iphone/ASI/ASIHTTPRequest.h rename to media/ios/ASI/ASIHTTPRequest.h diff --git a/media/iphone/ASI/ASIHTTPRequest.m b/media/ios/ASI/ASIHTTPRequest.m similarity index 100% rename from media/iphone/ASI/ASIHTTPRequest.m rename to media/ios/ASI/ASIHTTPRequest.m diff --git a/media/iphone/ASI/ASIHTTPRequestConfig.h b/media/ios/ASI/ASIHTTPRequestConfig.h similarity index 100% rename from media/iphone/ASI/ASIHTTPRequestConfig.h rename to media/ios/ASI/ASIHTTPRequestConfig.h diff --git a/media/iphone/ASI/ASIHTTPRequestDelegate.h b/media/ios/ASI/ASIHTTPRequestDelegate.h similarity index 100% rename from media/iphone/ASI/ASIHTTPRequestDelegate.h rename to media/ios/ASI/ASIHTTPRequestDelegate.h diff --git a/media/iphone/ASI/ASIInputStream.h b/media/ios/ASI/ASIInputStream.h similarity index 100% rename from media/iphone/ASI/ASIInputStream.h rename to media/ios/ASI/ASIInputStream.h diff --git a/media/iphone/ASI/ASIInputStream.m b/media/ios/ASI/ASIInputStream.m similarity index 100% rename from media/iphone/ASI/ASIInputStream.m rename to media/ios/ASI/ASIInputStream.m diff --git a/media/iphone/ASI/ASINetworkQueue.h b/media/ios/ASI/ASINetworkQueue.h similarity index 100% rename from media/iphone/ASI/ASINetworkQueue.h rename to media/ios/ASI/ASINetworkQueue.h diff --git a/media/iphone/ASI/ASINetworkQueue.m b/media/ios/ASI/ASINetworkQueue.m similarity index 100% rename from media/iphone/ASI/ASINetworkQueue.m rename to media/ios/ASI/ASINetworkQueue.m diff --git a/media/iphone/ASI/ASIProgressDelegate.h b/media/ios/ASI/ASIProgressDelegate.h similarity index 100% rename from media/iphone/ASI/ASIProgressDelegate.h rename to media/ios/ASI/ASIProgressDelegate.h diff --git a/media/iphone/App Store Description.txt b/media/ios/App Store Description.txt similarity index 100% rename from media/iphone/App Store Description.txt rename to media/ios/App Store Description.txt diff --git a/media/ios/Classes/ActivityCell.h b/media/ios/Classes/ActivityCell.h new file mode 100644 index 000000000..78552154e --- /dev/null +++ b/media/ios/Classes/ActivityCell.h @@ -0,0 +1,33 @@ +// +// ActivityCell.h +// NewsBlur +// +// Created by Roy Yang on 7/13/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "OHAttributedLabel.h" + +@interface ActivityCell : UITableViewCell { + OHAttributedLabel *activityLabel; + UIImageView *faviconView; + int topMargin; + int bottomMargin; + int leftMargin; + int rightMargin; + int avatarSize; +} + +@property (nonatomic, strong) OHAttributedLabel *activityLabel; +@property (nonatomic, strong) UIImageView *faviconView; +@property (readwrite) int topMargin; +@property (readwrite) int bottomMargin; +@property (readwrite) int leftMargin; +@property (readwrite) int rightMargin; +@property (readwrite) int avatarSize; + +- (int)setActivity:(NSDictionary *)activity withUserProfile:(NSDictionary *)userProfile withWidth:(int)width; +- (NSString *)stripFormatting:(NSString *)str; + +@end diff --git a/media/ios/Classes/ActivityCell.m b/media/ios/Classes/ActivityCell.m new file mode 100644 index 000000000..87e444115 --- /dev/null +++ b/media/ios/Classes/ActivityCell.m @@ -0,0 +1,180 @@ +// +// ActivityCell.m +// NewsBluractivity +// +// Created by Roy Yang on 7/13/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ActivityCell.h" +#import "NSAttributedString+Attributes.h" +#import "UIImageView+AFNetworking.h" + +@implementation ActivityCell + +@synthesize activityLabel; +@synthesize faviconView; +@synthesize topMargin; +@synthesize bottomMargin; +@synthesize leftMargin; +@synthesize rightMargin; +@synthesize avatarSize; + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + activityLabel = nil; + faviconView = nil; + + // create favicon and label in view + UIImageView *favicon = [[UIImageView alloc] initWithFrame:CGRectZero]; + self.faviconView = favicon; + [self.contentView addSubview:favicon]; + + OHAttributedLabel *activity = [[OHAttributedLabel alloc] initWithFrame:CGRectZero]; + activity.backgroundColor = [UIColor whiteColor]; + activity.automaticallyAddLinksForType = NO; + self.activityLabel = activity; + [self.contentView addSubview:activity]; + + topMargin = 15; + bottomMargin = 15; + leftMargin = 20; + rightMargin = 20; + avatarSize = 48; + } + + return self; +} + + +- (void)layoutSubviews { + [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.activityLabel.frame = labelRect; +} + +- (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; + activityLabelRect.size.width = width - leftMargin - avatarSize - leftMargin - rightMargin; + self.activityLabel.frame = activityLabelRect; + self.faviconView.frame = CGRectMake(leftMargin, topMargin, avatarSize, avatarSize); + + NSString *category = [activity objectForKey:@"category"]; + NSString *content = [activity objectForKey:@"content"]; + NSString *comment = [NSString stringWithFormat:@"\"%@\"", content]; + NSString *title = [self stripFormatting:[NSString stringWithFormat:@"%@", [activity objectForKey:@"title"]]]; + NSString *time = [[NSString stringWithFormat:@"%@ ago", [activity objectForKey:@"time_since"]] uppercaseString]; + NSString *withUserUsername = @""; + NSString *username = [NSString stringWithFormat:@"%@", [userProfile objectForKey:@"username"]]; + + NSString* txt; + + if ([category isEqualToString:@"follow"] || + [category isEqualToString:@"comment_reply"] || + [category isEqualToString:@"comment_like"] || + [category isEqualToString:@"signup"]) { + // this is for the rare instance when the with_user doesn't return anything + if ([[activity objectForKey:@"with_user"] class] == [NSNull class]) { + self.faviconView.frame = CGRectZero; + self.activityLabel.attributedText = nil; + return 1; + } + +// UIImage *placeholder = [UIImage imageNamed:@"user_light"]; + [self.faviconView setImageWithURL:[NSURL URLWithString:[[activity objectForKey:@"with_user"] objectForKey:@"photo_url"]] + placeholderImage:nil]; + } else if ([category isEqualToString:@"sharedstory"]) { +// UIImage *placeholder = [UIImage imageNamed:@"user_light"]; + [self.faviconView setImageWithURL:[NSURL URLWithString:[userProfile objectForKey:@"photo_url"]] + placeholderImage:nil]; + } else if ([category isEqualToString:@"feedsub"]) { +// UIImage *placeholder = [UIImage imageNamed:@"world"]; + NSString *faviconUrl = [NSString stringWithFormat:@"http://%@/rss_feeds/icon/%i", + NEWSBLUR_URL, + [[activity objectForKey:@"feed_id"] intValue]]; + [self.faviconView setImageWithURL:[NSURL URLWithString:faviconUrl ] + placeholderImage:nil]; + self.faviconView.contentMode = UIViewContentModeScaleAspectFit; + self.faviconView.frame = CGRectMake(leftMargin+16, topMargin, 16, 16); + } + + if ([category isEqualToString:@"follow"]) { + withUserUsername = [[activity objectForKey:@"with_user"] objectForKey:@"username"]; + txt = [NSString stringWithFormat:@"%@ followed %@.", username, withUserUsername]; + } else if ([category isEqualToString:@"comment_reply"]) { + withUserUsername = [[activity objectForKey:@"with_user"] objectForKey:@"username"]; + txt = [NSString stringWithFormat:@"%@ replied to %@: \n%@", username, withUserUsername, comment]; + } else if ([category isEqualToString:@"comment_like"]) { + withUserUsername = [[activity objectForKey:@"with_user"] objectForKey:@"username"]; + txt = [NSString stringWithFormat:@"%@ favorited %@'s comment on %@:\n%@", username, withUserUsername, title, comment]; + } else if ([category isEqualToString:@"sharedstory"]) { + if ([content isEqualToString:@""] || content == nil) { + txt = [NSString stringWithFormat:@"%@ shared %@.", username, title]; + } else { + txt = [NSString stringWithFormat:@"%@ shared %@:\n%@", username, title, comment]; + } + + } else if ([category isEqualToString:@"star"]) { + txt = [NSString stringWithFormat:@"You saved \"%@\".", content]; + } else if ([category isEqualToString:@"feedsub"]) { + txt = [NSString stringWithFormat:@"You subscribed to %@.", content]; + } else if ([category isEqualToString:@"signup"]) { + txt = [NSString stringWithFormat:@"You signed up for NewsBlur.", content]; + } + + NSString *txtWithTime = [NSString stringWithFormat:@"%@\n%@", txt, time]; + NSMutableAttributedString* attrStr = [NSMutableAttributedString attributedStringWithString:txtWithTime]; + + // for those calls we don't specify a range so it affects the whole string + [attrStr setFont:[UIFont fontWithName:@"Helvetica" size:14]]; + [attrStr setTextColor:UIColorFromRGB(0x333333)]; + + if (![username isEqualToString:@"You"]){ + [attrStr setTextColor:UIColorFromRGB(NEWSBLUR_LINK_COLOR) range:[txtWithTime rangeOfString:username]]; + [attrStr setTextBold:YES range:[txt rangeOfString:username]]; + } + + [attrStr setTextColor:UIColorFromRGB(NEWSBLUR_LINK_COLOR) range:[txtWithTime rangeOfString:title]]; + + if(withUserUsername.length) { + [attrStr setTextColor:UIColorFromRGB(NEWSBLUR_LINK_COLOR) range:[txtWithTime rangeOfString:withUserUsername]]; + [attrStr setTextBold:YES range:[txtWithTime rangeOfString:withUserUsername]]; + } + + [attrStr setTextColor:UIColorFromRGB(0x666666) range:[txtWithTime rangeOfString:comment]]; + + [attrStr setTextColor:UIColorFromRGB(0x999999) range:[txtWithTime rangeOfString:time]]; + [attrStr setFont:[UIFont fontWithName:@"Helvetica" size:10] range:[txtWithTime rangeOfString:time]]; + [attrStr setTextAlignment:kCTLeftTextAlignment lineBreakMode:kCTLineBreakByWordWrapping lineHeight:4]; + + self.activityLabel.attributedText = attrStr; + + [self.activityLabel sizeToFit]; + + int height = self.activityLabel.frame.size.height; + + return MAX(height, self.faviconView.frame.size.height); +} + +- (NSString *)stripFormatting:(NSString *)str { + while ([str rangeOfString:@" "].location != NSNotFound) { + str = [str stringByReplacingOccurrencesOfString:@" " withString:@" "]; + } + while ([str rangeOfString:@"\n"].location != NSNotFound) { + str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; + } + return str; +} + +@end diff --git a/media/ios/Classes/ActivityModule.h b/media/ios/Classes/ActivityModule.h new file mode 100644 index 000000000..e47035e88 --- /dev/null +++ b/media/ios/Classes/ActivityModule.h @@ -0,0 +1,42 @@ +// +// ActivityModule.h +// NewsBlur +// +// Created by Roy Yang on 7/11/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface ActivityModule : UIView + { + NewsBlurAppDelegate *appDelegate; + UITableView *activitiesTable; + UIPopoverController *popoverController; + + BOOL pageFetching; + BOOL pageFinished; + int activitiesPage; +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (nonatomic, strong) UITableView *activitiesTable; +@property (nonatomic, strong) UIPopoverController *popoverController; + +@property (nonatomic, readwrite) BOOL pageFetching; +@property (nonatomic, readwrite) BOOL pageFinished; +@property (readwrite) int activitiesPage; + +- (void)refreshWithActivities:(NSArray *)activities; + +- (void)fetchActivitiesDetail:(int)page; +- (void)finishLoadActivities:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; + +- (void)checkScroll; + +@end diff --git a/media/ios/Classes/ActivityModule.m b/media/ios/Classes/ActivityModule.m new file mode 100644 index 000000000..9ea0e88fe --- /dev/null +++ b/media/ios/Classes/ActivityModule.m @@ -0,0 +1,313 @@ +// +// ActivityModule.m +// NewsBlur +// +// Created by Roy Yang on 7/11/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ActivityModule.h" +#import "ActivityCell.h" +#import "NewsBlurAppDelegate.h" +#import "UserProfileViewController.h" +#import +#import "ASIHTTPRequest.h" +#import "ActivityCell.h" + +@implementation ActivityModule + +@synthesize appDelegate; +@synthesize activitiesTable; +@synthesize popoverController; +@synthesize pageFetching; +@synthesize pageFinished; +@synthesize activitiesPage; + +#define MINIMUM_ACTIVITY_HEIGHT 48 + 30 + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // initialize code here + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + self.activitiesTable = [[UITableView alloc] init]; + self.activitiesTable.dataSource = self; + self.activitiesTable.delegate = self; + self.activitiesTable.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);; + self.activitiesTable.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + [self addSubview:self.activitiesTable]; +} + +- (void)refreshWithActivities:(NSArray *)activities { + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + appDelegate.userActivitiesArray = activities; + + [self.activitiesTable reloadData]; + + self.pageFetching = NO; + + [self performSelector:@selector(checkScroll) + withObject:nil + afterDelay:0.1]; +} + +- (void)checkScroll { + NSInteger currentOffset = self.activitiesTable.contentOffset.y; + NSInteger maximumOffset = self.activitiesTable.contentSize.height - self.activitiesTable.frame.size.height; + + if (maximumOffset - currentOffset <= 60.0) { + [self fetchActivitiesDetail:self.activitiesPage + 1]; + } +} + +#pragma mark - +#pragma mark Get Interactions + +- (void)fetchActivitiesDetail:(int)page { + + // if there is no social profile, we are DONE +// if ([[appDelegate.dictUserProfile allKeys] count] == 0) { +// self.pageFinished = YES; +// [self.activitiesTable reloadData]; +// return; +// } else { +// if (page == 1) { +// self.pageFinished = NO; +// } +// } + + if (page == 1) { + self.pageFetching = NO; + self.pageFinished = NO; + appDelegate.userActivitiesArray = nil; + } + if (!self.pageFetching && !self.pageFinished) { + self.activitiesPage = page; + self.pageFetching = YES; + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + NSString *urlString = [NSString stringWithFormat:@ + "http://%@/social/activities?user_id=%@&page=%i&limit=10" + "&category=signup&category=star&category=feedsub&category=follow&category=comment_reply&category=comment_like&category=sharedstory", + NEWSBLUR_URL, + [appDelegate.dictUserProfile objectForKey:@"user_id"], + page]; + + NSURL *url = [NSURL URLWithString:urlString]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + + [request setDidFinishSelector:@selector(finishLoadActivities:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setDelegate:self]; + [request startAsynchronous]; + } +} + +- (void)finishLoadActivities:(ASIHTTPRequest *)request { + self.pageFetching = NO; + NSString *responseString = [request responseString]; + NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + // check for last page + if (![[results objectForKey:@"has_next_page"] intValue]) { + self.pageFinished = YES; + } + + NSArray *newActivities = [results objectForKey:@"activities"]; + NSMutableArray *confirmedActivities = [NSMutableArray array]; + if ([appDelegate.userActivitiesArray count]) { + NSMutableSet *activitiesDate = [NSMutableSet set]; + for (id activity in appDelegate.userActivitiesArray) { + [activitiesDate addObject:[activity objectForKey:@"date"]]; + } + for (id activity in newActivities) { + if (![activitiesDate containsObject:[activity objectForKey:@"date"]]) { + [confirmedActivities addObject:activity]; + } + } + } else { + confirmedActivities = [newActivities copy]; + } + + if (self.activitiesPage == 1) { + appDelegate.userActivitiesArray = confirmedActivities; + } else { + appDelegate.userActivitiesArray = [appDelegate.userActivitiesArray arrayByAddingObjectsFromArray:newActivities]; + } + + [self refreshWithActivities:appDelegate.userActivitiesArray]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +#pragma mark - +#pragma mark Table View - Interactions List + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + int activitesCount = [appDelegate.userActivitiesArray count]; + return activitesCount + 1; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + int activitiesCount = [appDelegate.userActivitiesArray count]; + if (indexPath.row >= activitiesCount) { + return MINIMUM_ACTIVITY_HEIGHT; + } + + ActivityCell *activityCell = [[ActivityCell alloc] init]; + + NSMutableDictionary *userProfile = [appDelegate.dictUserProfile mutableCopy]; + [userProfile setValue:@"You" forKey:@"username"]; + + int height = [activityCell setActivity:[appDelegate.userActivitiesArray + objectAtIndex:(indexPath.row)] + withUserProfile:userProfile + withWidth:self.frame.size.width - 20] + 30; + + return height; + +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + ActivityCell *cell = [tableView + dequeueReusableCellWithIdentifier:@"ActivityCell"]; + if (cell == nil) { + cell = [[ActivityCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:@"ActivityCell"]; + } + + if (indexPath.row >= [appDelegate.userActivitiesArray count]) { + // add in loading cell + return [self makeLoadingCell]; + } else { + + NSMutableDictionary *userProfile = [appDelegate.dictUserProfile mutableCopy]; + [userProfile setValue:@"You" forKey:@"username"]; + + NSDictionary *activitiy = [appDelegate.userActivitiesArray + objectAtIndex:(indexPath.row)]; + NSString *category = [activitiy objectForKey:@"category"]; + if ([category isEqualToString:@"follow"]) { + cell.accessoryType = UITableViewCellAccessoryNone; + } else if ([category isEqualToString:@"star"] || + [category isEqualToString:@"signup"]){ + cell.accessoryType = UITableViewCellAccessoryNone; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + } else { + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + + 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]; + if (indexPath.row < activitiesCount) { + NSDictionary *activity = [appDelegate.userActivitiesArray objectAtIndex:indexPath.row]; + NSString *category = [activity objectForKey:@"category"]; + if ([category isEqualToString:@"follow"]) { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + NSString *userId = [NSString stringWithFormat:@"%@", [[activity objectForKey:@"with_user"] objectForKey:@"user_id"]]; + appDelegate.activeUserProfileId = userId; + + NSString *username = [NSString stringWithFormat:@"%@", [[activity objectForKey:@"with_user"] objectForKey:@"username"]]; + appDelegate.activeUserProfileName = username; + + // pass cell to the show UserProfile + ActivityCell *cell = (ActivityCell *)[tableView cellForRowAtIndexPath:indexPath]; + [appDelegate showUserProfileModal:cell]; + } else if ([category isEqualToString:@"comment_reply"] || + [category isEqualToString:@"comment_like"]) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [[activity objectForKey:@"with_user"] objectForKey:@"id"]]; + NSString *contentIdStr = [NSString stringWithFormat:@"%@", [activity objectForKey:@"content_id"]]; + [appDelegate loadTryFeedDetailView:feedIdStr withStory:contentIdStr isSocial:YES withUser:[activity objectForKey:@"with_user"] showFindingStory:YES]; + appDelegate.tryFeedCategory = category; + } else if ([category isEqualToString:@"sharedstory"]) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"id"]]; + NSString *contentIdStr = [NSString stringWithFormat:@"%@", [activity objectForKey:@"content_id"]]; + [appDelegate loadTryFeedDetailView:feedIdStr withStory:contentIdStr isSocial:YES withUser:[activity objectForKey:@"with_user"] showFindingStory:YES]; + appDelegate.tryFeedCategory = category; + + } else if ([category isEqualToString:@"feedsub"]) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [activity objectForKey:@"feed_id"]]; + NSString *contentIdStr = nil; + [appDelegate loadTryFeedDetailView:feedIdStr withStory:contentIdStr isSocial:NO withUser:[activity objectForKey:@"with_user"] showFindingStory:NO]; + appDelegate.tryFeedCategory = category; + + } + + // have the selected cell deselect + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } +} + +- (UITableViewCell *)makeLoadingCell { + UITableViewCell *cell = [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:@"NoReuse"]; + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + if (self.pageFinished) { + UIImage *img = [UIImage imageNamed:@"fleuron.png"]; + UIImageView *fleuron = [[UIImageView alloc] initWithImage:img]; + int height = MINIMUM_ACTIVITY_HEIGHT; + + fleuron.frame = CGRectMake(0, 0, self.frame.size.width, height); + fleuron.contentMode = UIViewContentModeCenter; + [cell.contentView addSubview:fleuron]; + fleuron.backgroundColor = [UIColor whiteColor]; + } else { + cell.textLabel.text = @"Loading..."; + + UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + UIImage *spacer = [UIImage imageNamed:@"spacer"]; + UIGraphicsBeginImageContext(spinner.frame.size); + [spacer drawInRect:CGRectMake(0, 0, spinner.frame.size.width,spinner.frame.size.height)]; + UIImage* resizedSpacer = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + cell.imageView.image = resizedSpacer; + [cell.imageView addSubview:spinner]; + [spinner startAnimating]; + } + + return cell; +} + +- (void)scrollViewDidScroll: (UIScrollView *)scroll { + [self checkScroll]; +} + +@end diff --git a/media/iphone/Classes/AddSiteAutocompleteCell.h b/media/ios/Classes/AddSiteAutocompleteCell.h similarity index 63% rename from media/iphone/Classes/AddSiteAutocompleteCell.h rename to media/ios/Classes/AddSiteAutocompleteCell.h index fc2e34b31..7247b352f 100644 --- a/media/iphone/Classes/AddSiteAutocompleteCell.h +++ b/media/ios/Classes/AddSiteAutocompleteCell.h @@ -14,8 +14,8 @@ UILabel *feedSubs; } -@property (nonatomic, retain) IBOutlet UILabel *feedTitle; -@property (nonatomic, retain) IBOutlet UILabel *feedUrl; -@property (nonatomic, retain) IBOutlet UILabel *feedSubs; +@property (nonatomic) IBOutlet UILabel *feedTitle; +@property (nonatomic) IBOutlet UILabel *feedUrl; +@property (nonatomic) IBOutlet UILabel *feedSubs; @end diff --git a/media/iphone/Classes/AddSiteAutocompleteCell.m b/media/ios/Classes/AddSiteAutocompleteCell.m similarity index 85% rename from media/iphone/Classes/AddSiteAutocompleteCell.m rename to media/ios/Classes/AddSiteAutocompleteCell.m index 78edf05b3..c83f5ba27 100644 --- a/media/iphone/Classes/AddSiteAutocompleteCell.m +++ b/media/ios/Classes/AddSiteAutocompleteCell.m @@ -29,12 +29,6 @@ } -- (void)dealloc { - [feedTitle release]; - [feedUrl release]; - [feedSubs release]; - [super dealloc]; -} @end diff --git a/media/ios/Classes/AddSiteTableCell.h b/media/ios/Classes/AddSiteTableCell.h new file mode 100644 index 000000000..ded6c25fd --- /dev/null +++ b/media/ios/Classes/AddSiteTableCell.h @@ -0,0 +1,34 @@ +// +// AddSiteTableCell.h +// NewsBlur +// +// Created by Roy Yang on 8/7/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ABTableViewCell.h" + +@interface AddSiteTableCell : ABTableViewCell { + NewsBlurAppDelegate *appDelegate; + + NSString *siteTitle; + NSString *siteUrl; + NSString *siteSubscribers; + + UIImage *siteFavicon; + + UIColor *feedColorBar; + UIColor *feedColorBarTopBorder; +} + +@property (nonatomic) NSString *siteTitle; +@property (nonatomic) NSString *siteUrl; +@property (nonatomic) NSString *siteSubscribers; +@property (nonatomic) UIImage *siteFavicon; + +@property (nonatomic) UIColor *feedColorBar; +@property (nonatomic) UIColor *feedColorBarTopBorder; + + + +@end diff --git a/media/ios/Classes/AddSiteTableCell.m b/media/ios/Classes/AddSiteTableCell.m new file mode 100644 index 000000000..b772d8f8b --- /dev/null +++ b/media/ios/Classes/AddSiteTableCell.m @@ -0,0 +1,152 @@ +// +// AddSiteTableCell.m +// NewsBlur +// +// Created by Samuel Clay on 7/14/10. +// Copyright 2010 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "AddSiteTableCell.h" +#import "ABTableViewCell.h" +#import "UIView+TKCategory.h" +#import "Utilities.h" + +static UIFont *textFont = nil; +static UIFont *indicatorFont = nil; + +@implementation AddSiteTableCell + +@synthesize siteTitle; +@synthesize siteUrl; +@synthesize siteFavicon; +@synthesize feedColorBar; +@synthesize feedColorBarTopBorder; +@synthesize siteSubscribers; + +#define leftMargin 39 +#define rightMargin 18 + + ++ (void)initialize { + if (self == [AddSiteTableCell class]) { + textFont = [UIFont boldSystemFontOfSize:18]; + indicatorFont = [UIFont boldSystemFontOfSize:12]; + } +} + +- (void)drawContentView:(CGRect)r highlighted:(BOOL)highlighted { + int adjustForSocial = 3; + + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGRect rect = CGRectInset(r, 12, 12); + rect.size.width -= 18; // Scrollbar padding + + // set the background color + UIColor *backgroundColor; + if (self.selected || self.highlighted) { + backgroundColor = UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR); + } else { + backgroundColor = [UIColor whiteColor]; + } + [backgroundColor set]; + + CGContextFillRect(context, r); + + // set site title + UIColor *textColor; + UIFont *font; + + font = [UIFont fontWithName:@"Helvetica-Bold" size:14]; + textColor = UIColorFromRGB(0x606060); + + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); //0x686868 + } + [textColor set]; + + [self.siteTitle + drawInRect:CGRectMake(leftMargin, 6, rect.size.width - rightMargin, 21) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + + textColor = UIColorFromRGB(0x333333); + font = [UIFont fontWithName:@"Helvetica-Bold" size:12]; + + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); + } + [textColor set]; + + + // url + + // site subscribers + + textColor = UIColorFromRGB(0x262c6c); + font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; + + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); + } + [textColor set]; + + [self.siteSubscribers + drawInRect:CGRectMake(leftMargin + (rect.size.width - rightMargin) / 2 - 10, 42 + adjustForSocial, (rect.size.width - rightMargin) / 2 + 10, 15.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentRight]; + + // feed bar + CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor])); + + CGContextSetLineWidth(context, 10.0f); + CGContextBeginPath(context); + CGContextMoveToPoint(context, 5.0f, 1.0f); + CGContextAddLineToPoint(context, 5.0f, 81.0f); + CGContextStrokePath(context); + + CGContextSetLineWidth(context, 1.0f); + if (self.highlighted || self.selected) { + // top border + UIColor *blue = UIColorFromRGB(0x6eadf5); + + CGContextSetStrokeColor(context, CGColorGetComponents([blue CGColor])); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0, 0.5f); + CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f); + CGContextStrokePath(context); + + // bottom border + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0, self.bounds.size.height - 0.5f); + CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height - 0.5f); + CGContextStrokePath(context); + } else { + // top border + UIColor *gray = UIColorFromRGB(0xcccccc); + + CGContextSetStrokeColor(context, CGColorGetComponents([gray CGColor])); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 10.0f, 0.5f); + CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f); + CGContextStrokePath(context); + + // feed bar border + CGContextSetStrokeColor(context, CGColorGetComponents([feedColorBarTopBorder CGColor])); + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0.0f, 0.5f); + CGContextAddLineToPoint(context, 10.0, 0.5f); + CGContextStrokePath(context); + } + + // site favicon + [self.siteFavicon drawInRect:CGRectMake(18.0, 6.0, 16.0, 16.0)]; + +} + +@end diff --git a/media/iphone/Classes/AddSiteViewController.h b/media/ios/Classes/AddSiteViewController.h similarity index 54% rename from media/iphone/Classes/AddSiteViewController.h rename to media/ios/Classes/AddSiteViewController.h index 5a4bed08b..f3d4e2860 100644 --- a/media/iphone/Classes/AddSiteViewController.h +++ b/media/ios/Classes/AddSiteViewController.h @@ -48,25 +48,26 @@ - (void)showFolderPicker; - (void)hideFolderPicker; - (IBAction)checkSiteAddress; +- (void)reloadSearchResults; -@property (nonatomic, retain) IBOutlet NewsBlurAppDelegate *appDelegate; -@property (nonatomic, retain) IBOutlet UITextField *inFolderInput; -@property (nonatomic, retain) IBOutlet UITextField *addFolderInput; -@property (nonatomic, retain) IBOutlet UITextField *siteAddressInput; +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UITextField *inFolderInput; +@property (nonatomic) IBOutlet UITextField *addFolderInput; +@property (nonatomic) IBOutlet UITextField *siteAddressInput; -@property (nonatomic, retain) IBOutlet UIBarButtonItem *addButton; -@property (nonatomic, retain) IBOutlet UIBarButtonItem *cancelButton; -@property (nonatomic, retain) IBOutlet UIPickerView *folderPicker; -@property (nonatomic, retain) IBOutlet UITableView *siteTable; -@property (nonatomic, retain) IBOutlet UIScrollView *siteScrollView; -@property (nonatomic, retain) NSMutableData * jsonString; -@property (nonatomic, retain) NSMutableArray *autocompleteResults; +@property (nonatomic) IBOutlet UIBarButtonItem *addButton; +@property (nonatomic) IBOutlet UIBarButtonItem *cancelButton; +@property (nonatomic) IBOutlet UIPickerView *folderPicker; +@property (nonatomic) IBOutlet UITableView *siteTable; +@property (nonatomic) IBOutlet UIScrollView *siteScrollView; +@property (nonatomic) NSMutableData * jsonString; +@property (nonatomic) NSMutableArray *autocompleteResults; -@property (nonatomic, retain) IBOutlet UINavigationBar *navBar; -@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *activityIndicator; -@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *siteActivityIndicator; -@property (nonatomic, retain) IBOutlet UILabel *addingLabel; -@property (nonatomic, retain) IBOutlet UILabel *errorLabel; -@property (nonatomic, retain) IBOutlet UISegmentedControl *addTypeControl; +@property (nonatomic) IBOutlet UINavigationBar *navBar; +@property (nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; +@property (nonatomic) IBOutlet UIActivityIndicatorView *siteActivityIndicator; +@property (nonatomic) IBOutlet UILabel *addingLabel; +@property (nonatomic) IBOutlet UILabel *errorLabel; +@property (nonatomic) IBOutlet UISegmentedControl *addTypeControl; @end diff --git a/media/iphone/Classes/AddSiteViewController.m b/media/ios/Classes/AddSiteViewController.m similarity index 83% rename from media/iphone/Classes/AddSiteViewController.m rename to media/ios/Classes/AddSiteViewController.m index b591201d7..f72256732 100644 --- a/media/iphone/Classes/AddSiteViewController.m +++ b/media/ios/Classes/AddSiteViewController.m @@ -11,8 +11,16 @@ #import "NewsBlurAppDelegate.h" #import "ASIHTTPRequest.h" #import "ASIFormDataRequest.h" +#import "NBContainerViewController.h" #import "JSON.h" +@interface AddSiteViewController() + +@property (nonatomic) NSString *activeTerm_; +@property (nonatomic, strong) NSMutableDictionary *searchResults_; + +@end + @implementation AddSiteViewController @synthesize appDelegate; @@ -32,9 +40,11 @@ @synthesize addingLabel; @synthesize errorLabel; @synthesize addTypeControl; +@synthesize activeTerm_; +@synthesize searchResults_; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { } return self; @@ -44,16 +54,13 @@ UIImageView *folderImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"folder.png"]]; [inFolderInput setLeftView:folderImage]; [inFolderInput setLeftViewMode:UITextFieldViewModeAlways]; - [folderImage release]; UIImageView *folderImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"folder.png"]]; [addFolderInput setLeftView:folderImage2]; [addFolderInput setLeftViewMode:UITextFieldViewModeAlways]; - [folderImage2 release]; UIImageView *urlImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"world.png"]]; [siteAddressInput setLeftView:urlImage]; [siteAddressInput setLeftViewMode:UITextFieldViewModeAlways]; - [urlImage release]; navBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; @@ -62,6 +69,8 @@ siteAddressInput.frame.size.width, siteAddressInput.frame.size.height); + self.activeTerm_ = @""; + self.searchResults_ = [[NSMutableDictionary alloc] init]; [super viewDidLoad]; } @@ -74,38 +83,48 @@ [super viewWillAppear:animated]; } +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + + return NO; +} + - (void)viewDidAppear:(BOOL)animated { [self.activityIndicator stopAnimating]; [super viewDidAppear:animated]; [self showFolderPicker]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + NSLog(@"%@", self.siteTable.frame); + self.siteTable.hidden = NO; + self.siteScrollView.frame = CGRectMake(self.siteScrollView.frame.origin.x, + self.siteScrollView.frame.origin.y, + 320, + 295); + } } - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; - + // Release any cached data, images, etc that aren't in use. } -- (void)dealloc { - [appDelegate release]; - [inFolderInput release]; - [addFolderInput release]; - [siteAddressInput release]; - [addButton release]; - [cancelButton release]; - [folderPicker release]; - [siteTable release]; - [siteScrollView release]; - [jsonString release]; - [autocompleteResults release]; - [navBar release]; - [super dealloc]; -} - (IBAction)doCancelButton { - [appDelegate.addSiteViewController dismissModalViewControllerAnimated:YES]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController hidePopover]; + } else { + [appDelegate.addSiteViewController dismissModalViewControllerAnimated:YES]; + + } } - (IBAction)doAddButton { @@ -160,6 +179,7 @@ - (IBAction)checkSiteAddress { NSString *phrase = siteAddressInput.text; + if ([phrase length] == 0) { [UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ @@ -168,6 +188,12 @@ return; } + if ([self.searchResults_ objectForKey:phrase]) { + self.autocompleteResults = [self.searchResults_ objectForKey:phrase]; + [self reloadSearchResults]; + return; + } + int periodLoc = [phrase rangeOfString:@"."].location; if (periodLoc != NSNotFound && siteAddressInput.returnKeyType != UIReturnKeyDone) { // URL @@ -182,7 +208,7 @@ } [self.siteActivityIndicator startAnimating]; - NSString *urlString = [NSString stringWithFormat:@"http://%@/rss_feeds/feed_autocomplete?term=%@", + NSString *urlString = [NSString stringWithFormat:@"http://%@/rss_feeds/feed_autocomplete?term=%@&v=2", NEWSBLUR_URL, [phrase stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [ASIHTTPRequest requestWithURL:url]; @@ -193,8 +219,30 @@ - (void)autocompleteSite:(ASIHTTPRequest *)request { NSString *responseString = [request responseString]; - autocompleteResults = [[NSMutableArray alloc] initWithArray:[responseString JSONValue]]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + NSString *query = [NSString stringWithFormat:@"%@", [results objectForKey:@"term"]]; + NSString *phrase = siteAddressInput.text; + + // cache the results + [self.searchResults_ setValue:[results objectForKey:@"feeds"] forKey:query]; + + if ([phrase isEqualToString:query]) { + self.autocompleteResults = [results objectForKey:@"feeds"]; + [self reloadSearchResults]; + } + +// NSRange range = [query rangeOfString : activeTerm_]; +// BOOL found = (range.location != NSNotFound); +} + +- (void)reloadSearchResults { if ([siteAddressInput.text length] > 0 && [autocompleteResults count] > 0) { [UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ @@ -208,11 +256,13 @@ } [self.siteActivityIndicator stopAnimating]; + self.siteTable.hidden = NO; [siteTable reloadData]; } - (IBAction)addSite { [self hideFolderPicker]; + self.siteTable.hidden = YES; [siteAddressInput resignFirstResponder]; [self.addingLabel setHidden:NO]; [self.addingLabel setText:@"Adding site..."]; @@ -244,11 +294,15 @@ [self.errorLabel setText:[results valueForKey:@"message"]]; [self.errorLabel setHidden:NO]; } else { - [appDelegate.addSiteViewController dismissModalViewControllerAnimated:YES]; - [appDelegate reloadFeedsView:YES]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController hidePopover]; + } else { + [appDelegate.addSiteViewController dismissModalViewControllerAnimated:YES]; + + } + [appDelegate reloadFeedsView:NO]; } - [results release]; } - (NSString *)extractParentFolder { @@ -276,7 +330,9 @@ NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; NSString *parent_folder = [self extractParentFolder]; - [request setPostValue:parent_folder forKey:@"parent_folder"]; + if (![parent_folder isEqualToString:@"- Top Level -"]) { + [request setPostValue:parent_folder forKey:@"parent_folder"]; + } [request setPostValue:[addFolderInput text] forKey:@"folder"]; [request setDelegate:self]; [request setDidFinishSelector:@selector(finishAddFolder:)]; @@ -300,7 +356,6 @@ [appDelegate reloadFeedsView:YES]; } - [results release]; } - (void)requestFailed:(ASIHTTPRequest *)request @@ -311,6 +366,7 @@ NSError *error = [request error]; NSLog(@"Error: %@", error); [self.errorLabel setText:error.localizedDescription]; + self.siteTable.hidden = YES; } #pragma mark - @@ -365,7 +421,7 @@ - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { - return [[appDelegate dictFoldersArray] count]; + return [[appDelegate dictFoldersArray] count] - 1; } - (NSString *)pickerView:(UIPickerView *)pickerView @@ -374,7 +430,7 @@ numberOfRowsInComponent:(NSInteger)component { if (row == 0) { return @"— Top Level —"; } else { - return [[appDelegate dictFoldersArray] objectAtIndex:row]; + return [[appDelegate dictFoldersArray] objectAtIndex:row + 1]; } } @@ -385,7 +441,7 @@ numberOfRowsInComponent:(NSInteger)component { if (row == 0) { folder_title = @"- Top Level -"; } else { - folder_title = [[appDelegate dictFoldersArray] objectAtIndex:row]; + folder_title = [[appDelegate dictFoldersArray] objectAtIndex:row + 1]; } [inFolderInput setText:folder_title]; } @@ -400,6 +456,7 @@ numberOfRowsInComponent:(NSInteger)component { folderPicker.frame = CGRectMake(0, self.view.bounds.size.height - folderPicker.frame.size.height, folderPicker.frame.size.width, folderPicker.frame.size.height); }]; } + self.siteTable.hidden = YES; } - (void)hideFolderPicker { @@ -434,7 +491,7 @@ numberOfRowsInComponent:(NSInteger)component { NSDictionary *result = [autocompleteResults objectAtIndex:indexPath.row]; int subs = [[result objectForKey:@"num_subscribers"] intValue]; - NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setPositiveFormat:@"#,###"]; NSNumber *theScore = [NSNumber numberWithInt:subs]; cell.feedTitle.text = [result objectForKey:@"label"]; @@ -455,4 +512,4 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath { }]; } -@end +@end \ No newline at end of file diff --git a/media/ios/Classes/AuthorizeServicesViewController.h b/media/ios/Classes/AuthorizeServicesViewController.h new file mode 100644 index 000000000..f2cec5409 --- /dev/null +++ b/media/ios/Classes/AuthorizeServicesViewController.h @@ -0,0 +1,26 @@ +// +// AuthorizeServicesViewController.h +// NewsBlur +// +// Created by Roy Yang on 8/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface AuthorizeServicesViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; + NSString *url; + NSString *type; +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (nonatomic) NSString *url; +@property (nonatomic) NSString *type; + +@property (weak, nonatomic) IBOutlet UIWebView *webView; + +- (void)showError:(NSString *)error; +@end diff --git a/media/ios/Classes/AuthorizeServicesViewController.m b/media/ios/Classes/AuthorizeServicesViewController.m new file mode 100644 index 000000000..ec357e0a9 --- /dev/null +++ b/media/ios/Classes/AuthorizeServicesViewController.m @@ -0,0 +1,113 @@ +// +// AuthorizeServicesViewController.m +// NewsBlur +// +// Created by Roy Yang on 8/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "AuthorizeServicesViewController.h" +#import "NewsBlurAppDelegate.h" +#import "FirstTimeUserAddSitesViewController.h" +#import "FirstTimeUserAddFriendsViewController.h" + +@implementation AuthorizeServicesViewController + +@synthesize appDelegate; +@synthesize webView; +@synthesize url; +@synthesize type; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + self.webView.delegate = self; +} + +- (void)viewDidUnload { + [self setWebView:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { + + if ([type isEqualToString:@"google"]) { + self.navigationItem.title = @"Google Reader"; + } else if ([type isEqualToString:@"facebook"]) { + self.navigationItem.title = @"Facebook"; + } else if ([type isEqualToString:@"twitter"]) { + self.navigationItem.title = @"Twitter"; + } + NSString *urlAddress = [NSString stringWithFormat:@"http://%@%@", NEWSBLUR_URL, url]; + NSURL *fullUrl = [NSURL URLWithString:urlAddress]; + NSURLRequest *requestObj = [NSURLRequest requestWithURL:fullUrl]; + [self.webView loadRequest:requestObj]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + NSString *URLString = [[request URL] absoluteString]; + NSLog(@"URL STRING IS %@", URLString); + + if ([URLString isEqualToString:[NSString stringWithFormat:@"http://%@/", NEWSBLUR_URL]]) { + + + NSString *error = [self.webView stringByEvaluatingJavaScriptFromString:@"NEWSBLUR.error"]; + + [self.navigationController popViewControllerAnimated:YES]; + if ([type isEqualToString:@"google"]) { + if (error.length) { + [appDelegate.firstTimeUserAddSitesViewController importFromGoogleReaderFailed:error]; + } else { + [appDelegate.firstTimeUserAddSitesViewController importFromGoogleReader]; + } + + } else if ([type isEqualToString:@"facebook"]) { + if (error.length) { + [self showError:error]; + } else { + [appDelegate.firstTimeUserAddFriendsViewController selectFacebookButton]; + } + + } else if ([type isEqualToString:@"twitter"]) { + if (error.length) { + [self showError:error]; + } else { + [appDelegate.firstTimeUserAddFriendsViewController selectTwitterButton]; + } + } + return NO; + } + +// // for failed google reader authorization +// if ([URLString hasPrefix:[NSString stringWithFormat:@"http://%@/import/callback", NEWSBLUR_URL]]) { +// [self.navigationController popViewControllerAnimated:YES]; +// [appDelegate.firstTimeUserAddSitesViewController importFromGoogleReaderFailed]; +// return NO; +// } + + + return YES; +} + +- (void)showError:(NSString *)error { + [appDelegate.firstTimeUserAddFriendsViewController changeMessaging:error]; +} + + +@end diff --git a/media/ios/Classes/AuthorizeServicesViewController.xib b/media/ios/Classes/AuthorizeServicesViewController.xib new file mode 100644 index 000000000..6daee9350 --- /dev/null +++ b/media/ios/Classes/AuthorizeServicesViewController.xib @@ -0,0 +1,169 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBProxyObject + IBUIView + IBUIWebView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 274 + {320, 460} + + + _NS:9 + + 1 + MSAxIDEAA + + IBCocoaTouchFramework + 1 + YES + + + {{0, 20}, {320, 460}} + + + + + 3 + MQA + + 2 + + + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + webView + + + + 5 + + + + + + 0 + + + + + + 1 + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 4 + + + + + + + AuthorizeServicesViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 5 + + + + + AuthorizeServicesViewController + UIViewController + + webView + UIWebView + + + webView + + webView + UIWebView + + + + IBProjectSource + ./Classes/AuthorizeServicesViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + 1181 + + diff --git a/media/iphone/Classes/BaseViewController.h b/media/ios/Classes/BaseViewController.h similarity index 100% rename from media/iphone/Classes/BaseViewController.h rename to media/ios/Classes/BaseViewController.h diff --git a/media/iphone/Classes/BaseViewController.m b/media/ios/Classes/BaseViewController.m similarity index 94% rename from media/iphone/Classes/BaseViewController.m rename to media/ios/Classes/BaseViewController.m index 10292beab..88183eac9 100644 --- a/media/iphone/Classes/BaseViewController.m +++ b/media/ios/Classes/BaseViewController.m @@ -39,7 +39,6 @@ for (ASIHTTPRequest* r in toremove) { [requests removeObject:r]; } - [toremove release]; } - (void) cancelRequests { @@ -68,8 +67,8 @@ [MBProgressHUD hideHUDForView:self.view animated:YES]; MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - [HUD setCustomView:[[[UIImageView alloc] - initWithImage:[UIImage imageNamed:@"warning.gif"]] autorelease]]; + [HUD setCustomView:[[UIImageView alloc] + initWithImage:[UIImage imageNamed:@"warning.gif"]]]; [HUD setMode:MBProgressHUDModeCustomView]; HUD.labelText = errorMessage; [HUD hide:YES afterDelay:1]; @@ -99,9 +98,7 @@ - (void)dealloc { [self cancelRequests]; - [requests release]; - [super dealloc]; } diff --git a/media/ios/Classes/DashboardViewController.h b/media/ios/Classes/DashboardViewController.h new file mode 100644 index 000000000..3e408768a --- /dev/null +++ b/media/ios/Classes/DashboardViewController.h @@ -0,0 +1,39 @@ +// +// DashboardViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class InteractionsModule; +@class ActivityModule; +@class FeedbackModule; + +@interface DashboardViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; + InteractionsModule *interactionsModule; + ActivityModule *activitiesModule; + UIWebView *feedbackWebView; + UIToolbar *toolbar; + UIToolbar *topToolbar; + UISegmentedControl *segmentedButton; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet InteractionsModule *interactionsModule; +@property (nonatomic) IBOutlet ActivityModule *activitiesModule; +@property (nonatomic) IBOutlet UIWebView *feedbackWebView; + +@property (nonatomic) IBOutlet UIToolbar *topToolbar; +@property (nonatomic) IBOutlet UIToolbar *toolbar; +@property (nonatomic) IBOutlet UISegmentedControl *segmentedButton; + +- (IBAction)doLogout:(id)sender; +- (void)refreshInteractions; +- (void)refreshActivity; +- (IBAction)tapSegmentedButton:(id)sender; +@end diff --git a/media/ios/Classes/DashboardViewController.m b/media/ios/Classes/DashboardViewController.m new file mode 100644 index 000000000..3cfa6e560 --- /dev/null +++ b/media/ios/Classes/DashboardViewController.m @@ -0,0 +1,138 @@ +// +// DashboardViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "DashboardViewController.h" +#import "NewsBlurAppDelegate.h" +#import "ActivityModule.h" +#import "InteractionsModule.h" +#import "UserProfileViewController.h" + +#define FEEDBACK_URL @"http://dev.newsblur.com/about" + +@implementation DashboardViewController + +@synthesize appDelegate; +@synthesize interactionsModule; +@synthesize activitiesModule; +@synthesize feedbackWebView; +@synthesize topToolbar; +@synthesize toolbar; +@synthesize segmentedButton; + + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + self.toolbar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + self.interactionsModule.hidden = NO; + self.activitiesModule.hidden = YES; + self.feedbackWebView.hidden = YES; + 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; + + + NSString *urlAddress = FEEDBACK_URL; + //Create a URL object. + NSURL *url = [NSURL URLWithString:urlAddress]; + //URL Requst Object + NSURLRequest *requestObj = [NSURLRequest requestWithURL:url]; + //Load the request in the UIWebView. + [self.feedbackWebView loadRequest:requestObj]; + +} + +- (void)viewDidUnload { + [self setAppDelegate:nil]; + [self setInteractionsModule:nil]; + [self setActivitiesModule:nil]; + [self setToolbar:nil]; + [self setSegmentedButton:nil]; + [self setFeedbackWebView:nil]; + [self setTopToolbar:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { + +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (IBAction)doLogout:(id)sender { + [appDelegate confirmLogout]; +} + +# pragma mark +# pragma mark Navigation + +- (IBAction)tapSegmentedButton:(id)sender { + int selectedSegmentIndex = [self.segmentedButton selectedSegmentIndex]; + + if (selectedSegmentIndex == 0) { + self.interactionsModule.hidden = NO; + self.activitiesModule.hidden = YES; + self.feedbackWebView.hidden = YES; + } else if (selectedSegmentIndex == 1) { + self.interactionsModule.hidden = YES; + self.activitiesModule.hidden = NO; + self.feedbackWebView.hidden = YES; + } else if (selectedSegmentIndex == 2) { + self.interactionsModule.hidden = YES; + self.activitiesModule.hidden = YES; + self.feedbackWebView.hidden = NO; + } +} + +# pragma mark +# pragma mark Interactions + +- (void)refreshInteractions { + [self.interactionsModule fetchInteractionsDetail:1]; +} + +# pragma mark +# pragma mark Activities + +- (void)refreshActivity { + [self.activitiesModule fetchActivitiesDetail:1]; +} + +# pragma mark +# pragma mark Feedback + +- (BOOL)webView:(UIWebView *)webView +shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType { + NSURL *url = [request URL]; + NSString *urlString = [NSString stringWithFormat:@"%@", url]; + + if ([urlString isEqualToString: FEEDBACK_URL]){ + return YES; + } else { + return NO; + } +} +@end \ No newline at end of file diff --git a/media/ios/Classes/DashboardViewController.xib b/media/ios/Classes/DashboardViewController.xib new file mode 100644 index 000000000..e3a17bf42 --- /dev/null +++ b/media/ios/Classes/DashboardViewController.xib @@ -0,0 +1,1873 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBProxyObject + IBUIBarButtonItem + IBUILabel + IBUIToolbar + IBUIWebView + IBUISegmentedControl + IBUIView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 274 + + + + 290 + {478, 44} + + + + _NS:9 + NO + NO + IBIPadFramework + + + 1 + MCAwLjUwMTk2MDgxNCAxAA + + + + + 293 + {478, 44} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + NewsBlur + + 3 + MQA + + + 0 + 10 + 1 + + 2 + 16 + + + Helvetica-Bold + 16 + 16 + + + + + 274 + {{0, 44}, {478, 872}} + + + + _NS:9 + + 3 + MQA + + 2 + + + IBIPadFramework + + + + 274 + {{0, 44}, {478, 872}} + + + + _NS:9 + + 3 + MQA + + + IBIPadFramework + + + + 274 + {{0, 44}, {478, 872}} + + + + _NS:9 + + 1 + MSAxIDEAA + + IBIPadFramework + 1 + YES + + + + 266 + + + + 292 + {{119, 8}, {240, 30}} + + + + _NS:9 + NO + IBIPadFramework + 2 + 2 + 0 + + Interactions + Your Activities + + + + + + + + + + + {0, 0} + {0, 0} + + + + + + + + {{0, 916}, {478, 44}} + + + + _NS:9 + NO + NO + IBIPadFramework + + + IBIPadFramework + + 5 + + + IBIPadFramework + 240 + + + + + IBIPadFramework + + 5 + + + + + {478, 960} + + + + + NO + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 3 + + + + activitiesModule + + + + 39 + + + + segmentedButton + + + + 44 + + + + interactionsModule + + + + 29 + + + + topToolbar + + + + 54 + + + + toolbar + + + + 55 + + + + feedbackWebView + + + + 50 + + + + tapSegmentedButton: + + + 13 + + 46 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + + + + + + + 22 + + + + + + + + + + 25 + + + + + + + + 26 + + + + + 27 + + + + + 24 + + + + + 38 + + + + + + 28 + + + + + 51 + + + + + + 59 + + + + + 49 + + + + + + + DashboardViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + InteractionsModule + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + ActivityModule + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 60 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + tapCancelButton: + UIBarButtonItem + + + tapCancelButton: + + tapCancelButton: + UIBarButtonItem + + + + NewsBlurAppDelegate + UITableView + UIToolbar + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + toolbar + UIToolbar + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UITableView + UISearchBar + UISearchDisplayController + + + + appDelegate + NewsBlurAppDelegate + + + friendsTable + UITableView + + + searchBar + UISearchBar + + + searchDisplayController + UISearchDisplayController + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + GoogleReaderViewController + UIViewController + + tapCancelButton: + id + + + tapCancelButton: + + tapCancelButton: + id + + + + NewsBlurAppDelegate + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + webView + UIWebView + + + + IBProjectSource + ./Classes/GoogleReaderViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + UINavigationController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + GoogleReaderViewController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findFriendsNavigationController + UINavigationController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + googleReaderViewController + GoogleReaderViewController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + showMenuButton: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + toolbarTitle + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UIBarButtonItem + UIProgressView + UIView + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + 1181 + + diff --git a/media/ios/Classes/DataUtilities.h b/media/ios/Classes/DataUtilities.h new file mode 100644 index 000000000..26f78e8c6 --- /dev/null +++ b/media/ios/Classes/DataUtilities.h @@ -0,0 +1,18 @@ +// +// DataUtilities.h +// NewsBlur +// +// Created by Roy Yang on 7/20/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface DataUtilities : NSObject + ++ (NSArray *)updateUserProfiles:(NSArray *)userProfiles withNewUserProfiles:(NSArray *)newUserProfiles; ++ (NSDictionary *)updateComment:(NSDictionary *)newCommen for:(NewsBlurAppDelegate *)appDelegate; + +@end diff --git a/media/ios/Classes/DataUtilities.m b/media/ios/Classes/DataUtilities.m new file mode 100644 index 000000000..12145ba1d --- /dev/null +++ b/media/ios/Classes/DataUtilities.m @@ -0,0 +1,89 @@ +// +// DataUtilities.m +// NewsBlur +// +// Created by Roy Yang on 7/20/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "DataUtilities.h" +#import "NewsBlurAppDelegate.h" + +@implementation DataUtilities + ++ (NSArray *)updateUserProfiles:(NSArray *)userProfiles withNewUserProfiles:(NSArray *)newUserProfiles { + + NSMutableArray *updatedUserProfiles = [userProfiles mutableCopy]; + + for (int i = 0; i < newUserProfiles.count; i++) { + BOOL isInUserProfiles = NO; + NSDictionary *newUser = [newUserProfiles objectAtIndex:i]; + NSString *newUserIdStr = [NSString stringWithFormat:@"%@", [newUser objectForKey:@"user_id"]]; + + for (int j = 0; j < userProfiles.count; j++) { + NSDictionary *user = [userProfiles objectAtIndex:j]; + NSString *userIdStr = [NSString stringWithFormat:@"%@", [user objectForKey:@"user_id"]]; + if ([newUserIdStr isEqualToString:userIdStr]) { + isInUserProfiles = YES; + break; + } + } + + if (!isInUserProfiles) { + [updatedUserProfiles addObject:newUser]; + } + } + return updatedUserProfiles; +} + ++ (NSDictionary *)updateComment:(NSDictionary *)newComment for:(NewsBlurAppDelegate *)appDelegate { + NSDictionary *comment = [newComment objectForKey:@"comment"]; + NSArray *userProfiles = [newComment objectForKey:@"user_profiles"]; + + appDelegate.activeFeedUserProfiles = [DataUtilities + updateUserProfiles:appDelegate.activeFeedUserProfiles + withNewUserProfiles:userProfiles]; + + NSString *commentUserId = [NSString stringWithFormat:@"%@", [comment objectForKey:@"user_id"]]; + BOOL foundComment = NO; + + NSArray *friendComments = [appDelegate.activeStory objectForKey:@"friend_comments"]; + NSMutableArray *newFriendsComments = [[NSMutableArray alloc] init]; + for (int i = 0; i < friendComments.count; i++) { + NSString *userId = [NSString stringWithFormat:@"%@", + [[friendComments objectAtIndex:i] objectForKey:@"user_id"]]; + if([userId isEqualToString:commentUserId]){ + [newFriendsComments addObject:comment]; + foundComment = YES; + } else { + [newFriendsComments addObject:[friendComments objectAtIndex:i]]; + } + } + + // make mutable copy + NSMutableDictionary *newActiveStory = [appDelegate.activeStory mutableCopy]; + + if (!foundComment) { + NSArray *publicComments = [appDelegate.activeStory objectForKey:@"public_comments"]; + NSMutableArray *newPublicComments = [[NSMutableArray alloc] init]; + for (int i = 0; i < publicComments.count; i++) { + NSString *userId = [NSString stringWithFormat:@"%@", + [[publicComments objectAtIndex:i] objectForKey:@"user_id"]]; + if([userId isEqualToString:commentUserId]){ + [newPublicComments addObject:comment]; + } else { + [newPublicComments addObject:[publicComments objectAtIndex:i]]; + } + } + + [newActiveStory setValue:[NSArray arrayWithArray:newPublicComments] forKey:@"public_comments"]; + } else { + [newActiveStory setValue:[NSArray arrayWithArray:newFriendsComments] forKey:@"friend_comments"]; + } + + NSDictionary *newStory = [NSDictionary dictionaryWithDictionary:newActiveStory]; + + return newStory; +} + +@end diff --git a/media/ios/Classes/FeedDetailTableCell.h b/media/ios/Classes/FeedDetailTableCell.h new file mode 100644 index 000000000..e4ff8af15 --- /dev/null +++ b/media/ios/Classes/FeedDetailTableCell.h @@ -0,0 +1,53 @@ +// +// FeedDetailTableCell.h +// NewsBlur +// +// Created by Samuel Clay on 7/14/10. +// Copyright 2010 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" +#import "ABTableViewCell.h" + +@interface FeedDetailTableCell : ABTableViewCell { + NewsBlurAppDelegate *appDelegate; + + // All views + NSString *storyTitle; + NSString *storyAuthor; + NSString *storyDate; + int storyScore; + + // River view + NSString *siteTitle; + UIImage *siteFavicon; + BOOL isRead; + BOOL isShort; + BOOL isRiverOrSocial; + BOOL hasAlpha; + + UIColor *feedColorBar; + UIColor *feedColorBarTopBorder; +} + +@property (nonatomic) NSString *siteTitle; +@property (nonatomic) UIImage *siteFavicon; + +@property (readwrite) int storyScore; + +@property (nonatomic) NSString *storyTitle; +@property (nonatomic) NSString *storyAuthor; +@property (nonatomic) NSString *storyDate; + +@property (nonatomic) UIColor *feedColorBar; +@property (nonatomic) UIColor *feedColorBarTopBorder; + +@property (readwrite) BOOL isRead; +@property (readwrite) BOOL isShort; +@property (readwrite) BOOL isRiverOrSocial; +@property (readwrite) BOOL hasAlpha; + +- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha; + +@end diff --git a/media/ios/Classes/FeedDetailTableCell.m b/media/ios/Classes/FeedDetailTableCell.m new file mode 100644 index 000000000..7dface224 --- /dev/null +++ b/media/ios/Classes/FeedDetailTableCell.m @@ -0,0 +1,278 @@ +// +// FeedDetailTableCell.m +// NewsBlur +// +// Created by Samuel Clay on 7/14/10. +// Copyright 2010 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "FeedDetailTableCell.h" +#import "ABTableViewCell.h" +#import "UIView+TKCategory.h" +#import "Utilities.h" + +static UIFont *textFont = nil; +static UIFont *indicatorFont = nil; + + +@implementation FeedDetailTableCell + +@synthesize storyTitle; +@synthesize storyAuthor; +@synthesize storyDate; +@synthesize storyScore; +@synthesize siteTitle; +@synthesize siteFavicon; +@synthesize isRead; +@synthesize isShort; +@synthesize isRiverOrSocial; +@synthesize feedColorBar; +@synthesize feedColorBarTopBorder; +@synthesize hasAlpha; + + +#define leftMargin 26 +#define rightMargin 18 + + ++ (void) initialize { + if (self == [FeedDetailTableCell class]) { + textFont = [UIFont boldSystemFontOfSize:18]; + indicatorFont = [UIFont boldSystemFontOfSize:12]; + } +} + +- (void)drawContentView:(CGRect)r highlighted:(BOOL)highlighted { + + + int adjustForSocial = 3; + if (self.isRiverOrSocial) { + adjustForSocial = 20; + } + + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGRect rect = CGRectInset(r, 12, 12); + rect.size.width -= 18; // Scrollbar padding + + // set the background color + UIColor *backgroundColor; + if (self.selected || self.highlighted) { + backgroundColor = UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR); + } else { + backgroundColor = UIColorFromRGB(0xf4f4f4); + } + [backgroundColor set]; + + CGContextFillRect(context, r); + NSLog(@"WIDTH is %f", rect.size.width); + // set site title + UIColor *textColor; + UIFont *font; + + if (self.isRead) { + font = [UIFont fontWithName:@"Helvetica" size:11]; + textColor = UIColorFromRGB(0xc0c0c0); + } else { + font = [UIFont fontWithName:@"Helvetica-Bold" size:11]; + textColor = UIColorFromRGB(0x606060); + + } + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); //0x686868 + } + [textColor set]; + + if (self.isRiverOrSocial) { + [self.siteTitle + drawInRect:CGRectMake(leftMargin + 20, 7, rect.size.width - 20, 21) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + + if (self.isRead) { + font = [UIFont fontWithName:@"Helvetica" size:12]; + textColor = UIColorFromRGB(0xc0c0c0); + + } else { + textColor = UIColorFromRGB(0x333333); + font = [UIFont fontWithName:@"Helvetica-Bold" size:12]; + } + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); + } + [textColor set]; + } + + // story title + + CGSize theSize = [self.storyTitle sizeWithFont:font constrainedToSize:CGSizeMake(rect.size.width, 30.0) lineBreakMode:UILineBreakModeTailTruncation]; + + int storyTitleY = 7 + adjustForSocial + ((30 - theSize.height)/2); + if (self.isShort){ + storyTitleY = 7 + adjustForSocial + 2; + } + + [self.storyTitle + drawInRect:CGRectMake(leftMargin, storyTitleY, rect.size.width, theSize.height) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + + int storyAuthorDateY = 41 + adjustForSocial; + if (self.isShort){ + storyAuthorDateY -= 13; + } + + // story author style + + if (self.isRead) { + textColor = UIColorFromRGB(0xc0c0c0); + font = [UIFont fontWithName:@"Helvetica" size:10]; + } else { + textColor = UIColorFromRGB(0x959595); + font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; + } + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); + } + [textColor set]; + + [self.storyAuthor + drawInRect:CGRectMake(leftMargin, storyAuthorDateY, (rect.size.width) / 2 - 10, 15.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + + // story date + if (self.isRead) { + textColor = UIColorFromRGB(0xbabdd1); + font = [UIFont fontWithName:@"Helvetica" size:10]; + } else { + textColor = UIColorFromRGB(0x262c6c); + font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; + } + + if (self.selected || self.highlighted) { + textColor = UIColorFromRGB(0x686868); + } + [textColor set]; + + [self.storyDate + drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentRight]; + + // feed bar + + CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBarTopBorder CGColor])); //feedColorBarTopBorder + if (self.isRead) { + CGContextSetAlpha(context, 0.25); + } + CGContextSetLineWidth(context, 6.0f); + CGContextBeginPath(context); + CGContextMoveToPoint(context, 3.0f, 1.0f); + CGContextAddLineToPoint(context, 3.0f, self.frame.size.height - 1); + CGContextStrokePath(context); + + CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor])); + CGContextBeginPath(context); + CGContextMoveToPoint(context, 9.0f, 1.0f); + CGContextAddLineToPoint(context, 9.0, self.frame.size.height - 1); + CGContextStrokePath(context); + + // reset for borders + + CGContextSetAlpha(context, 1.0); + CGContextSetLineWidth(context, 1.0f); + if (self.highlighted || self.selected) { + // top border + UIColor *blue = UIColorFromRGB(0x6eadf5); + + CGContextSetStrokeColor(context, CGColorGetComponents([blue CGColor])); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0, 0.5f); + CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f); + CGContextStrokePath(context); + + // 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); + CGContextStrokePath(context); + } else { + // top border + UIColor *white = UIColorFromRGB(0xffffff); + + CGContextSetStrokeColor(context, CGColorGetComponents([white CGColor])); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0.0f, 0.5f); + CGContextAddLineToPoint(context, self.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]; + } + self.hasAlpha = YES; + } + + if (self.isRiverOrSocial) { + [self.siteFavicon drawInRect:CGRectMake(leftMargin, 6.0, 16.0, 16.0)]; + } + + // story indicator + int storyIndicatorY = 4 + adjustForSocial; + if (self.isShort){ + storyIndicatorY = 4 + adjustForSocial - 5 ; + } + + UIColor *scoreColor; + if (storyScore == -1) { + scoreColor = UIColorFromRGB(0xCC2A2E); + } else if (storyScore == 0) { + scoreColor = UIColorFromRGB(0xF9C72A); + } else { + scoreColor = UIColorFromRGB(0x3B7613); + } + CGContextSetFillColorWithColor(context, UIColorFromRGB(0xf4f4f4).CGColor); + CGContextFillEllipseInRect(context, CGRectMake(7, storyIndicatorY + 12, 12, 12)); + + if (self.isRead) { + CGContextSetAlpha(context, 0.25); + } + + CGContextSetFillColorWithColor(context, scoreColor.CGColor); + CGContextFillEllipseInRect(context, CGRectMake(9, storyIndicatorY + 14, 8, 8)); +} + +- (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 diff --git a/media/iphone/Classes/FeedDetailViewController.h b/media/ios/Classes/FeedDetailViewController.h similarity index 53% rename from media/iphone/Classes/FeedDetailViewController.h rename to media/ios/Classes/FeedDetailViewController.h index 8822f5451..8250efb84 100644 --- a/media/iphone/Classes/FeedDetailViewController.h +++ b/media/ios/Classes/FeedDetailViewController.h @@ -8,20 +8,21 @@ #import #import "ASIHTTPRequest.h" -#import "PullToRefreshView.h" #import "BaseViewController.h" +#import "Utilities.h" @class NewsBlurAppDelegate; +@class FeedDetailTableCell; @interface FeedDetailViewController : BaseViewController - { + { NewsBlurAppDelegate *appDelegate; NSArray * stories; int feedPage; BOOL pageFetching; - BOOL pageRefreshing; BOOL pageFinished; UITableView * storyTitlesTable; @@ -29,9 +30,23 @@ UISlider * feedScoreSlider; UIBarButtonItem * feedMarkReadButton; UISegmentedControl * intelligenceControl; - PullToRefreshView *pull; + UIPopoverController *popoverController; } +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) UIPopoverController *popoverController; +@property (nonatomic, strong) IBOutlet UITableView *storyTitlesTable; +@property (nonatomic) IBOutlet UIToolbar *feedViewToolbar; +@property (nonatomic) IBOutlet UISlider * feedScoreSlider; +@property (nonatomic) IBOutlet UIBarButtonItem * feedMarkReadButton; +@property (nonatomic) IBOutlet UIBarButtonItem * settingsButton; +@property (nonatomic) IBOutlet UISegmentedControl * intelligenceControl; + +@property (nonatomic) NSArray * stories; +@property (nonatomic, readwrite) int feedPage; +@property (nonatomic, readwrite) BOOL pageFetching; +@property (nonatomic, readwrite) BOOL pageFinished; + - (void)resetFeedDetail; - (void)fetchNextPage:(void(^)())callback; - (void)fetchFeedDetail:(int)page withCallback:(void(^)())callback; @@ -41,33 +56,24 @@ - (void)renderStories:(NSArray *)newStories; - (void)scrollViewDidScroll:(UIScrollView *)scroll; - (IBAction)selectIntelligence; +- (void)changeIntelligence:(NSInteger)newLevel; - (NSDictionary *)getStoryAtRow:(NSInteger)indexPathRow; - (void)checkScroll; -- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; -- (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view; -- (void)finishedRefreshingFeed:(ASIHTTPRequest *)request; -- (void)failRefreshingFeed:(ASIHTTPRequest *)request; +- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation; +- (void)fadeSelectedCell; - (IBAction)doOpenMarkReadActionSheet:(id)sender; - (IBAction)doOpenSettingsActionSheet; - (void)confirmDeleteSite; - (void)deleteSite; - (void)deleteFolder; - (void)openMoveView; - -@property (nonatomic, retain) IBOutlet NewsBlurAppDelegate *appDelegate; -@property (nonatomic, retain) IBOutlet UITableView *storyTitlesTable; -@property (nonatomic, retain) IBOutlet UIToolbar *feedViewToolbar; -@property (nonatomic, retain) IBOutlet UISlider * feedScoreSlider; -@property (nonatomic, retain) IBOutlet UIBarButtonItem * feedMarkReadButton; -@property (nonatomic, retain) IBOutlet UIBarButtonItem * settingsButton; -@property (nonatomic, retain) IBOutlet UISegmentedControl * intelligenceControl; -@property (nonatomic, retain) PullToRefreshView *pull; - -@property (nonatomic, retain) NSArray * stories; -@property (nonatomic, readwrite) int feedPage; -@property (nonatomic, readwrite) BOOL pageFetching; -@property (nonatomic, readwrite) BOOL pageRefreshing; -@property (nonatomic, readwrite) BOOL pageFinished; - -@end +- (void)showUserProfile; +- (void)changeActiveFeedDetailRow; +- (void)instafetchFeed; +- (void)changeActiveStoryTitleCellLayout; +- (void)loadFaviconsFromActiveFeed; +- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request; +@end \ No newline at end of file diff --git a/media/iphone/Classes/FeedDetailViewController.m b/media/ios/Classes/FeedDetailViewController.m similarity index 51% rename from media/iphone/Classes/FeedDetailViewController.m rename to media/ios/Classes/FeedDetailViewController.m index 77f4af302..c202333a9 100644 --- a/media/iphone/Classes/FeedDetailViewController.m +++ b/media/ios/Classes/FeedDetailViewController.m @@ -9,9 +9,11 @@ #import #import "FeedDetailViewController.h" #import "NewsBlurAppDelegate.h" +#import "NBContainerViewController.h" #import "FeedDetailTableCell.h" -#import "PullToRefreshView.h" #import "ASIFormDataRequest.h" +#import "UserProfileViewController.h" +#import "StoryDetailViewController.h" #import "NSString+HTML.h" #import "MBProgressHUD.h" #import "Base64.h" @@ -19,23 +21,31 @@ #import "StringHelper.h" #import "Utilities.h" -#define kTableViewRowHeight 65; + +#define kTableViewRowHeight 61; #define kTableViewRiverRowHeight 81; +#define kTableViewShortRowDifference 15; #define kMarkReadActionSheet 1; #define kSettingsActionSheet 2; +@interface FeedDetailViewController () + +@property (nonatomic) UIActionSheet* actionSheet_; // add this line + +@end + @implementation FeedDetailViewController +@synthesize popoverController; @synthesize storyTitlesTable, feedViewToolbar, feedScoreSlider, feedMarkReadButton; @synthesize settingsButton; @synthesize stories; @synthesize appDelegate; @synthesize feedPage; @synthesize pageFetching; -@synthesize pageRefreshing; @synthesize pageFinished; @synthesize intelligenceControl; -@synthesize pull; +@synthesize actionSheet_; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { @@ -43,30 +53,51 @@ } return self; } - + - (void)viewDidLoad { - pull = [[PullToRefreshView alloc] initWithScrollView:self.storyTitlesTable]; - [pull setDelegate:self]; - [self.storyTitlesTable addSubview:pull]; - [super viewDidLoad]; + + self.storyTitlesTable.backgroundColor = UIColorFromRGB(0xf4f4f4); +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation + duration:(NSTimeInterval)duration { + [self setUserAvatarLayout:toInterfaceOrientation]; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [self checkScroll]; } - (void)viewWillAppear:(BOOL)animated { + // + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + [self setUserAvatarLayout:orientation]; + self.pageFinished = NO; [MBProgressHUD hideHUDForView:self.view animated:YES]; - - if (appDelegate.isRiverView) { - self.storyTitlesTable.separatorStyle = UITableViewCellSeparatorStyleNone; - self.storyTitlesTable.separatorColor = [UIColor clearColor]; - } else { - self.storyTitlesTable.separatorStyle = UITableViewCellSeparatorStyleSingleLine; - self.storyTitlesTable.separatorColor = [UIColor colorWithRed:.9 green:.9 blue:.9 alpha:1.0]; - } - + + // set center title UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed]; self.navigationItem.titleView = titleLabel; + + // set right avatar title image + if (appDelegate.isSocialView) { + UIButton *titleImageButton = [appDelegate makeRightFeedTitle:appDelegate.activeFeed]; + [titleImageButton addTarget:self action:@selector(showUserProfile) forControlEvents:UIControlEventTouchUpInside]; + UIBarButtonItem *titleImageBarButton = [[UIBarButtonItem alloc] + initWithCustomView:titleImageButton]; + self.navigationItem.rightBarButtonItem = titleImageBarButton; + } else { + self.navigationItem.rightBarButtonItem = nil; + } + // Commenting out until training is ready... // UIBarButtonItem *trainBarButton = [UIBarButtonItem alloc]; // [trainBarButton setImage:[UIImage imageNamed:@"train.png"]]; @@ -85,75 +116,105 @@ [self.storyTitlesTable beginUpdates]; [self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; [self.storyTitlesTable endUpdates]; + //[self.storyTitlesTable reloadData]; } [appDelegate setRecentlyReadStories:[NSMutableArray array]]; - [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_red.png"] forSegmentAtIndex:0]; - [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_yellow.png"] forSegmentAtIndex:1]; - [self.intelligenceControl setImage:[UIImage imageNamed:@"bullet_green.png"] forSegmentAtIndex:2]; - [self.intelligenceControl addTarget:self - action:@selector(selectIntelligence) - forControlEvents:UIControlEventValueChanged]; - [self.intelligenceControl setSelectedSegmentIndex:[appDelegate selectedIntelligence]+1]; [super viewWillAppear:animated]; - - BOOL pullFound = NO; - for (UIView *view in self.storyTitlesTable.subviews) { - if ([view isKindOfClass:[PullToRefreshView class]]) { - pullFound = YES; - if (appDelegate.isRiverView) { - [view removeFromSuperview]; - } - } - } - if (!appDelegate.isRiverView && !pullFound) { - [self.storyTitlesTable addSubview:pull]; - } - - if (appDelegate.isRiverView && [appDelegate.activeFolder isEqualToString:@"Everything"]) { + + if ((appDelegate.isSocialRiverView || appDelegate.isRiverView || appDelegate.isSocialView)) { settingsButton.enabled = NO; } else { settingsButton.enabled = YES; } + + if (appDelegate.isSocialRiverView || + [appDelegate.activeFolder isEqualToString:@"All Stories"]) { + feedMarkReadButton.enabled = NO; + } else { + feedMarkReadButton.enabled = YES; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [self.storyTitlesTable reloadData]; + int location = appDelegate.locationOfActiveStory; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:location inSection:0]; + if (indexPath) { + [self.storyTitlesTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; + } + [self performSelector:@selector(fadeSelectedCell) withObject:self afterDelay:0.6]; + } } - (void)viewDidAppear:(BOOL)animated { -// [[storyTitlesTable cellForRowAtIndexPath:[storyTitlesTable indexPathForSelectedRow]] setSelected:NO]; // TODO: DESELECT CELL --- done, see line below: - [self.storyTitlesTable deselectRowAtIndexPath:[storyTitlesTable indexPathForSelectedRow] animated:YES]; - [pull refreshLastUpdatedDate]; + if (appDelegate.inStoryDetail = YES && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + appDelegate.inStoryDetail = NO; + [appDelegate.storyDetailViewController clearStory]; + [self checkScroll]; + } - [super viewDidAppear:animated]; + NSString *title = appDelegate.isRiverView ? + appDelegate.activeFolder : + [appDelegate.activeFeed objectForKey:@"feed_title"]; + NSLog(@"title %@", title); } -- (void)dealloc { - [storyTitlesTable release]; - [feedViewToolbar release]; - [feedScoreSlider release]; - [feedMarkReadButton release]; - [settingsButton release]; - [stories release]; - [appDelegate release]; - [intelligenceControl release]; - [pull release]; - [super dealloc]; +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.popoverController dismissPopoverAnimated:YES]; } + +- (void)fadeSelectedCell { + // have the selected cell deselect + int location = appDelegate.locationOfActiveStory; + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:location inSection:0]; + if (indexPath) { + [self.storyTitlesTable deselectRowAtIndexPath:indexPath animated:YES]; + + } + +} + +- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + if (UIInterfaceOrientationIsPortrait(orientation)) { + UIButton *avatar = (UIButton *)self.navigationItem.rightBarButtonItem.customView; + CGRect buttonFrame = avatar.frame; + buttonFrame.size = CGSizeMake(32, 32); + avatar.frame = buttonFrame; + } else { + UIButton *avatar = (UIButton *)self.navigationItem.rightBarButtonItem.customView; + CGRect buttonFrame = avatar.frame; + buttonFrame.size = CGSizeMake(28, 28); + avatar.frame = buttonFrame; + } + } +} + + #pragma mark - #pragma mark Initialization - (void)resetFeedDetail { self.pageFetching = NO; self.pageFinished = NO; - self.pageRefreshing = NO; self.feedPage = 1; } +#pragma mark - +#pragma mark Regular and Social Feeds + - (void)fetchNextPage:(void(^)())callback { [self fetchFeedDetail:self.feedPage+1 withCallback:callback]; } -- (void)fetchFeedDetail:(int)page withCallback:(void(^)())callback { - if ([appDelegate.activeFeed objectForKey:@"id"] != nil && !self.pageFetching && !self.pageFinished) { +- (void)fetchFeedDetail:(int)page withCallback:(void(^)())callback { + NSString *theFeedDetailURL; + + if (!self.pageFetching && !self.pageFinished) { + self.feedPage = page; self.pageFetching = YES; int storyCount = appDelegate.storyCount; @@ -161,18 +222,24 @@ [self.storyTitlesTable reloadData]; [storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]; } - - NSString *theFeedDetailURL = [NSString stringWithFormat:@"http://%@/reader/feed/%@?page=%d", - NEWSBLUR_URL, - [appDelegate.activeFeed objectForKey:@"id"], - self.feedPage]; - + if (appDelegate.isSocialView) { + theFeedDetailURL = [NSString stringWithFormat:@"http://%@/social/stories/%@/?page=%d", + NEWSBLUR_URL, + [appDelegate.activeFeed objectForKey:@"user_id"], + self.feedPage]; + } else { + theFeedDetailURL = [NSString stringWithFormat:@"http://%@/reader/feed/%@/?page=%d", + NEWSBLUR_URL, + [appDelegate.activeFeed objectForKey:@"id"], + self.feedPage]; + } [self cancelRequests]; - __block ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL]; + __weak ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL]; [request setDelegate:self]; [request setResponseEncoding:NSUTF8StringEncoding]; [request setDefaultResponseEncoding:NSUTF8StringEncoding]; [request setFailedBlock:^(void) { + NSLog(@"in failed block %@", request); [self informError:[request error]]; }]; [request setCompletionBlock:^(void) { @@ -181,59 +248,16 @@ callback(); } }]; - [request setTimeOutSeconds:30]; + [request setTimeOutSeconds:10]; [request setTag:[[[appDelegate activeFeed] objectForKey:@"id"] intValue]]; [request startAsynchronous]; } } -- (void)finishedLoadingFeed:(ASIHTTPRequest *)request { - if ([request responseStatusCode] >= 500) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.15 * NSEC_PER_SEC), - dispatch_get_current_queue(), ^{ - [appDelegate.navigationController - popToViewController:[appDelegate.navigationController.viewControllers - objectAtIndex:0] - animated:YES]; - }); - [self informError:@"The server barfed!"]; - - return; - } - - NSString *responseString = [request responseString]; - NSDictionary *results = [[NSDictionary alloc] - initWithDictionary:[responseString JSONValue]]; - if (!appDelegate.isRiverView && request.tag != [[results objectForKey:@"feed_id"] intValue]) { - [results release]; - return; - } - - [pull finishedLoading]; - NSArray *newStories = [results objectForKey:@"stories"]; - NSMutableArray *confirmedNewStories = [NSMutableArray array]; - if ([appDelegate.activeFeedStories count]) { - NSMutableSet *storyIds = [NSMutableSet set]; - for (id story in appDelegate.activeFeedStories) { - [storyIds addObject:[story objectForKey:@"id"]]; - } - for (id story in newStories) { - if (![storyIds containsObject:[story objectForKey:@"id"]]) { - [confirmedNewStories addObject:story]; - } - } - } else { - confirmedNewStories = [[newStories copy] autorelease]; - } - [self renderStories:confirmedNewStories]; - - [results release]; -} - #pragma mark - #pragma mark River of News -- (void)fetchRiverPage:(int)page withCallback:(void(^)())callback { +- (void)fetchRiverPage:(int)page withCallback:(void(^)())callback { if (!self.pageFetching && !self.pageFinished) { self.feedPage = page; self.pageFetching = YES; @@ -251,14 +275,24 @@ } } - NSString *theFeedDetailURL = [NSString stringWithFormat:@"http://%@/reader/river_stories/?feeds=%@&page=%d&read_stories_count=%d", - NEWSBLUR_URL, - [appDelegate.activeFolderFeeds componentsJoinedByString:@"&feeds="], - self.feedPage, - readStoriesCount]; + NSString *theFeedDetailURL; + + if (appDelegate.isSocialRiverView) { + theFeedDetailURL = [NSString stringWithFormat: + @"http://%@/social/river_stories/?page=%d&order=newest", + NEWSBLUR_URL, + self.feedPage]; + } else { + theFeedDetailURL = [NSString stringWithFormat: + @"http://%@/reader/river_stories/?feeds=%@&page=%d&read_stories_count=%d", + NEWSBLUR_URL, + [appDelegate.activeFolderFeeds componentsJoinedByString:@"&feeds="], + self.feedPage, + readStoriesCount]; + } [self cancelRequests]; - __block ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL]; + __weak ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL]; [request setDelegate:self]; [request setResponseEncoding:NSUTF8StringEncoding]; [request setDefaultResponseEncoding:NSUTF8StringEncoding]; @@ -276,6 +310,104 @@ } } +#pragma mark - +#pragma mark Processing Stories + +- (void)finishedLoadingFeed:(ASIHTTPRequest *)request { + if ([request responseStatusCode] >= 500) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.15 * NSEC_PER_SEC), + dispatch_get_current_queue(), ^{ + [appDelegate.navigationController + popToViewController:[appDelegate.navigationController.viewControllers + objectAtIndex:0] + animated:YES]; + }); + [self informError:@"The server barfed!"]; + + return; + } + + NSString *responseString = [request responseString]; + NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + if (!(appDelegate.isRiverView || appDelegate.isSocialView || appDelegate.isSocialRiverView) + && request.tag != [[results objectForKey:@"feed_id"] intValue]) { + return; + } + + if (appDelegate.isSocialView || appDelegate.isSocialRiverView) { + NSArray *newFeeds = [results objectForKey:@"feeds"]; + for (int i = 0; i < newFeeds.count; i++){ + NSString *feedKey = [NSString stringWithFormat:@"%@", [[newFeeds objectAtIndex:i] objectForKey:@"id"]]; + [appDelegate.dictActiveFeeds setObject:[newFeeds objectAtIndex:i] + forKey:feedKey]; + } + [self loadFaviconsFromActiveFeed]; + } + + NSArray *newStories = [results objectForKey:@"stories"]; + NSMutableArray *confirmedNewStories = [[NSMutableArray alloc] init]; + if ([appDelegate.activeFeedStories count]) { + NSMutableSet *storyIds = [NSMutableSet set]; + for (id story in appDelegate.activeFeedStories) { + [storyIds addObject:[story objectForKey:@"id"]]; + } + for (id story in newStories) { + if (![storyIds containsObject:[story objectForKey:@"id"]]) { + [confirmedNewStories addObject:story]; + } + } + } else { + confirmedNewStories = [newStories copy]; + } + + // Adding new user profiles to appDelegate.activeFeedUserProfiles + + NSArray *newUserProfiles = [[NSArray alloc] init]; + if ([results objectForKey:@"user_profiles"] != nil) { + newUserProfiles = [results objectForKey:@"user_profiles"]; + } + // add self to user profiles + if (self.feedPage == 1) { + newUserProfiles = [newUserProfiles arrayByAddingObject:appDelegate.dictUserProfile]; + } + + if ([newUserProfiles count]){ + NSMutableArray *confirmedNewUserProfiles = [NSMutableArray array]; + if ([appDelegate.activeFeedUserProfiles count]) { + NSMutableSet *userProfileIds = [NSMutableSet set]; + for (id userProfile in appDelegate.activeFeedUserProfiles) { + [userProfileIds addObject:[userProfile objectForKey:@"id"]]; + } + for (id userProfile in newUserProfiles) { + if (![userProfileIds containsObject:[userProfile objectForKey:@"id"]]) { + [confirmedNewUserProfiles addObject:userProfile]; + } + } + } else { + confirmedNewUserProfiles = [newUserProfiles copy]; + } + + + if (self.feedPage == 1) { + [appDelegate setFeedUserProfiles:confirmedNewUserProfiles]; + } else if (newUserProfiles.count > 0) { + [appDelegate addFeedUserProfiles:confirmedNewUserProfiles]; + } + +// NSLog(@"activeFeedUserProfiles is %@", appDelegate.activeFeedUserProfiles); +// NSLog(@"# of user profiles added: %i", appDelegate.activeFeedUserProfiles.count); +// NSLog(@"user profiles added: %@", appDelegate.activeFeedUserProfiles); + } + + [self renderStories:confirmedNewStories]; +} + #pragma mark - #pragma mark Stories @@ -291,35 +423,57 @@ NSInteger newVisibleStoriesCount = [[appDelegate activeFeedStoryLocations] count] - existingStoriesCount; -// NSLog(@"Paging: %d/%d", existingStoriesCount, [appDelegate unreadCount]); - if (existingStoriesCount > 0 && newVisibleStoriesCount > 0) { NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; for (int i=0; i < newVisibleStoriesCount; i++) { [indexPaths addObject:[NSIndexPath indexPathForRow:(existingStoriesCount+i) inSection:0]]; } - [self.storyTitlesTable beginUpdates]; - [self.storyTitlesTable insertRowsAtIndexPaths:indexPaths - withRowAnimation:UITableViewRowAnimationNone]; - [self.storyTitlesTable endUpdates]; - [indexPaths release]; + + [self.storyTitlesTable reloadData]; + } else if (newVisibleStoriesCount > 0) { [self.storyTitlesTable reloadData]; + } else if (newStoriesCount == 0 || (self.feedPage > 15 && existingStoriesCount >= [appDelegate unreadCount])) { self.pageFinished = YES; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:existingStoriesCount - inSection:0]; - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - [self.storyTitlesTable beginUpdates]; - [self.storyTitlesTable reloadRowsAtIndexPaths:indexPaths - withRowAnimation:UITableViewRowAnimationNone]; - [self.storyTitlesTable endUpdates]; + [self.storyTitlesTable reloadData]; + } + + self.pageFetching = NO; + + // test for tryfeed + if (appDelegate.inFindingStoryMode && appDelegate.tryFeedStoryId) { + for (int i = 0; i < appDelegate.activeFeedStories.count; i++) { + NSString *storyIdStr = [[appDelegate.activeFeedStories objectAtIndex:i] objectForKey:@"id"]; + if ([storyIdStr isEqualToString:appDelegate.tryFeedStoryId]) { + NSDictionary *feed = [appDelegate.activeFeedStories objectAtIndex:i]; + + int score = [NewsBlurAppDelegate computeStoryScore:[feed objectForKey:@"intelligence"]]; + + if (score < appDelegate.selectedIntelligence) { + [self changeIntelligence:score]; + } + int locationOfStoryId = [appDelegate locationOfStoryId:storyIdStr]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:locationOfStoryId inSection:0]; + + [self.storyTitlesTable selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom]; + + FeedDetailTableCell *cell = (FeedDetailTableCell *)[self.storyTitlesTable cellForRowAtIndexPath:indexPath]; + [self loadStory:cell atRow:indexPath.row]; + + // found the story, reset the two flags. + appDelegate.tryFeedStoryId = nil; + appDelegate.inFindingStoryMode = NO; + } + } } - self.pageFetching = NO; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController syncNextPreviousButtons]; + } [self performSelector:@selector(checkScroll) withObject:nil @@ -327,7 +481,6 @@ } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { - [connection release]; // inform the user NSLog(@"Connection failed! Error - %@", @@ -342,26 +495,37 @@ } - (UITableViewCell *)makeLoadingCell { - UITableViewCell *cell = [[[UITableViewCell alloc] + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle - reuseIdentifier:@"NoReuse"] autorelease]; + reuseIdentifier:@"NoReuse"]; cell.selectionStyle = UITableViewCellSelectionStyleNone; - if (self.pageFinished) { - UIView * blue = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 321, 17)]; - [cell.contentView addSubview:blue]; - blue.backgroundColor = [UIColor whiteColor]; - [UIView beginAnimations:nil context:NULL]; - [UIView setAnimationDuration:0.5f]; - blue.backgroundColor = [UIColor colorWithRed:.7f green:0.7f blue:0.7f alpha:1.0f]; - [UIView commitAnimations]; - [blue release]; + UIImage *img = [UIImage imageNamed:@"fleuron.png"]; + UIImageView *fleuron = [[UIImageView alloc] initWithImage:img]; + int height = 0; + + if (appDelegate.isRiverView || appDelegate.isSocialView) { + height = kTableViewRiverRowHeight; + } else { + height = kTableViewRowHeight; + } + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad + && !appDelegate.masterContainerViewController.storyTitlesOnLeft + && UIInterfaceOrientationIsPortrait(orientation)) { + height = height - kTableViewShortRowDifference; + } + + fleuron.frame = CGRectMake(0, 0, self.view.frame.size.width, height); + fleuron.contentMode = UIViewContentModeCenter; + [cell.contentView addSubview:fleuron]; } else { cell.textLabel.text = @"Loading..."; - UIActivityIndicatorView *spinner = [[[UIActivityIndicatorView alloc] - initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease]; + UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; UIImage *spacer = [UIImage imageNamed:@"spacer"]; UIGraphicsBeginImageContext(spinner.frame.size); [spacer drawInRect:CGRectMake(0,0,spinner.frame.size.width,spinner.frame.size.height)]; @@ -378,129 +542,161 @@ #pragma mark - #pragma mark Table View - Feed List -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (self.pageRefreshing) { - // Refreshing feed - return 1; - } else { - int storyCount = [[appDelegate activeFeedStoryLocations] count]; +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + int storyCount = [[appDelegate activeFeedStoryLocations] count]; - // The +1 is for the finished/loading bar. - return storyCount + 1; - } + // The +1 is for the finished/loading bar. + return storyCount + 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *cellIdentifier; - - if (appDelegate.isRiverView) { + + NSString *cellIdentifier; + NSDictionary *feed ; + + if (appDelegate.isRiverView || appDelegate.isSocialView) { cellIdentifier = @"FeedRiverDetailCellIdentifier"; } else { cellIdentifier = @"FeedDetailCellIdentifier"; } - - FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView - dequeueReusableCellWithIdentifier:cellIdentifier]; - if (cell == nil) { - NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"FeedDetailTableCell" - owner:self - options:nil]; - for (id oneObject in nib) { - if ([oneObject isKindOfClass:[FeedDetailTableCell class]]) { - if (([(FeedDetailTableCell *)oneObject tag] == 0 && !appDelegate.isRiverView) || - ([(FeedDetailTableCell *)oneObject tag] == 1 && appDelegate.isRiverView)) { - cell = (FeedDetailTableCell *)oneObject; - break; - } - - } - } - } + FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView + dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) { + cell = [[FeedDetailTableCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil]; + } + if (indexPath.row >= [[appDelegate activeFeedStoryLocations] count]) { return [self makeLoadingCell]; } - + NSDictionary *story = [self getStoryAtRow:indexPath.row]; + id feedId = [story objectForKey:@"story_feed_id"]; - NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; - NSDictionary *feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + NSString *feedIdStr = [NSString stringWithFormat:@"%@", feedId]; + + if (appDelegate.isSocialView || appDelegate.isSocialRiverView) { + feed = [appDelegate.dictActiveFeeds objectForKey:feedIdStr]; + // this is to catch when a user is already subscribed + if (!feed) { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + + NSString *siteTitle = [feed objectForKey:@"feed_title"]; + cell.siteTitle = siteTitle; + + NSString *title = [story objectForKey:@"story_title"]; + cell.storyTitle = [title stringByDecodingHTMLEntities]; + + cell.storyDate = [story objectForKey:@"short_parsed_date"]; if ([[story objectForKey:@"story_authors"] class] != [NSNull class]) { - cell.storyAuthor.text = [[story objectForKey:@"story_authors"] - uppercaseString]; + cell.storyAuthor = [[story objectForKey:@"story_authors"] uppercaseString]; } else { - cell.storyAuthor.text = @""; + cell.storyAuthor = @""; } - BOOL isStoryRead = [[story objectForKey:@"read_status"] intValue] == 1; - NSString *title = [story objectForKey:@"story_title"]; - cell.storyTitle.text = [title stringByDecodingHTMLEntities]; - cell.storyDate.text = [story objectForKey:@"short_parsed_date"]; + // feed color bar border + unsigned int colorBorder = 0; + NSString *faviconColor = [feed valueForKey:@"favicon_color"]; + + if ([faviconColor class] == [NSNull class]) { + faviconColor = @"505050"; + } + NSScanner *scannerBorder = [NSScanner scannerWithString:faviconColor]; + [scannerBorder scanHexInt:&colorBorder]; + + cell.feedColorBar = UIColorFromRGB(colorBorder); + + // feed color bar border + NSString *faviconFade = [feed valueForKey:@"favicon_border"]; + if ([faviconFade class] == [NSNull class]) { + faviconFade = @"505050"; + } + scannerBorder = [NSScanner scannerWithString:faviconFade]; + [scannerBorder scanHexInt:&colorBorder]; + cell.feedColorBarTopBorder = UIColorFromRGB(colorBorder); + + // favicon + cell.siteFavicon = [Utilities getImage:feedIdStr]; + + // undread indicator + int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; - if (score > 0) { - cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_green.png"]; - } else if (score == 0) { - cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_yellow.png"]; - } else if (score < 0) { - cell.storyUnreadIndicator.image = [UIImage imageNamed:@"bullet_red.png"]; - } + cell.storyScore = score; + + cell.isRead = [[story objectForKey:@"read_status"] intValue] == 1; - // River view - if (appDelegate.isRiverView && cell) { - UIView *gradientView = [appDelegate makeFeedTitleGradient:feed - withRect:CGRectMake(0, 0, cell.frame.size.width, 21)]; - [cell.feedGradient addSubview:gradientView]; + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad + && !appDelegate.masterContainerViewController.storyTitlesOnLeft + && UIInterfaceOrientationIsPortrait(orientation)) { + cell.isShort = YES; } - - if (!isStoryRead) { - // Unread story - cell.storyTitle.textColor = [UIColor colorWithRed:0.1f green:0.1f blue:0.1f alpha:1.0]; - cell.storyTitle.font = [UIFont fontWithName:@"Helvetica-Bold" size:13]; - cell.storyAuthor.textColor = [UIColor colorWithRed:0.58f green:0.58f blue:0.58f alpha:1.0]; - cell.storyAuthor.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; - cell.storyDate.textColor = [UIColor colorWithRed:0.14f green:0.18f blue:0.42f alpha:1.0]; - cell.storyDate.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; - cell.storyUnreadIndicator.alpha = 1; - } else { - // Read story - cell.storyTitle.textColor = [UIColor colorWithRed:0.15f green:0.25f blue:0.25f alpha:0.9]; - cell.storyTitle.font = [UIFont fontWithName:@"Helvetica" size:12]; - cell.storyAuthor.textColor = [UIColor colorWithRed:0.58f green:0.58f blue:0.58f alpha:0.5]; - cell.storyAuthor.font = [UIFont fontWithName:@"Helvetica-Bold" size:10]; - cell.storyDate.textColor = [UIColor colorWithRed:0.14f green:0.18f blue:0.42f alpha:0.5]; - cell.storyDate.font = [UIFont fontWithName:@"Helvetica" size:10]; - cell.storyUnreadIndicator.alpha = 0.15f; -// cell.feedTitle.font = [UIFont fontWithName:@"Helvetica" size:11]; -// cell.feedTitle.textColor = [UIColor blackColor]; -// cell.feedTitle.shadowColor = nil; -// cell.feedFavicon.alpha = 0.5f; - cell.feedGradient.alpha = 0.25f; + + if (appDelegate.isRiverView || appDelegate.isSocialView || appDelegate.isSocialRiverView) { + cell.isRiverOrSocial = YES; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + int rowIndex = [appDelegate locationOfActiveStory]; + if (rowIndex == indexPath.row) { + [self.storyTitlesTable selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; + } } return cell; } +- (void)loadStory:(FeedDetailTableCell *)cell atRow:(int)row { + cell.isRead = YES; + [cell setNeedsLayout]; + [appDelegate setActiveStory:[[appDelegate activeFeedStories] objectAtIndex:row]]; + [appDelegate setOriginalStoryCount:[appDelegate unreadCount]]; + [appDelegate loadStoryDetailView]; +} + +- (void)changeActiveStoryTitleCellLayout { + int rowIndex = [appDelegate locationOfActiveStory]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0]; + FeedDetailTableCell *cell = (FeedDetailTableCell*) [self.storyTitlesTable cellForRowAtIndexPath:indexPath]; + cell.isRead = YES; + [cell setNeedsLayout]; +} + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row < [appDelegate.activeFeedStoryLocations count]) { + // mark the cell as read + FeedDetailTableCell *cell = (FeedDetailTableCell*) [tableView cellForRowAtIndexPath:indexPath]; int location = [[[appDelegate activeFeedStoryLocations] objectAtIndex:indexPath.row] intValue]; - [appDelegate setActiveStory:[[appDelegate activeFeedStories] objectAtIndex:location]]; - [appDelegate setOriginalStoryCount:[appDelegate unreadCount]]; - [appDelegate loadStoryDetailView]; + [self loadStory:cell atRow:location]; } } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.row >= [[appDelegate activeFeedStoryLocations] count]) { - if (self.pageFinished) return 16; - else return kTableViewRowHeight; - } else { - if (appDelegate.isRiverView) { - return kTableViewRiverRowHeight; - } else { - return kTableViewRowHeight; + if (appDelegate.isRiverView || appDelegate.isSocialView || appDelegate.isSocialRiverView) { + int height = kTableViewRiverRowHeight; + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad + && !appDelegate.masterContainerViewController.storyTitlesOnLeft + && UIInterfaceOrientationIsPortrait(orientation)) { + height = height - kTableViewShortRowDifference; } + return height; + } else { + int height = kTableViewRowHeight; + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad + && !appDelegate.masterContainerViewController.storyTitlesOnLeft + && UIInterfaceOrientationIsPortrait(orientation)) { + height = height - kTableViewShortRowDifference; + } + return height; } } @@ -512,7 +708,8 @@ NSInteger currentOffset = self.storyTitlesTable.contentOffset.y; NSInteger maximumOffset = self.storyTitlesTable.contentSize.height - self.storyTitlesTable.frame.size.height; - if (maximumOffset - currentOffset <= 60.0) { + if (maximumOffset - currentOffset <= 60.0 || + (appDelegate.inFindingStoryMode)) { if (appDelegate.isRiverView) { [self fetchRiverPage:self.feedPage+1 withCallback:nil]; } else { @@ -523,6 +720,14 @@ - (IBAction)selectIntelligence { NSInteger newLevel = [self.intelligenceControl selectedSegmentIndex] - 1; + [self changeIntelligence:newLevel]; + + [self performSelector:@selector(checkScroll) + withObject:nil + afterDelay:1.0]; +} + +- (void)changeIntelligence:(NSInteger)newLevel { NSInteger previousLevel = [appDelegate selectedIntelligence]; NSMutableArray *insertIndexPaths = [NSMutableArray array]; NSMutableArray *deleteIndexPaths = [NSMutableArray array]; @@ -531,9 +736,13 @@ if (newLevel < previousLevel) { [appDelegate setSelectedIntelligence:newLevel]; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + [userPreferences setInteger:(newLevel + 1) forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; + [appDelegate calculateStoryLocations]; } - + for (int i=0; i < [[appDelegate activeFeedStoryLocations] count]; i++) { int location = [[[appDelegate activeFeedStoryLocations] objectAtIndex:i] intValue]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; @@ -576,10 +785,6 @@ withRowAnimation:UITableViewRowAnimationNone]; } [self.storyTitlesTable endUpdates]; - - [self performSelector:@selector(checkScroll) - withObject:nil - afterDelay:1.0]; } - (NSDictionary *)getStoryAtRow:(NSInteger)indexPathRow { @@ -606,25 +811,18 @@ [request startAsynchronous]; [appDelegate markActiveFolderAllRead]; - [appDelegate.navigationController - popToViewController:[appDelegate.navigationController.viewControllers - objectAtIndex:0] - animated:YES]; } else if (!appDelegate.isRiverView && includeHidden) { // Mark feed as read NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/mark_feed_as_read", NEWSBLUR_URL]; NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; - [request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"]; - [request setDelegate:nil]; + [request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"]; + [request setDidFinishSelector:@selector(finishMarkAllAsRead:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setDelegate:self]; [request startAsynchronous]; - - [appDelegate markActiveFeedAllRead]; - [appDelegate.navigationController - popToViewController:[appDelegate.navigationController.viewControllers - objectAtIndex:0] - animated:YES]; + [appDelegate markFeedAllRead:[appDelegate.activeFeed objectForKey:@"id"]]; } else { // Mark visible stories as read NSDictionary *feedsStories = [appDelegate markVisibleStoriesRead]; @@ -633,9 +831,15 @@ NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; [request setPostValue:[feedsStories JSONRepresentation] forKey:@"feeds_stories"]; - [request setDelegate:nil]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishMarkAllAsRead:)]; + [request setDidFailSelector:@selector(requestFailed:)]; [request startAsynchronous]; - + } + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.navigationController popToRootViewControllerAnimated:YES]; + [appDelegate.masterContainerViewController transitionFromFeedDetail]; + } else { [appDelegate.navigationController popToViewController:[appDelegate.navigationController.viewControllers objectAtIndex:0] @@ -643,7 +847,26 @@ } } +- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request { +// NSString *responseString = [request responseString]; +// NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; +// NSError *error; +// NSDictionary *results = [NSJSONSerialization +// JSONObjectWithData:responseData +// options:kNilOptions +// error:&error]; + + +} + - (IBAction)doOpenMarkReadActionSheet:(id)sender { + // already displaying action sheet? + if (self.actionSheet_) { + [self.actionSheet_ dismissWithClickedButtonIndex:-1 animated:YES]; + self.actionSheet_ = nil; + return; + } + // Individual sites just get marked as read, no action sheet needed. if (!appDelegate.isRiverView) { [self markFeedsReadWithAllStories:YES]; @@ -660,6 +883,8 @@ destructiveButtonTitle:nil otherButtonTitles:nil]; + self.actionSheet_ = options; + int visibleUnreadCount = appDelegate.visibleUnreadCount; int totalUnreadCount = [appDelegate unreadCount]; NSArray *buttonTitles = nil; @@ -692,8 +917,11 @@ options.cancelButtonIndex = [options addButtonWithTitle:@"Cancel"]; options.tag = kMarkReadActionSheet; - [options showInView:self.view]; - [options release]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [options showFromBarButtonItem:self.feedMarkReadButton animated:YES]; + } else { + [options showInView:self.view]; + } } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { @@ -727,11 +955,24 @@ [self confirmDeleteSite]; } else if (buttonIndex == 1) { [self openMoveView]; + } else if (buttonIndex == 2) { + [self instafetchFeed]; } - } + } +} + +- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { + // just set to nil + actionSheet_ = nil; } - (IBAction)doOpenSettingsActionSheet { + // already displaying action sheet? + if (self.actionSheet_) { + [self.actionSheet_ dismissWithClickedButtonIndex:-1 animated:YES]; + self.actionSheet_ = nil; + return; + } NSString *title = appDelegate.isRiverView ? appDelegate.activeFolder : [appDelegate.activeFeed objectForKey:@"feed_title"]; @@ -742,6 +983,8 @@ destructiveButtonTitle:nil otherButtonTitles:nil]; + self.actionSheet_ = options; + if (![title isEqualToString:@"Everything"]) { NSString *deleteText = [NSString stringWithFormat:@"Delete %@", appDelegate.isRiverView ? @@ -752,19 +995,31 @@ NSString *moveText = @"Move to another folder"; [options addButtonWithTitle:moveText]; + + NSString *fetchText = @"Insta-fetch stories"; + [options addButtonWithTitle:fetchText]; + } options.cancelButtonIndex = [options addButtonWithTitle:@"Cancel"]; options.tag = kSettingsActionSheet; - [options showInView:self.view]; - [options release]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [options showFromBarButtonItem:self.settingsButton animated:YES]; + } else { + [options showInView:self.view]; + } } - (void)confirmDeleteSite { - UIAlertView *deleteConfirm = [[UIAlertView alloc] initWithTitle:@"Positive?" message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Delete", nil]; + UIAlertView *deleteConfirm = [[UIAlertView alloc] + initWithTitle:@"Positive?" + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Delete", + nil]; [deleteConfirm show]; [deleteConfirm setTag:0]; - [deleteConfirm release]; } - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { @@ -790,7 +1045,7 @@ NEWSBLUR_URL]; NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL]; - __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail]; + __weak ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail]; [request setDelegate:self]; [request addPostValue:[[appDelegate activeFeed] objectForKey:@"id"] forKey:@"feed_id"]; [request addPostValue:[appDelegate extractFolderName:appDelegate.activeFolder] forKey:@"in_folder"]; @@ -819,7 +1074,7 @@ NEWSBLUR_URL]; NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL]; - __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail]; + __weak ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail]; [request setDelegate:self]; [request addPostValue:[appDelegate extractFolderName:appDelegate.activeFolder] forKey:@"folder_to_delete"]; @@ -844,15 +1099,44 @@ [appDelegate showMoveSite]; } -#pragma mark - -#pragma mark PullToRefresh +- (void)showUserProfile { + appDelegate.activeUserProfileId = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed objectForKey:@"user_id"]]; + appDelegate.activeUserProfileName = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed objectForKey:@"username"]]; + [appDelegate showUserProfileModal:self.navigationItem.rightBarButtonItem]; +} -// called when the user pulls-to-refresh -- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view { - if (appDelegate.isRiverView) { - [pull finishedLoading]; - return; +- (void)changeActiveFeedDetailRow { + int rowIndex = [appDelegate locationOfActiveStory]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0]; + NSIndexPath *offsetIndexPath = [NSIndexPath indexPathForRow:rowIndex - 1 inSection:0]; + + [storyTitlesTable selectRowAtIndexPath:indexPath + animated:YES + scrollPosition:UITableViewScrollPositionNone]; + + // check to see if the cell is completely visible + CGRect cellRect = [storyTitlesTable rectForRowAtIndexPath:indexPath]; + + cellRect = [storyTitlesTable convertRect:cellRect toView:storyTitlesTable.superview]; + + BOOL completelyVisible = CGRectContainsRect(storyTitlesTable.frame, cellRect); + + if (!completelyVisible) { + [storyTitlesTable scrollToRowAtIndexPath:offsetIndexPath + atScrollPosition:UITableViewScrollPositionTop + animated:YES]; } +} + + +#pragma mark - +#pragma mark instafetchFeed + +// called when the user taps refresh button + +- (void)instafetchFeed { + NSLog(@"Instafetch"); NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/refresh_feed/%@", @@ -871,36 +1155,89 @@ [appDelegate setStories:nil]; self.feedPage = 1; self.pageFetching = YES; - self.pageRefreshing = YES; [self.storyTitlesTable reloadData]; [storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]; } - (void)finishedRefreshingFeed:(ASIHTTPRequest *)request { NSString *responseString = [request responseString]; - NSDictionary *results = [[NSDictionary alloc] - initWithDictionary:[responseString JSONValue]]; - [pull finishedLoading]; - self.pageRefreshing = NO; - [self renderStories:[results objectForKey:@"stories"]]; + NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; - [results release]; + [self renderStories:[results objectForKey:@"stories"]]; } - (void)failRefreshingFeed:(ASIHTTPRequest *)request { NSLog(@"Fail: %@", request); - self.pageRefreshing = NO; [self informError:[request error]]; - [pull finishedLoading]; [self fetchFeedDetail:1 withCallback:nil]; } -// called when the date shown needs to be updated, optional -- (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view { -// NSLog(@"Updated; %@", [appDelegate.activeFeed objectForKey:@"updated_seconds_ago"]); - int seconds = -1 * [[appDelegate.activeFeed objectForKey:@"updated_seconds_ago"] intValue]; - return [[[NSDate alloc] initWithTimeIntervalSinceNow:seconds] autorelease]; +#pragma mark - +#pragma mark loadSocial Feeds + +- (void)loadFaviconsFromActiveFeed { + NSArray * keys = [appDelegate.dictActiveFeeds allKeys]; + + if (![keys count]) { + // if no new favicons, return + return; + } + + NSString *feedIdsQuery = [NSString stringWithFormat:@"?feed_ids=%@", + [[keys valueForKey:@"description"] componentsJoinedByString:@"&feed_ids="]]; + NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/favicons%@", + NEWSBLUR_URL, + feedIdsQuery]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + + [request setDidFinishSelector:@selector(saveAndDrawFavicons:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setDelegate:self]; + [request startAsynchronous]; } +- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request { + + NSString *responseString = [request responseString]; + NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); + dispatch_async(queue, ^{ + for (id feed_id in results) { + NSMutableDictionary *feed = [[appDelegate.dictActiveFeeds objectForKey:feed_id] mutableCopy]; + [feed setValue:[results objectForKey:feed_id] forKey:@"favicon"]; + [appDelegate.dictActiveFeeds setValue:feed forKey:feed_id]; + + NSString *favicon = [feed objectForKey:@"favicon"]; + if ((NSNull *)favicon != [NSNull null] && [favicon length] > 0) { + NSData *imageData = [NSData dataWithBase64EncodedString:favicon]; + UIImage *faviconImage = [UIImage imageWithData:imageData]; + [Utilities saveImage:faviconImage feedId:feed_id]; + } + } + [Utilities saveimagesToDisk]; + + dispatch_sync(dispatch_get_main_queue(), ^{ + [self.storyTitlesTable reloadData]; + }); + }); + +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} @end diff --git a/media/iphone/NewsBlur.xcodeproj/FeedTableCell.h b/media/ios/Classes/FeedTableCell.h similarity index 66% rename from media/iphone/NewsBlur.xcodeproj/FeedTableCell.h rename to media/ios/Classes/FeedTableCell.h index 4cfca8266..2c56e4a3d 100644 --- a/media/iphone/NewsBlur.xcodeproj/FeedTableCell.h +++ b/media/ios/Classes/FeedTableCell.h @@ -23,16 +23,18 @@ NSString *_positiveCountStr; NSString *_neutralCountStr; NSString *_negativeCountStr; + BOOL isSocial; } -@property (nonatomic, retain) NewsBlurAppDelegate *appDelegate; -@property (nonatomic, retain) NSString *feedTitle; -@property (nonatomic, retain) UIImage *feedFavicon; +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (nonatomic) NSString *feedTitle; +@property (nonatomic) UIImage *feedFavicon; @property (assign, nonatomic) int positiveCount; @property (assign, nonatomic) int neutralCount; @property (assign, nonatomic) int negativeCount; -@property (nonatomic, retain) NSString *positiveCountStr; -@property (nonatomic, retain) NSString *neutralCountStr; -@property (nonatomic, retain) NSString *negativeCountStr; +@property (assign, nonatomic) BOOL isSocial; +@property (nonatomic) NSString *positiveCountStr; +@property (nonatomic) NSString *neutralCountStr; +@property (nonatomic) NSString *negativeCountStr; @end diff --git a/media/ios/Classes/FeedTableCell.m b/media/ios/Classes/FeedTableCell.m new file mode 100644 index 000000000..e24bc3b06 --- /dev/null +++ b/media/ios/Classes/FeedTableCell.m @@ -0,0 +1,253 @@ +// +// FeedTableCell.m +// NewsBlur +// +// Created by Samuel Clay on 7/18/11. +// Copyright 2011 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "FeedTableCell.h" +#import "ABTableViewCell.h" +#import "UIView+TKCategory.h" + +static UIFont *textFont = nil; +static UIFont *indicatorFont = nil; +static UIColor *indicatorWhiteColor = nil; +static UIColor *indicatorBlackColor = nil; +static UIColor *positiveBackgroundColor = nil; +static UIColor *neutralBackgroundColor = nil; +static UIColor *negativeBackgroundColor = nil; +static CGFloat *psColors = nil; + +@implementation FeedTableCell + +@synthesize appDelegate; +@synthesize feedTitle; +@synthesize feedFavicon; +@synthesize positiveCount = _positiveCount; +@synthesize neutralCount = _neutralCount; +@synthesize negativeCount = _negativeCount; +@synthesize positiveCountStr; +@synthesize neutralCountStr; +@synthesize negativeCountStr; +@synthesize isSocial; + ++ (void) initialize{ + if (self == [FeedTableCell class]) { + textFont = [UIFont boldSystemFontOfSize:18]; + indicatorFont = [UIFont boldSystemFontOfSize:12]; + indicatorWhiteColor = [UIColor whiteColor]; + indicatorBlackColor = [UIColor blackColor]; + + UIColor *ps = UIColorFromRGB(0x3B7613); + UIColor *nt = UIColorFromRGB(0xF9C72A); + UIColor *ng = UIColorFromRGB(0xCC2A2E); + positiveBackgroundColor = ps; + neutralBackgroundColor = nt; + negativeBackgroundColor = ng; +// 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; + } +} + + +- (void) setPositiveCount:(int)ps { + if (ps == _positiveCount) return; + + _positiveCount = ps; + _positiveCountStr = [NSString stringWithFormat:@"%d", ps]; + [self setNeedsDisplay]; +} + +- (void) setNeutralCount:(int)nt { + if (nt == _neutralCount) return; + + _neutralCount = nt; + _neutralCountStr = [NSString stringWithFormat:@"%d", nt]; + [self setNeedsDisplay]; +} + +- (void) setNegativeCount:(int)ng { + if (ng == _negativeCount) return; + + _negativeCount = ng; + _negativeCountStr = [NSString stringWithFormat:@"%d", ng]; + [self setNeedsDisplay]; +} + + +- (void) drawContentView:(CGRect)r highlighted:(BOOL)highlighted { + + CGContextRef context = UIGraphicsGetCurrentContext(); + + UIColor *backgroundColor; + + backgroundColor = self.selected || self.highlighted ? + UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR) : + [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1.0]; + + [backgroundColor set]; + CGContextFillRect(context, r); + + if (self.highlighted || self.selected) { + // top border + UIColor *blue = UIColorFromRGB(0x6eadf5); + CGContextSetStrokeColor(context, CGColorGetComponents([blue CGColor])); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0, 0.5f); + CGContextAddLineToPoint(context, self.bounds.size.width, 0.5f); + CGContextStrokePath(context); + + // 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); + CGContextStrokePath(context); + } + + CGRect rect = CGRectInset(r, 12, 12); + rect.size.width -= 18; // Scrollbar padding + + int psWidth = _positiveCount == 0 ? 0 : _positiveCount < 10 ? + 14 : _positiveCount < 100 ? 22 : 28; + int ntWidth = _neutralCount == 0 ? 0 : _neutralCount < 10 ? + 14 : _neutralCount < 100 ? 22 : 28; + int ngWidth = _negativeCount == 0 ? 0 : _negativeCount < 10 ? + 14 : _negativeCount < 100 ? 22 : 28; + + int psOffset = _positiveCount == 0 ? 0 : psWidth - 20; + int ntOffset = _neutralCount == 0 ? 0 : ntWidth - 20; + int ngOffset = _negativeCount == 0 ? 0 : ngWidth - 20; + + int psPadding = _positiveCount == 0 ? 0 : 2; + int ntPadding = _neutralCount == 0 ? 0 : 2; + + if(_positiveCount > 0){ + [positiveBackgroundColor set]; + CGRect rr; + + if (self.isSocial) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 14, psWidth, 17); + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 10, psWidth, 17); + } + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 9, psWidth, 17); + } + + ; + [UIView drawLinearGradientInRect:rr colors:psColors]; + [UIView drawRoundRectangleInRect:rr withRadius:4]; + + [indicatorWhiteColor set]; + + CGSize size = [_positiveCountStr sizeWithFont:indicatorFont]; + float x_pos = (rr.size.width - size.width) / 2; + float y_pos = (rr.size.height - size.height) / 2; + [_positiveCountStr + drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) + withFont:indicatorFont]; + } + if(_neutralCount > 0 && appDelegate.selectedIntelligence <= 0){ + [neutralBackgroundColor set]; + + CGRect rr; + if (self.isSocial) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 14, ntWidth, 17); + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 10, ntWidth, 17); + } + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 9, ntWidth, 17); + } + + [UIView drawRoundRectangleInRect:rr withRadius:4]; +// [UIView drawLinearGradientInRect:rr colors:ntColors]; + + [indicatorBlackColor set]; + CGSize size = [_neutralCountStr sizeWithFont:indicatorFont]; + float x_pos = (rr.size.width - size.width) / 2; + float y_pos = (rr.size.height - size.height) / 2; + [_neutralCountStr + drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) + withFont:indicatorFont]; + } + if(_negativeCount > 0 && appDelegate.selectedIntelligence <= -1){ + [negativeBackgroundColor set]; + CGRect rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntWidth - ntPadding - ngOffset, self.isSocial ? 14: 9, ngWidth, 17); + [UIView drawRoundRectangleInRect:rr withRadius:4]; +// [UIView drawLinearGradientInRect:rr colors:ngColors]; + + [indicatorWhiteColor set]; + CGSize size = [_negativeCountStr sizeWithFont:indicatorFont]; + float x_pos = (rr.size.width - size.width) / 2; + float y_pos = (rr.size.height - size.height) / 2; + [_negativeCountStr + drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) + withFont:indicatorFont]; + } + + UIColor *textColor = self.selected || self.highlighted ? + [UIColor blackColor]: + [UIColor blackColor]; + + [textColor set]; + UIFont *font; + if (self.negativeCount || self.neutralCount || self.positiveCount) { + font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:13.0]; + } else { + font = [UIFont fontWithName:@"Helvetica" size:12.6]; + } + + if (isSocial) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.feedFavicon drawInRect:CGRectMake(12.0, 5.0, 36.0, 36.0)]; + [feedTitle + drawInRect:CGRectMake(56, 13, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10 - 20, 20.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + } else { + [self.feedFavicon drawInRect:CGRectMake(9.0, 3.0, 32.0, 32.0)]; + [feedTitle + drawInRect:CGRectMake(50, 11, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10 - 20, 20.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + } + + } else { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.feedFavicon drawInRect:CGRectMake(12.0, 9.0, 16.0, 16.0)]; + [feedTitle + drawInRect:CGRectMake(36.0, 9.0, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10, 20.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + } else { + [self.feedFavicon drawInRect:CGRectMake(9.0, 9.0, 16.0, 16.0)]; + [feedTitle + drawInRect:CGRectMake(34.0, 9.0, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10, 20.0) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + } + } + +} + + + +@end \ No newline at end of file diff --git a/media/ios/Classes/FeedsMenuViewController.h b/media/ios/Classes/FeedsMenuViewController.h new file mode 100644 index 000000000..c7b6008a8 --- /dev/null +++ b/media/ios/Classes/FeedsMenuViewController.h @@ -0,0 +1,23 @@ +// +// FeedsMenuViewController.h +// NewsBlur +// +// Created by Roy Yang on 6/19/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface FeedsMenuViewController : UIViewController + { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic, strong) NSArray *menuOptions; +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property ( nonatomic) IBOutlet UITableView *menuTableView; + +@end diff --git a/media/ios/Classes/FeedsMenuViewController.m b/media/ios/Classes/FeedsMenuViewController.m new file mode 100644 index 000000000..76fb2c501 --- /dev/null +++ b/media/ios/Classes/FeedsMenuViewController.m @@ -0,0 +1,95 @@ +// +// FeedsMenuViewController.m +// NewsBlur +// +// Created by Roy Yang on 6/19/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FeedsMenuViewController.h" +#import "NewsBlurAppDelegate.h" +#import "MBProgressHUD.h" +#import "NBContainerViewController.h" +#import "NewsBlurViewController.h" + +@implementation FeedsMenuViewController + +@synthesize appDelegate; +@synthesize menuOptions; +@synthesize menuTableView; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + + self.menuOptions = [[NSArray alloc] + initWithObjects:@"Find Friends", @"Logout", nil]; +} + +- (void)viewDidUnload +{ + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; + self.menuOptions = nil; + self.menuTableView = nil; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +#pragma mark - +#pragma mark - Table view data source + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [self.menuOptions count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *CellIndentifier = @"Cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIndentifier]; + + if (cell == nil) { + cell = [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CellIndentifier]; + } + + cell.textLabel.text = [self.menuOptions objectAtIndex:[indexPath row]]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.row == 0) { + [appDelegate showFindFriends]; + } if (indexPath.row == 1) { + [appDelegate confirmLogout]; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController hidePopover]; + } else { + [appDelegate.feedsViewController.popoverController dismissPopoverAnimated:YES]; + appDelegate.feedsViewController.popoverController = nil; + } + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + +} + +@end diff --git a/media/ios/Classes/FeedsMenuViewController.xib b/media/ios/Classes/FeedsMenuViewController.xib new file mode 100644 index 000000000..68d3fa019 --- /dev/null +++ b/media/ios/Classes/FeedsMenuViewController.xib @@ -0,0 +1,1622 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBProxyObject + IBUIView + IBUITableView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 274 + {320, 460} + + + _NS:9 + + 3 + MQA + + YES + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + {320, 460} + + + + + 3 + MQA + + 2 + + + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + menuTableView + + + + 19 + + + + dataSource + + + + 14 + + + + delegate + + + + 15 + + + + + + 0 + + + + + + 1 + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 12 + + + + + + + FeedsMenuViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 19 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + tapCancelButton: + UIBarButtonItem + + + tapCancelButton: + + tapCancelButton: + UIBarButtonItem + + + + NewsBlurAppDelegate + UITableView + UIToolbar + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + toolbar + UIToolbar + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UITableViewController + + NewsBlurAppDelegate + UISearchBar + UISearchDisplayController + + + + appDelegate + NewsBlurAppDelegate + + + searchBar + UISearchBar + + + searchDisplayController + UISearchDisplayController + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + GoogleReaderViewController + UIViewController + + tapCancelButton: + id + + + tapCancelButton: + + tapCancelButton: + id + + + + NewsBlurAppDelegate + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + webView + UIWebView + + + + IBProjectSource + ./Classes/GoogleReaderViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + GoogleReaderViewController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + googleReaderViewController + GoogleReaderViewController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + showMenuButton: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + toolbarTitle + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UIBarButtonItem + UIProgressView + UIView + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + 1181 + + diff --git a/media/ios/Classes/FindSitesViewController.h b/media/ios/Classes/FindSitesViewController.h new file mode 100644 index 000000000..505b95303 --- /dev/null +++ b/media/ios/Classes/FindSitesViewController.h @@ -0,0 +1,34 @@ +// +// findSitesViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/31/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface FindSitesViewController : UIViewController + { + NewsBlurAppDelegate *appDelegate; + UISearchBar *sitesSearchBar; + UITableView *sitesTable; + + NSArray *sites; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UISearchBar *sitesSearchBar; +@property (nonatomic) IBOutlet UITableView *sitesTable; +@property (nonatomic) NSArray *sites; + + +- (void)doCancelButton; +- (void)loadSitesList:(NSString *)query; +- (void)requestFinished:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; + +@end diff --git a/media/ios/Classes/FindSitesViewController.m b/media/ios/Classes/FindSitesViewController.m new file mode 100644 index 000000000..70fd1b3ba --- /dev/null +++ b/media/ios/Classes/FindSitesViewController.m @@ -0,0 +1,335 @@ +// +// FriendsListViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/1/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" +#import "FindSitesViewController.h" +#import "AddSiteViewController.h" +#import "MBProgressHUD.h" +#import "FeedDetailTableCell.h" +#import "Utilities.h" +#import "Base64.h" + +#define FIND_SITES_ROW_HEIGHT 81; + +@implementation UINavigationController (DelegateAutomaticDismissKeyboard) +- (BOOL)disablesAutomaticKeyboardDismissal { + return [self.topViewController disablesAutomaticKeyboardDismissal]; +} +@end + +@interface FindSitesViewController() + +@property (readwrite) BOOL inSearch_; +@property (nonatomic) NSString *searchTerm_; +@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator_; +@end + +@implementation FindSitesViewController + +@synthesize appDelegate; +@synthesize sitesSearchBar; +@synthesize sitesTable; +@synthesize sites; +@synthesize inSearch_; +@synthesize searchTerm_; +@synthesize loadingIndicator_; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + + if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + } + return self; +} + +- (void)viewDidLoad { + + self.sitesTable.separatorStyle = UITableViewCellSeparatorStyleNone; + + // loading indicator + UIActivityIndicatorView *loader = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + + + self.loadingIndicator_ = loader; + [self.view addSubview:self.loadingIndicator_]; + + + + [super viewDidLoad]; + + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + + self.navigationItem.title = @"Find Sites"; + UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle: @"Done" + style: UIBarButtonSystemItemCancel + target: self + action: @selector(doCancelButton)]; + [self.navigationItem setRightBarButtonItem:cancelButton]; + + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + + self.sitesTable.rowHeight = FIND_SITES_ROW_HEIGHT; + +} + +- (void)viewDidUnload +{ + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; + + self.appDelegate = nil; + self.sitesSearchBar = nil; + self.sitesTable = nil; + self.sites = nil; + +} + +- (void)viewWillAppear:(BOOL)animated { + [self.sitesSearchBar becomeFirstResponder]; + +} + +- (void)viewDidAppear:(BOOL)animated { + CGRect vb = self.view.bounds; + self.loadingIndicator_.frame = CGRectMake(vb.size.width - 52, 12,20,20); +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [self.sitesTable reloadData]; +} + +- (void)doCancelButton { + [appDelegate.modalNavigationController dismissModalViewControllerAnimated:YES]; +} + +#pragma mark - UISearchBar delegate methods + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + if (searchBar.text.length == 0) { + self.sites = nil; + self.inSearch_ = NO; + [self.sitesTable reloadData]; + } else { + self.inSearch_ = YES; + self.searchTerm_ = searchText; + [self loadSitesList:searchText]; + } + +} + +- (void)searchBarTextDidEndEditing:(UISearchBar *)theSearchBar { + [theSearchBar resignFirstResponder]; +} + +- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { + [searchBar resignFirstResponder]; + +} + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + searchBar.text = nil; +} + +- (void)loadSitesList:(NSString *)query { + [self.loadingIndicator_ startAnimating]; +// [MBProgressHUD hideHUDForView:self.view animated:YES]; +// MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; +// HUD.labelText = @"Searching..."; + + NSString *urlString = [NSString stringWithFormat:@"http://%@/rss_feeds/feed_autocomplete?term=%@&limit=10&v=2", + NEWSBLUR_URL, [query stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + + NSURL *url = [NSURL URLWithString:urlString]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(requestFinished:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)requestFinished:(ASIHTTPRequest *)request { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + if (self.inSearch_) { + NSString *responseString = [request responseString]; + NSData *responseData= [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // int statusCode = [request responseStatusCode]; + + self.sites = [results objectForKey:@"feeds"]; + + [self.sitesTable reloadData]; + + NSString *originalSearchTerm = [NSString stringWithFormat:@"%@", [results objectForKey:@"term"]]; + if ([self.searchTerm_ isEqualToString:originalSearchTerm]) { + [self.loadingIndicator_ stopAnimating]; + } + } + +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +- (BOOL)disablesAutomaticKeyboardDismissal { + return NO; +} + +#pragma mark - Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return FIND_SITES_ROW_HEIGHT; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (self.inSearch_){ + int siteCount = [self.sites count]; + if (siteCount == 0) { + return 3; + } + return siteCount; + } else { + return 3; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + CGRect vb = self.view.bounds; + + static NSString *CellIdentifier = @"AddSiteEmptyCellIdentifier"; + int sitesCount = [self.sites count]; + + if (self.inSearch_ && sitesCount){ + if (sitesCount > indexPath.row) { + + + + + FeedDetailTableCell *cell = (FeedDetailTableCell *)[tableView + dequeueReusableCellWithIdentifier:@"AddSiteCellIdentifier"]; + if (cell == nil) { + cell = [[FeedDetailTableCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil]; + } + + + NSDictionary *site = [self.sites objectAtIndex:indexPath.row]; + + cell.siteTitle = [site objectForKey:@"label"]; + cell.storyTitle = [site objectForKey:@"tagline"]; + cell.storyAuthor = [site objectForKey:@"value"]; + // adding comma to the number of subscribers + NSNumberFormatter *formatter = [NSNumberFormatter new]; + [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; // this line is important! + NSString *formatted = [formatter stringFromNumber:[NSNumber numberWithInteger:[[site objectForKey:@"num_subscribers"] intValue]]]; + NSString *subscribers = [NSString stringWithFormat:@"%@ subscribers", formatted]; + cell.storyDate = subscribers; + cell.isRiverOrSocial = YES; + + // feed color bar border + unsigned int colorBorder = 0; + NSString *faviconColor = [site valueForKey:@"favicon_color"]; + + if ([faviconColor class] == [NSNull class]) { + faviconColor = @"505050"; + } + NSScanner *scannerBorder = [NSScanner scannerWithString:faviconColor]; + [scannerBorder scanHexInt:&colorBorder]; + + cell.feedColorBar = UIColorFromRGB(colorBorder); + + // feed color bar border + NSString *faviconFade = [site valueForKey:@"favicon_border"]; + if ([faviconFade class] == [NSNull class]) { + faviconFade = @"505050"; + } + scannerBorder = [NSScanner scannerWithString:faviconFade]; + [scannerBorder scanHexInt:&colorBorder]; + cell.feedColorBarTopBorder = UIColorFromRGB(colorBorder); + + // favicon + NSString *faviconStr = [NSString stringWithFormat:@"%@", [site valueForKey:@"favicon"]]; + NSData *imageData = [NSData dataWithBase64EncodedString:faviconStr]; + UIImage *faviconImage = [UIImage imageWithData:imageData]; + + cell.siteFavicon = faviconImage; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + return cell; + } + } + + UITableViewCell *cell = [tableView + dequeueReusableCellWithIdentifier:CellIdentifier]; + + if (cell == nil) { + cell = [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:nil]; + } else { + [[[cell contentView] subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)]; + } + + int row = 0; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + row = 1; + } + + UILabel *msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vb.size.width, 81)]; + msg.textColor = UIColorFromRGB(0x7a7a7a); + if (vb.size.width > 320) { + msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 20.0]; + } else { + msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 14.0]; + } + msg.textAlignment = UITextAlignmentCenter; + + if (self.inSearch_ && sitesCount){ + if (indexPath.row == row) { + [cell.contentView addSubview:msg]; + msg.text = @"No results."; + } + } else { + if (indexPath.row == row) { + msg.text = @"Search for sites above"; + } + } + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + [self.sitesSearchBar resignFirstResponder]; +} + +-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self.sitesSearchBar resignFirstResponder]; + [appDelegate.modalNavigationController pushViewController:appDelegate.addSiteViewController animated:YES]; + +} + +@end diff --git a/media/ios/Classes/FindSitesViewController.xib b/media/ios/Classes/FindSitesViewController.xib new file mode 100644 index 000000000..9bb24be76 --- /dev/null +++ b/media/ios/Classes/FindSitesViewController.xib @@ -0,0 +1,1680 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUITableView + IBUIView + IBUISearchBar + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 274 + {{0, 44}, {320, 416}} + + + _NS:9 + + 3 + MQA + + YES + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + 290 + {320, 44} + + + + _NS:9 + 3 + IBCocoaTouchFramework + Find sites by URL or name + + 1 + IBCocoaTouchFramework + + + + {{0, 20}, {320, 460}} + + + + + 3 + MQA + + 2 + + + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + sitesTable + + + + 6 + + + + sitesSearchBar + + + + 7 + + + + delegate + + + + 8 + + + + dataSource + + + + 9 + + + + delegate + + + + 10 + + + + + + 0 + + + + + + 1 + + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 4 + + + + + 5 + + + + + + + FindSitesViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 10 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + GoogleReaderViewController + UIViewController + + tapCancelButton: + id + + + tapCancelButton: + + tapCancelButton: + id + + + + NewsBlurAppDelegate + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + webView + UIWebView + + + + IBProjectSource + ./Classes/GoogleReaderViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + GoogleReaderViewController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + googleReaderViewController + GoogleReaderViewController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + toolbarTitle + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + 1181 + + diff --git a/media/ios/Classes/FirstTimeUserAddFriendsViewController.h b/media/ios/Classes/FirstTimeUserAddFriendsViewController.h new file mode 100644 index 000000000..e1abc8850 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddFriendsViewController.h @@ -0,0 +1,45 @@ +// +// FTUXAddFriendsViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +#import "NewsBlurAppDelegate.h" + +@class ASIHTTPRequest; + +@interface FirstTimeUserAddFriendsViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIBarButtonItem *nextButton; +@property (weak, nonatomic) IBOutlet UIButton *facebookButton; +@property (weak, nonatomic) IBOutlet UIButton *twitterButton; +@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *facebookActivityIndicator; +@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *twitterActivityIndicator; +@property (weak, nonatomic) IBOutlet UILabel *friendsLabel; + +- (IBAction)tapNextButton; +- (IBAction)tapTwitterButton; +- (IBAction)tapFacebookButton; +- (void)selectTwitterButton; +- (void)selectFacebookButton; +- (IBAction)toggleAutoFollowFriends:(id)sender; + +- (void)connectToSocial; +- (void)finishConnectFromSocial:(ASIHTTPRequest *)request; + +- (void)finishTwitterConnect; +- (void)finishFacebookConnect; + +- (void)finishedWithError:(ASIHTTPRequest *)request; +- (void)finishToggleAutoFollowFriends:(ASIHTTPRequest *)request; + +- (void)changeMessaging:(NSString *)msg; + +@end \ No newline at end of file diff --git a/media/ios/Classes/FirstTimeUserAddFriendsViewController.m b/media/ios/Classes/FirstTimeUserAddFriendsViewController.m new file mode 100644 index 000000000..11052e818 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddFriendsViewController.m @@ -0,0 +1,247 @@ +// +// FTUXAddFriendsViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FirstTimeUserAddFriendsViewController.h" +#import "FirstTimeUserAddNewsBlurViewController.h" +#import "AuthorizeServicesViewController.h" +#import "ASIHTTPRequest.h" + +@interface FirstTimeUserAddFriendsViewController () + +@end + +@implementation FirstTimeUserAddFriendsViewController + +@synthesize appDelegate; +@synthesize nextButton; +@synthesize facebookButton; +@synthesize twitterButton; +@synthesize facebookActivityIndicator; +@synthesize twitterActivityIndicator; +@synthesize friendsLabel; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + + + UIBarButtonItem *next = [[UIBarButtonItem alloc] initWithTitle:@"Skip this step" style:UIBarButtonSystemItemDone target:self action:@selector(tapNextButton)]; + self.nextButton = next; + self.navigationItem.rightBarButtonItem = next; + + self.navigationItem.title = @"Find Friends"; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.friendsLabel.font = [UIFont systemFontOfSize:14]; + } +} + +- (void)viewDidUnload { + [self setNextButton:nil]; + [self setFacebookButton:nil]; + [self setTwitterButton:nil]; + [self setFacebookActivityIndicator:nil]; + [self setTwitterActivityIndicator:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { +// [self selectTwitterButton]; + [self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + + return NO; +} + +- (IBAction)tapNextButton { + [appDelegate.ftuxNavigationController pushViewController:appDelegate.firstTimeUserAddNewsBlurViewController animated:YES]; +} + +- (IBAction)tapTwitterButton { + AuthorizeServicesViewController *service = [[AuthorizeServicesViewController alloc] init]; + service.url = @"/oauth/twitter_connect"; + service.type = @"twitter"; + [appDelegate.ftuxNavigationController pushViewController:service animated:YES]; +} + + +- (IBAction)tapFacebookButton { + AuthorizeServicesViewController *service = [[AuthorizeServicesViewController alloc] init]; + service.url = @"/oauth/facebook_connect"; + service.type = @"facebook"; + [appDelegate.ftuxNavigationController pushViewController:service animated:YES]; +} + + +- (void)selectTwitterButton { + self.nextButton.title = @"Next"; + self.twitterButton.userInteractionEnabled = NO; + [self.twitterButton setTitle:@"Connecting" forState:UIControlStateNormal]; + [self.twitterActivityIndicator startAnimating]; + [self connectToSocial]; +} + +- (void)selectFacebookButton { + self.nextButton.title = @"Next"; + self.facebookButton.userInteractionEnabled = NO; + [self.facebookButton setTitle:@"Connecting" forState:UIControlStateNormal]; + [self.facebookActivityIndicator startAnimating]; + [self connectToSocial]; +} + +#pragma mark - +#pragma mark Check Social + +- (void)connectToSocial { + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/load_user_friends", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishConnectFromSocial:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)finishConnectFromSocial:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); + + BOOL facebookSync = [[[[results objectForKey:@"services"] objectForKey:@"facebook"] objectForKey:@"syncing"] boolValue]; + BOOL twitterSync = [[[[results objectForKey:@"services"] objectForKey:@"twitter"] objectForKey:@"syncing"] boolValue]; + + if (![[[[results objectForKey:@"services"] objectForKey:@"facebook"] objectForKey:@"facebook_uid"] isKindOfClass:[NSNull class]]) { + [self finishFacebookConnect]; + } else { + if (facebookSync) { + [self performSelector:@selector(connectToSocial) withObject:self afterDelay:3]; + } + } + + if (![[[[results objectForKey:@"services"] objectForKey:@"twitter"] objectForKey:@"twitter_uid"] isKindOfClass:[NSNull class]]) { + [self finishTwitterConnect]; + } else { + if (twitterSync) { + [self performSelector:@selector(connectToSocial) withObject:self afterDelay:3]; + } + } +} + +- (void)finishFacebookConnect { + [self.facebookActivityIndicator stopAnimating]; + self.friendsLabel.textColor = UIColorFromRGB(0x333333); + self.facebookButton.selected = YES; + self.friendsLabel.text = @"You have successfully connected to Facebook."; + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(self.facebookButton.frame.origin.x + self.facebookButton.frame.size.width - 24, + self.facebookButton.frame.origin.y + 8, + 16, + 16); + [self.view addSubview:checkmarkView]; + +} + +- (void)finishTwitterConnect { + [self.twitterActivityIndicator stopAnimating]; + self.friendsLabel.textColor = UIColorFromRGB(0x333333); + self.friendsLabel.text = @"You have successfully connected to Twitter."; + + self.twitterButton.selected = YES; + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(self.twitterButton.frame.origin.x + self.twitterButton.frame.size.width - 24, + self.twitterButton.frame.origin.y + 8, + 16, + 16); + [self.view addSubview:checkmarkView]; +} + +#pragma mark - +#pragma mark Toggle Auto Follow + +- (IBAction)toggleAutoFollowFriends:(id)sender { + UISwitch *button = (UISwitch *)sender; + + NSURL *preferenceURL = [NSURL URLWithString: + [NSString stringWithFormat:@"http://%@/profile/set_preference", + NEWSBLUR_URL]]; + + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:preferenceURL]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] + setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; + + if (button.on) { + [request setPostValue:@"false" forKey:@"autofollow_friends"]; + } else { + [request setPostValue:@"true" forKey:@"autofollow_friends"]; + } + + [request setDelegate:self]; + [request setResponseEncoding:NSUTF8StringEncoding]; + [request setDefaultResponseEncoding:NSUTF8StringEncoding]; + [request setDidFinishSelector:@selector(finishToggleAutoFollowFriends:)]; + [request setDidFailSelector:@selector(finishedWithError:)]; + [request setTimeOutSeconds:30]; + [request startAsynchronous]; +} + +- (void)finishedWithError:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); + +} + +- (void)finishToggleAutoFollowFriends:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); +} + +- (void)changeMessaging:(NSString *)msg { + self.friendsLabel.text = msg; + self.friendsLabel.textColor = [UIColor redColor]; +} + +@end diff --git a/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib b/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib new file mode 100644 index 000000000..7161cbbf5 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib @@ -0,0 +1,1862 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIView + IBUIImageView + IBUILabel + IBProxyObject + IBUIActivityIndicatorView + IBUISwitch + IBUIButton + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 292 + + + + 274 + {540, 540} + + + + _NS:9 + YES + 4 + NO + IBIPadFramework + + NSImage + subtle-pattern-4.jpg + + + + + 303 + {{70, 49}, {400, 100}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Connect with your friends to easily follow the stories that matter to them. + + 1 + MCAwIDAAA + + + + 3 + MQA + + {0, 1} + 0 + 10 + 5 + 1 + + 1 + 18 + + + Helvetica + 18 + 16 + + + + + 303 + {{121, 421}, {298, 106}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Feel comfortable connecting to these services. Nothing happens without your permission. + + + + 0 + 10 + 4 + 1 + + 1 + 13 + + + Helvetica + 13 + 16 + + + + + 301 + {{178, 175}, {184, 34}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + 0.0 + 0.0 + 20 + 0.0 + Connected + Twitter + + + + + 1 + MCAwLjUwMTk2MDgxNCAxAA + + + NSImage + twitter.png + + + 2 + 13 + + + Helvetica-Bold + 13 + 16 + + + + + -2147483347 + {{215, 182}, {20, 20}} + + + + _NS:9 + NO + IBIPadFramework + + + + 301 + {{178, 262}, {184, 34}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + 0.0 + 0.0 + 20 + 0.0 + Connected + Facebook + + + + + 1 + MCAwLjI1MDk4MDQwNyAwLjUwMTk2MDgxNAA + + + NSImage + facebook.png + + + + + + + -2147483347 + {{215, 269}, {20, 20}} + + + + _NS:9 + NO + IBIPadFramework + + + + 301 + + + + 301 + {{27, 17}, {122, 34}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Auto-follow friends + + + + 0 + 10 + 1 + + + + + + 301 + {{142, 20}, {94, 27}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + YES + + + {{145, 361}, {251, 68}} + + + + _NS:9 + + 3 + MCAwAA + + NO + IBIPadFramework + + + {{0, 44}, {540, 540}} + + + + _NS:9 + + 3 + MQA + + 2 + + + + NO + + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 131 + + + + facebookButton + + + + 136 + + + + twitterButton + + + + 137 + + + + facebookActivityIndicator + + + + 140 + + + + twitterActivityIndicator + + + + 141 + + + + friendsLabel + + + + 143 + + + + tapFacebookButton + + + 7 + + 135 + + + + tapTwitterButton + + + 7 + + 133 + + + + toggleAutoFollowFriends: + + + 13 + + 148 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 129 + + + + + + + + + + + + + + + 53 + + + + + 54 + + + + + 56 + + + + + 138 + + + + + 139 + + + + + 142 + + + + + 146 + + + + + 149 + + + + + + + + + 145 + + + + + 144 + + + + + + + FirstTimeUserAddFriendsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + + 149 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UILabel + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UITableView + UIButton + UIView + UILabel + UIBarButtonItem + UIScrollView + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + categoriesTable + UITableView + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + scrollView + UIScrollView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + UIView + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + noFocusMessage + UIView + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + {184, 34} + {1000, 1000} + {184, 34} + + 1181 + + diff --git a/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.h b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.h new file mode 100644 index 000000000..74e8f0ad3 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.h @@ -0,0 +1,29 @@ +// +// FTUXAddNewsBlurViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +#import "NewsBlurAppDelegate.h" + +@interface FirstTimeUserAddNewsBlurViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIBarButtonItem *nextButton; +@property (strong, nonatomic) IBOutlet UILabel *instructionsLabel; + +- (IBAction)tapNextButton; +- (IBAction)tapNewsBlurButton:(id)sender; +- (IBAction)tapPopularButton:(id)sender; + +- (void)finishAddSite:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)addSite:(NSString *)siteUrl; +- (void)addPopular; +@end \ No newline at end of file diff --git a/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.m b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.m new file mode 100644 index 000000000..f04c43e68 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.m @@ -0,0 +1,156 @@ +// +// FTUXAddNewsBlurViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FirstTimeUserAddNewsBlurViewController.h" +#import "NewsBlurViewController.h" + +@implementation FirstTimeUserAddNewsBlurViewController + +@synthesize appDelegate; +@synthesize nextButton; +@synthesize instructionsLabel; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + + + UIBarButtonItem *next = [[UIBarButtonItem alloc] initWithTitle:@"Start reading" style:UIBarButtonSystemItemDone target:self action:@selector(tapNextButton)]; + self.nextButton = next; + self.navigationItem.rightBarButtonItem = next; + + self.navigationItem.title = @"All Done!"; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.instructionsLabel.font = [UIFont systemFontOfSize:14]; + } + +} + +- (void)viewDidUnload +{ + [self setNextButton:nil]; + [self setInstructionsLabel:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone]; +} + +- (void)viewDidAppear:(BOOL)animated { + +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + + return NO; +} + + +- (IBAction)tapNextButton { + [appDelegate.ftuxNavigationController dismissModalViewControllerAnimated:YES]; + [appDelegate.feedsViewController fetchFeedList:NO]; +} + +- (IBAction)tapNewsBlurButton:(id)sender { + UIButton *button = (UIButton *)sender; + button.selected = YES; + button.userInteractionEnabled = NO; + + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(button.frame.origin.x + button.frame.size.width - 24, + button.frame.origin.y + 8, + 16, + 16); + [self.view addSubview:checkmarkView]; + + [self addSite:@"http://blog.newsblur.com/rss"]; +} + +- (IBAction)tapPopularButton:(id)sender { + UIButton *button = (UIButton *)sender; + button.selected = YES; + button.userInteractionEnabled = NO; + + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(button.frame.origin.x + button.frame.size.width - 24, + button.frame.origin.y + 8, + 16, + 16); + [self.view addSubview:checkmarkView]; + [self addPopular]; +} + +#pragma mark - +#pragma mark Add Site + +- (void)addPopular { + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/follow/", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + + [request setPostValue:@"social:popular" forKey:@"user_id"]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishAddSite:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)addSite:(NSString *)siteUrl { + NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/add_url/", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + + [request setPostValue:siteUrl forKey:@"url"]; + [request setPostValue:@"true" forKey:@"auto_active"]; + [request setPostValue:@"true" forKey:@"skip_fetch"]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishAddSite:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +- (void)finishAddSite:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); +} + +@end diff --git a/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.xib b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.xib new file mode 100644 index 000000000..09594e213 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddNewsBlurViewController.xib @@ -0,0 +1,1686 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIButton + IBUIImageView + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 292 + + + + 292 + {540, 576} + + + + _NS:9 + YES + 4 + NO + IBIPadFramework + + NSImage + subtle-pattern-4.jpg + + + + + 303 + {{70, 86}, {400, 100}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Wonderful things are happening at NewsBlur. Add our blog for the latest news. + + 1 + MCAwIDAAA + + + + 3 + MQA + + {0, 1} + 0 + 10 + 7 + 1 + + 1 + 18 + + + Helvetica + 18 + 16 + + + + + 301 + {{178, 220}, {184, 34}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + Following NewsBlur + Follow NewsBlur Blog + + + 3 + MQA + + 2 + + + + + 1 + MC42IDAuNiAwLjYAA + + + NSImage + google.png + + + + 2 + 13 + + + Helvetica-Bold + 13 + 16 + + + + + 301 + {{178, 300}, {184, 34}} + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + Following Popular + Follow Popular Stories + + + 3 + MQA + + + + + 1 + MC42IDAuNiAwLjYAA + + + + + + + + {{0, 44}, {540, 576}} + + + + _NS:9 + + 3 + MQA + + + + NO + + + IBUIModalFormSheetSimulatedSizeMetrics + + YES + + + + + + {540, 620} + {540, 620} + + + IBIPadFramework + Form Sheet + + IBIPadFramework + + + + + + + view + + + + 130 + + + + instructionsLabel + + + + 138 + + + + tapNewsBlurButton: + + + 7 + + 133 + + + + tapNewsBlurButton: + + + 7 + + 135 + + + + tapPopularButton: + + + 7 + + 136 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 129 + + + + + + + + + + + 57 + + + + + 131 + + + + + 58 + + + + + 134 + + + + + + + FirstTimeUserAddNewsBlurViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + + 138 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UILabel + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UITableView + UIButton + UIView + UILabel + UIBarButtonItem + UIScrollView + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + categoriesTable + UITableView + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + scrollView + UIScrollView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + UIView + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + noFocusMessage + UIView + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + {184, 34} + {1000, 1000} + + 1181 + + diff --git a/media/ios/Classes/FirstTimeUserAddSitesViewController.h b/media/ios/Classes/FirstTimeUserAddSitesViewController.h new file mode 100644 index 000000000..9e7418cdb --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddSitesViewController.h @@ -0,0 +1,34 @@ +// +// FTUXaddSitesViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" + +@interface FirstTimeUserAddSitesViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIButton *googleReaderButton; +@property (nonatomic) IBOutlet UIView *googleReaderButtonWrapper; +@property (nonatomic) IBOutlet UIBarButtonItem *nextButton; +@property (nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; +@property (nonatomic) IBOutlet UILabel *instructionLabel; +@property (nonatomic) IBOutlet UITableView *categoriesTable; +@property (strong, nonatomic) IBOutlet UIScrollView *scrollView; + +- (IBAction)tapNextButton; +- (void)tapGoogleReaderButton; + +- (void)addCategory:(id)sender; +- (void)importFromGoogleReader; +- (void)importFromGoogleReaderFailed:(NSString *)error; +- (void)updateSites; + +- (CGFloat)tableViewHeight; +@end \ No newline at end of file diff --git a/media/ios/Classes/FirstTimeUserAddSitesViewController.m b/media/ios/Classes/FirstTimeUserAddSitesViewController.m new file mode 100644 index 000000000..d4f8bb2ae --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddSitesViewController.m @@ -0,0 +1,463 @@ +// +// FTUXaddSitesViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/22/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "FirstTimeUserAddSitesViewController.h" +#import "FirstTimeUserAddFriendsViewController.h" +#import "AuthorizeServicesViewController.h" +#import "NewsBlurViewController.h" +#import "SiteCell.h" +#import "Base64.h" + +@interface FirstTimeUserAddSitesViewController() + +@property (readwrite) int importedFeedCount_; +@property (nonatomic) UIButton *currentButton_; +@property (nonatomic, strong) NSMutableSet *selectedCategories_; +@property (readwrite) BOOL googleImportSuccess_; + +@end; + +@implementation FirstTimeUserAddSitesViewController + +@synthesize appDelegate; +@synthesize googleReaderButton; +@synthesize nextButton; +@synthesize activityIndicator; +@synthesize instructionLabel; +@synthesize categoriesTable; +@synthesize scrollView; +@synthesize googleReaderButtonWrapper; +@synthesize importedFeedCount_; +@synthesize currentButton_; +@synthesize selectedCategories_; +@synthesize googleImportSuccess_; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + self.selectedCategories_ = [[NSMutableSet alloc] init]; + + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + + + UIBarButtonItem *next = [[UIBarButtonItem alloc] initWithTitle:@"Next step" style:UIBarButtonSystemItemDone target:self action:@selector(tapNextButton)]; + self.nextButton = next; + self.nextButton.enabled = NO; + self.navigationItem.rightBarButtonItem = next; + + self.navigationItem.title = @"Add Sites"; + self.activityIndicator.hidesWhenStopped = YES; + + self.categoriesTable.delegate = self; + self.categoriesTable.dataSource = self; + self.categoriesTable.backgroundColor = [UIColor clearColor]; +// self.categoriesTable.separatorColor = [UIColor clearColor]; + self.categoriesTable.opaque = NO; + self.categoriesTable.backgroundView = nil; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.instructionLabel.font = [UIFont systemFontOfSize:14]; + } + + + UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityView.frame = CGRectMake(68, 7, 20, 20.0); + self.activityIndicator = activityView; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone]; + [self.categoriesTable reloadData]; + [self.scrollView setContentSize:CGSizeMake(self.view.frame.size.width, self.tableViewHeight + 100)]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.categoriesTable.frame = CGRectMake((self.view.frame.size.width - 300)/2, 60, self.categoriesTable.frame.size.width, self.tableViewHeight); + } else { + self.categoriesTable.frame = CGRectMake(10, 60, self.categoriesTable.frame.size.width, self.tableViewHeight); + } + + NSLog(@"%f height", self.tableViewHeight); +} + +- (void)viewDidUnload { + [super viewDidUnload]; + [self setActivityIndicator:nil]; + [self setInstructionLabel:nil]; + [self setCategoriesTable:nil]; + [self setGoogleReaderButton:nil]; + [self setScrollView:nil]; + [self setNextButton:nil]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + return NO; +} + + +- (IBAction)tapNextButton { + [appDelegate.ftuxNavigationController pushViewController:appDelegate.firstTimeUserAddFriendsViewController animated:YES]; + + if (self.selectedCategories_.count) { + NSString *urlString = [NSString stringWithFormat:@"http://%@/categories/subscribe", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + + for(NSObject *category in self.selectedCategories_) { + [request addPostValue:category forKey:@"category"]; + } + + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishAddingCategories:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; + } +} + +- (void)finishAddingCategories:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); +} + +#pragma mark - +#pragma mark Import Google Reader + +- (void)tapGoogleReaderButton { + AuthorizeServicesViewController *service = [[AuthorizeServicesViewController alloc] init]; + service.url = @"/import/authorize"; + service.type = @"google"; + [appDelegate.ftuxNavigationController pushViewController:service animated:YES]; +} + +- (void)importFromGoogleReader { + UIView *header = [self.categoriesTable viewWithTag:0]; + UIButton *button = (UIButton *)[header viewWithTag:1000]; + self.googleReaderButton = button; + + self.nextButton.enabled = YES; + [self.googleReaderButton setTitle:@"Importing sites..." forState:UIControlStateNormal]; + self.instructionLabel.textColor = UIColorFromRGB(0x333333); + self.googleReaderButton.userInteractionEnabled = NO; + self.instructionLabel.text = @"This might take a minute. Feel free to continue..."; + [self.googleReaderButton addSubview:self.activityIndicator]; + [self.activityIndicator startAnimating]; + NSString *urlString = [NSString stringWithFormat:@"http://%@/import/import_from_google_reader/", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + [request setPostValue:@"true" forKey:@"auto_active"]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishImportFromGoogleReader:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)importFromGoogleReaderFailed:(NSString *)error { + [self.googleReaderButton setTitle:@"Retry Google Reader" forState:UIControlStateNormal]; + self.instructionLabel.textColor = [UIColor redColor]; + self.instructionLabel.text = error; +} + +- (void)finishImportFromGoogleReader:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + NSLog(@"results are %@", results); + + self.importedFeedCount_ = [[results objectForKey:@"feed_count"] intValue]; + [self performSelector:@selector(updateSites) withObject:nil afterDelay:1]; + self.googleImportSuccess_ = YES; +} + +- (void)updateSites { + self.instructionLabel.text = @"And just like that, we're done!\nAdd more categories or move on..."; + NSString *msg = [NSString stringWithFormat:@"Imported %i site%@", + self.importedFeedCount_, + self.importedFeedCount_ == 1 ? @"" : @"s"]; + [self.googleReaderButton setTitle:msg forState:UIControlStateSelected]; + self.googleReaderButton.selected = YES; + [self.activityIndicator stopAnimating]; + + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(self.googleReaderButton.frame.size.width - 24, + 8, + 16, + 16); + [self.googleReaderButton addSubview:checkmarkView]; +} + +#pragma mark - +#pragma mark Add Categories + +- (void)addCategory:(id)sender { + NSInteger tag = ((UIControl *) sender).tag; + + // set the currentButton + self.currentButton_ = (UIButton *)sender; + if (tag == 1000) { + [self tapGoogleReaderButton]; + } else { + UIButton *button = (UIButton *)sender; + NSLog(@"self.currentButton_.titleLabel.text is %@", self.currentButton_.titleLabel.text); + if (button.selected) { + [self.selectedCategories_ removeObject:self.currentButton_.titleLabel.text]; + + self.nextButton.enabled = YES; + button.selected = NO; + UIImageView *imageView = (UIImageView*)[button viewWithTag:100]; + [imageView removeFromSuperview]; + } else { + [self.selectedCategories_ addObject:self.currentButton_.titleLabel.text]; + button.selected = YES; + } + } + if (self.googleImportSuccess_) { + self.nextButton.enabled = YES; + } else if (self.selectedCategories_.count) { + self.nextButton.enabled = YES; + } else { + self.nextButton.enabled = NO; + } + + NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(tag - 1000, 1)]; + + [self.categoriesTable reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; +} + +- (void)finishAddFolder:(ASIHTTPRequest *)request { + NSLog(@"Successfully added."); +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +#pragma mark - +#pragma mark Add Site + +- (void)addSite:(NSString *)siteUrl { + NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/add_url", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + + [request setPostValue:siteUrl forKey:@"url"]; + + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishAddFolder:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +#pragma mark - +#pragma mark Table View - Interactions List + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return appDelegate.categories.count + 1; +} + +//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +//{ +// NSDictionary *category = [appDelegate.categories objectAtIndex:section]; +// return [category objectForKey:@"title"]; +//} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (section == 0 ) { + return 1; + } else { + NSDictionary *category = [appDelegate.categories objectAtIndex:section - 1]; + NSArray *categorySiteList = [category objectForKey:@"feed_ids"]; + return categorySiteList.count; + } + return 0; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 26; +} + +- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 54.0; +} + +- (UIView *)tableView:(UITableView *)tableView +viewForHeaderInSection:(NSInteger)section { + + // create the parent view that will hold header Label + UIView* customView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 34.0)]; + customView.tag = section; + + + UIImage *buttonImage =[[UIImage imageNamed:@"google.png"] stretchableImageWithLeftCapWidth:5.0 topCapHeight:0.0]; + UIButton *headerBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + headerBtn.tag = section + 1000; + [headerBtn setBackgroundImage:buttonImage forState:UIControlStateNormal]; + headerBtn.titleLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:14]; + + headerBtn.frame = CGRectMake(0, 20.0, 300, 34.0); + headerBtn.titleLabel.shadowColor = UIColorFromRGB(0x1E5BDB); + headerBtn.titleLabel.shadowOffset = CGSizeMake(0, 1); + NSString *categoryTitle; + if (section == 0) { + categoryTitle = @"Google Reader"; + } else { + NSDictionary *category = [appDelegate.categories objectAtIndex:section - 1]; + categoryTitle = [category objectForKey:@"title"]; + + BOOL inSelect = [self.selectedCategories_ containsObject:[NSString stringWithFormat:@"%@", [category objectForKey:@"title"]]]; + NSLog(@"inselected %i", inSelect); + if (inSelect) { + headerBtn.selected = YES; + UIImage *checkmark = [UIImage imageNamed:@"258-checkmark"]; + UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmark]; + checkmarkView.frame = CGRectMake(headerBtn.frame.origin.x + headerBtn.frame.size.width - 24, + 8, + 16, + 16); + checkmarkView.tag = 100; + [headerBtn addSubview:checkmarkView]; + } + + } + + + [headerBtn setTitle:categoryTitle forState:UIControlStateNormal]; + + [headerBtn addTarget:self action:@selector(addCategory:) forControlEvents:UIControlEventTouchUpInside]; + + + + [customView addSubview:headerBtn]; + + + + + + + return customView; +} + + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + SiteCell *cell = [tableView + dequeueReusableCellWithIdentifier:@"ActivityCell"]; + if (cell == nil) { + cell = [[SiteCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:@"ActivityCell"]; + } + + NSString *siteTitle; + + if (indexPath.section == 0 ) { + siteTitle = @"Import your sites from Google Reader"; + cell.siteFavicon = nil; + cell.feedColorBar = nil; + cell.feedColorBarTopBorder = nil; + } else { + NSDictionary *category = [appDelegate.categories objectAtIndex:indexPath.section - 1]; + NSArray *categorySiteList = [category objectForKey:@"feed_ids"]; + NSString * feedId = [NSString stringWithFormat:@"%@", [categorySiteList objectAtIndex:indexPath.row ]]; + + NSDictionary *feed = [appDelegate.categoryFeeds objectForKey:feedId]; + siteTitle = [feed objectForKey:@"feed_title"]; + + BOOL inSelect = [self.selectedCategories_ containsObject:[NSString stringWithFormat:@"%@", [category objectForKey:@"title"]]]; + + if (inSelect) { + cell.isRead = NO; + } else { + cell.isRead = YES; + } + + // feed color bar border + unsigned int colorBorder = 0; + NSString *faviconColor = [feed valueForKey:@"favicon_color"]; + + if ([faviconColor class] == [NSNull class]) { + faviconColor = @"505050"; + } + NSScanner *scannerBorder = [NSScanner scannerWithString:faviconColor]; + [scannerBorder scanHexInt:&colorBorder]; + + cell.feedColorBar = UIColorFromRGB(colorBorder); + + // feed color bar border + NSString *faviconFade = [feed valueForKey:@"favicon_border"]; + if ([faviconFade class] == [NSNull class]) { + faviconFade = @"505050"; + } + scannerBorder = [NSScanner scannerWithString:faviconFade]; + [scannerBorder scanHexInt:&colorBorder]; + cell.feedColorBarTopBorder = UIColorFromRGB(colorBorder); + + // favicon + + NSString *faviconStr = [NSString stringWithFormat:@"%@", [feed valueForKey:@"favicon"]]; + NSData *imageData = [NSData dataWithBase64EncodedString:faviconStr]; + UIImage *faviconImage = [UIImage imageWithData:imageData]; + + + cell.siteFavicon = faviconImage; + } + + cell.opaque = NO; + cell.siteTitle = siteTitle; + + return cell; +} + +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + UIView *header = [self.categoriesTable viewWithTag:indexPath.section]; + UIButton *button = (UIButton *)[header viewWithTag:indexPath.section + 1000]; + [button sendActionsForControlEvents:UIControlEventTouchUpInside]; +} + +-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { + UIView *header = [self.categoriesTable viewWithTag:indexPath.section]; + UIButton *button = (UIButton *)[header viewWithTag:indexPath.section + 1000]; + [button sendActionsForControlEvents:UIControlStateSelected]; + return indexPath; +} + +- (CGFloat)tableViewHeight { + [self.categoriesTable layoutIfNeeded]; + return [self.categoriesTable contentSize].height; +} + +@end diff --git a/media/ios/Classes/FirstTimeUserAddSitesViewController.xib b/media/ios/Classes/FirstTimeUserAddSitesViewController.xib new file mode 100644 index 000000000..a2cf3b886 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserAddSitesViewController.xib @@ -0,0 +1,1633 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUILabel + IBUITableView + IBUIImageView + IBUIView + IBUIScrollView + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 292 + + + + 274 + {540, 540} + + + + _NS:9 + YES + 4 + NO + IBIPadFramework + + NSImage + subtle-pattern-4.jpg + + + + + 274 + + + + 277 + {{120, 80}, {300, 463}} + + + _NS:9 + + 3 + MCAwAA + + YES + IBIPadFramework + YES + NO + NO + 1 + 2 + 0 + YES + 44 + 10 + 10 + + + + 303 + {{70, 12}, {400, 60}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Start your collection of great sites to follow. + + 1 + MCAwIDAAA + + + + 3 + MQA + + {0, 1} + 0 + 10 + 2 + 1 + + 1 + 17 + + + Helvetica + 17 + 16 + + + + {540, 540} + + + + _NS:9 + YES + YES + IBIPadFramework + + + {{0, 44}, {540, 540}} + + + + _NS:9 + + 3 + MQA + + 2 + + + + NO + + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 130 + + + + categoriesTable + + + + 161 + + + + scrollView + + + + 169 + + + + instructionLabel + + + + 171 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 129 + + + + + + + + + 138 + + + + + 168 + + + + + + + + + 160 + + + + + 170 + + + + + + + FirstTimeUserAddSitesViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 171 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UILabel + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UITableView + UIButton + UIView + UILabel + UIBarButtonItem + UIScrollView + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + categoriesTable + UITableView + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + scrollView + UIScrollView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + UIView + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + noFocusMessage + UIView + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + subtle-pattern-4.jpg + {1000, 1000} + + 1181 + + diff --git a/media/ios/Classes/FirstTimeUserViewController.h b/media/ios/Classes/FirstTimeUserViewController.h new file mode 100644 index 000000000..0ae2e0b73 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserViewController.h @@ -0,0 +1,28 @@ +// +// FirstTimeUserViewController.h +// NewsBlur +// +// Created by Roy Yang on 6/13/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" + +@class NewsBlurAppDelegate; + +@interface FirstTimeUserViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIBarButtonItem *nextButton; +@property (nonatomic) IBOutlet UIImageView *logo; +@property (weak, nonatomic) IBOutlet UILabel *header; +@property (weak, nonatomic) IBOutlet UILabel *footer; + +- (IBAction)tapNextButton; +- (void)rotateLogo; +-(void)handleTimer:(NSTimer *)timer; + +@end diff --git a/media/ios/Classes/FirstTimeUserViewController.m b/media/ios/Classes/FirstTimeUserViewController.m new file mode 100644 index 000000000..31e860a7f --- /dev/null +++ b/media/ios/Classes/FirstTimeUserViewController.m @@ -0,0 +1,149 @@ +// +// FirstTimeUserViewController.m +// NewsBlur +// +// Created by Roy Yang on 6/13/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FirstTimeUserViewController.h" +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" +#import "FirstTimeUserAddSitesViewController.h" +#import + +#define WELCOME_BUTTON_TITLE @"LET'S GET STARTED" +#define ADD_SITES_SKIP_BUTTON_TITLE @"SKIP THIS STEP" +#define ADD_SITES_BUTTON_TITLE @"NEXT" +#define ADD_FRIENDS_BUTTON_TITLE @"SKIP THIS STEP" +#define ADD_NEWSBLUR_BUTTON_TITLE @"FINISH" + +@interface FirstTimeUserViewController () + +@property (readwrite) float angle_; +@property (readwrite) float timerInterval_; +@property (nonatomic) NSTimer *timer_; +@end + +@implementation FirstTimeUserViewController + +@synthesize appDelegate; +@synthesize nextButton; +@synthesize logo; +@synthesize header; +@synthesize footer; +@synthesize angle_; +@synthesize timerInterval_; +@synthesize timer_; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. + +// + UIBarButtonItem *next = [[UIBarButtonItem alloc] initWithTitle:@"Get Started" style:UIBarButtonSystemItemDone target:self action:@selector(tapNextButton)]; + self.nextButton = next; + self.navigationItem.rightBarButtonItem = next; + + self.navigationItem.title = @"Welcome"; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + logo.frame = CGRectMake(80, 90, 160, 160); + header.font = [UIFont systemFontOfSize:22]; + footer.font = [UIFont systemFontOfSize:16]; + } + + UITapGestureRecognizer *singleFingerTap = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(tapNextButton)]; + [self.view addGestureRecognizer:singleFingerTap]; + +} + +- (void)viewDidUnload +{ + [self setNextButton:nil]; + [self setLogo:nil]; + [self setHeader:nil]; + [self setFooter:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone]; + + UIImage *logoImg = [UIImage imageNamed:@"logo_512"]; + UIImageView *logoView = [[UIImageView alloc] initWithImage:logoImg]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + logoView.frame = CGRectMake(80, 90, 160, 160); + } else { + logoView.frame = CGRectMake(150, 99, 240, 240); + } + + self.logo = logoView; + [self.view addSubview:self.logo]; + [self rotateLogo]; +} + +- (void)viewDidAppear:(BOOL)animated { + +} + +- (void)viewDidDisappear:(BOOL)animated { + self.logo = nil; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + + return NO; +} + + +- (IBAction)tapNextButton { + [appDelegate.ftuxNavigationController pushViewController:appDelegate.firstTimeUserAddSitesViewController animated:YES]; +} + +- (void)rotateLogo { + angle_ = 0; + timerInterval_ = 0.01; + + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDuration:1]; + [UIView setAnimationCurve:UIViewAnimationCurveEaseIn]; + + timer_ = [NSTimer scheduledTimerWithTimeInterval: timerInterval_ target: self selector:@selector(handleTimer:) userInfo: nil repeats: YES]; + [UIView commitAnimations]; +} + +-(void)handleTimer:(NSTimer *)timer +{ + timerInterval_ += .01; + angle_ += 0.001; + if (angle_ > 6.283) { + angle_ = 0; + } + + CGAffineTransform transform = CGAffineTransformMakeRotation(angle_); + self.logo.transform = transform; +} + +@end diff --git a/media/ios/Classes/FirstTimeUserViewController.xib b/media/ios/Classes/FirstTimeUserViewController.xib new file mode 100644 index 000000000..ed404c768 --- /dev/null +++ b/media/ios/Classes/FirstTimeUserViewController.xib @@ -0,0 +1,1555 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIImageView + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 301 + + + + 274 + {540, 540} + + + + _NS:9 + YES + 4 + NO + IBIPadFramework + + NSImage + subtle-pattern-4.jpg + + + + + 303 + {{70, 376}, {400, 97}} + + + + _NS:9 + NO + YES + 7 + NO + IBIPadFramework + Faster! Faster! Until the thrill of speed overcomes the fear of death. + + 1 + MCAwIDAAA + + + + 3 + MQA + + {0, 1} + 0 + 12 + 5 + 1 + + 1 + 18 + + + Helvetica + 18 + 16 + + NO + + + {{0, 44}, {540, 540}} + + + + _NS:9 + + 3 + MQA + + 2 + + + + NO + + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 130 + + + + footer + + + + 133 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 129 + + + + + + + + + 116 + + + + + 131 + + + + + + + FirstTimeUserViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 133 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UIButton + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UIButton + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIButton + UIView + UILabel + UIBarButtonItem + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + subtle-pattern-4.jpg + {1000, 1000} + + 1181 + + diff --git a/media/ios/Classes/FollowGrid.h b/media/ios/Classes/FollowGrid.h new file mode 100644 index 000000000..9467f00fc --- /dev/null +++ b/media/ios/Classes/FollowGrid.h @@ -0,0 +1,27 @@ +// +// FollowGrid.h +// NewsBlur +// +// Created by Roy Yang on 8/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface FollowGrid : UITableViewCell { + NewsBlurAppDelegate *appDelegate; + + NSDictionary *profiles; + NSArray *followList; + +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (nonatomic) NSDictionary *profiles; +@property (nonatomic) NSArray *followList; + +- (void)refreshWithWidth:(int)width; + +@end diff --git a/media/ios/Classes/FollowGrid.m b/media/ios/Classes/FollowGrid.m new file mode 100644 index 000000000..8a8814214 --- /dev/null +++ b/media/ios/Classes/FollowGrid.m @@ -0,0 +1,47 @@ +// +// FollowGrid.m +// NewsBlur +// +// Created by Roy Yang on 8/10/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FollowGrid.h" + +@implementation FollowGrid + +@synthesize appDelegate; + +@synthesize profiles; +@synthesize followList; + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + // Initialization code + profiles = nil; + followList = nil; + } + return self; +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated +{ + [super setSelected:selected animated:animated]; + + // Configure the view for the selected state +} + +- (void)refreshWithWidth:(int)width { + // username + UILabel *user = [[UILabel alloc] initWithFrame:CGRectZero]; + user.textColor = UIColorFromRGB(NEWSBLUR_LINK_COLOR); + user.font = [UIFont fontWithName:@"Helvetica-Bold" size:18]; + user.backgroundColor = [UIColor clearColor]; + user.frame = CGRectMake(0, 10, 100, 22); + user.text = @"roy"; + [self.contentView addSubview:user]; +} + +@end diff --git a/media/ios/Classes/FontSettingsViewController.h b/media/ios/Classes/FontSettingsViewController.h new file mode 100644 index 000000000..d083c0498 --- /dev/null +++ b/media/ios/Classes/FontSettingsViewController.h @@ -0,0 +1,29 @@ +// +// FontPopover.h +// NewsBlur +// +// Created by Roy Yang on 6/18/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface FontSettingsViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; + + IBOutlet UILabel *smallFontSizeLabel; + IBOutlet UILabel *largeFontSizeLabel; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property ( nonatomic) IBOutlet UISegmentedControl *fontStyleSegment; +@property ( nonatomic) IBOutlet UISegmentedControl *fontSizeSegment; + +- (IBAction)changeFontStyle:(id)sender; +- (IBAction)changeFontSize:(id)sender; +- (void)setSanSerif; +- (void)setSerif; + +@end diff --git a/media/ios/Classes/FontSettingsViewController.m b/media/ios/Classes/FontSettingsViewController.m new file mode 100644 index 000000000..5923acd41 --- /dev/null +++ b/media/ios/Classes/FontSettingsViewController.m @@ -0,0 +1,115 @@ +// +// FontPopover.m +// NewsBlur +// +// Created by Roy Yang on 6/18/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FontSettingsViewController.h" +#import "NewsBlurAppDelegate.h" +#import "StoryDetailViewController.h" + +@implementation FontSettingsViewController + +@synthesize appDelegate; +@synthesize fontStyleSegment; +@synthesize fontSizeSegment; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; +} + +- (void)viewWillAppear:(BOOL)animated { + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + if ([userPreferences stringForKey:@"fontStyle"]) { + if ([[userPreferences stringForKey:@"fontStyle"] isEqualToString:@"NB-san-serif"]) { + [fontStyleSegment setSelectedSegmentIndex:0]; + } else if ([[userPreferences stringForKey:@"fontStyle"] isEqualToString:@"NB-serif"]) { + [fontStyleSegment setSelectedSegmentIndex:1]; + } + } + + if([userPreferences stringForKey:@"fontSizing"]){ + NSString *fontSize = [NSString stringWithFormat:@"%@", [userPreferences stringForKey:@"fontSizing"]]; + if ([fontSize isEqualToString:@"NB-extra-small"]) { + [fontSizeSegment setSelectedSegmentIndex:0]; + } else if ([fontSize isEqualToString:@"NB-small"]) { + [fontSizeSegment setSelectedSegmentIndex:1]; + } else if ([fontSize isEqualToString:@"NB-medium"]) { + [fontSizeSegment setSelectedSegmentIndex:2]; + } else if ([fontSize isEqualToString:@"NB-large"]) { + [fontSizeSegment setSelectedSegmentIndex:3]; + } else if ([fontSize isEqualToString:@"NB-extra-large"]) { + [fontSizeSegment setSelectedSegmentIndex:4]; + } + } + // Do any additional setup after loading the view from its nib. +} + +- (void)viewDidUnload +{ + + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return YES; +} + +- (IBAction)changeFontStyle:(id)sender { + if ([sender selectedSegmentIndex] == 0) { + [self setSanSerif]; + } else { + [self setSerif]; + } +} + +- (IBAction)changeFontSize:(id)sender { + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if ([sender selectedSegmentIndex] == 0) { + [appDelegate.storyDetailViewController changeFontSize:@"NB-extra-small"]; + [userPreferences setObject:@"NB-extra-small" forKey:@"fontSizing"]; + } else if ([sender selectedSegmentIndex] == 1) { + [appDelegate.storyDetailViewController changeFontSize:@"NB-small"]; + [userPreferences setObject:@"NB-small" forKey:@"fontSizing"]; + } else if ([sender selectedSegmentIndex] == 2) { + [appDelegate.storyDetailViewController changeFontSize:@"NB-medium"]; + [userPreferences setObject:@"NB-medium" forKey:@"fontSizing"]; + } else if ([sender selectedSegmentIndex] == 3) { + [appDelegate.storyDetailViewController changeFontSize:@"NB-large"]; + [userPreferences setObject:@"NB-large" forKey:@"fontSizing"]; + } else if ([sender selectedSegmentIndex] == 4) { + [appDelegate.storyDetailViewController changeFontSize:@"NB-extra-large"]; + [userPreferences setObject:@"NB-extra-large" forKey:@"fontSizing"]; + } + [userPreferences synchronize]; +} + +- (void)setSanSerif { + [fontStyleSegment setSelectedSegmentIndex:0]; + [appDelegate.storyDetailViewController setFontStyle:@"Helvetica"]; +} + +- (void)setSerif { + [fontStyleSegment setSelectedSegmentIndex:1]; + [appDelegate.storyDetailViewController setFontStyle:@"Georgia"]; +} + +@end diff --git a/media/ios/Classes/FontSettingsViewController.xib b/media/ios/Classes/FontSettingsViewController.xib new file mode 100644 index 000000000..4298e14f7 --- /dev/null +++ b/media/ios/Classes/FontSettingsViewController.xib @@ -0,0 +1,1597 @@ + + + + 1280 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBProxyObject + IBUIView + IBUISegmentedControl + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 292 + + + + 293 + {{20, 20}, {234, 30}} + + + + _NS:9 + NO + IBIPadFramework + 2 + 2 + 0 + + Helvetica + Georgia + + + + + + + + + + + {0, 0} + {0, 0} + + + + + + + + + 293 + {{20, 81}, {234, 30}} + + + + _NS:9 + NO + IBIPadFramework + 2 + 5 + 2 + + 11pt + 12pt + 14pt + 16pt + 18pt + + + + + + + + + + + + + + + + + {0, 0} + {0, 0} + {0, 0} + {0, 0} + {0, 0} + + + + + + + + + + + {274, 130} + + + + + 3 + MQA + + 2 + + + NO + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 3 + + + + fontStyleSegment + + + + 32 + + + + fontSizeSegment + + + + 34 + + + + changeFontStyle: + + + 13 + + 12 + + + + changeFontSize: + + + 13 + + 29 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + + + 11 + + + + + 28 + + + + + + + FontSettingsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + + 34 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UIButton + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UIButton + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIButton + UIView + UILabel + UIBarButtonItem + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + YES + 3 + 1181 + + diff --git a/media/ios/Classes/FriendsListViewController.h b/media/ios/Classes/FriendsListViewController.h new file mode 100644 index 000000000..bc5e63095 --- /dev/null +++ b/media/ios/Classes/FriendsListViewController.h @@ -0,0 +1,37 @@ +// +// FriendsListViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/1/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface FriendsListViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; + UISearchBar *friendSearchBar; + UITableView *friendsTable; + NSArray *suggestedUserProfiles; + NSArray *userProfiles; + NSArray *userProfileIds; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UISearchBar *friendSearchBar; +@property (nonatomic) IBOutlet UITableView *friendsTable; + +@property (nonatomic) NSArray *userProfiles; +@property (nonatomic) NSArray *suggestedUserProfiles; + +- (void)doCancelButton; +- (void)loadFriendsList:(NSString *)query; +- (void)requestFinished:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)loadSuggestedFriendsList; +- (void)loadSuggestedFriendsListFinished:(ASIHTTPRequest *)request; +- (void)hideUserProfileModal; +@end diff --git a/media/ios/Classes/FriendsListViewController.m b/media/ios/Classes/FriendsListViewController.m new file mode 100644 index 000000000..a33af8b7f --- /dev/null +++ b/media/ios/Classes/FriendsListViewController.m @@ -0,0 +1,417 @@ +// +// FriendsListViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/1/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "FriendsListViewController.h" +#import "NewsBlurAppDelegate.h" +#import "UserProfileViewController.h" +#import "ASIHTTPRequest.h" +#import "ProfileBadge.h" +#import "MBProgressHUD.h" + +@implementation UINavigationController (DelegateAutomaticDismissKeyboard) +- (BOOL)disablesAutomaticKeyboardDismissal { + return [self.topViewController disablesAutomaticKeyboardDismissal]; +} +@end + +@interface FriendsListViewController() + +@property (readwrite) BOOL inSearch_; + +@end + +@implementation FriendsListViewController + +@synthesize appDelegate; +@synthesize friendSearchBar; +@synthesize friendsTable; +@synthesize suggestedUserProfiles; +@synthesize userProfiles; +@synthesize inSearch_; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + + if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.navigationItem.title = @"Find Friends"; + UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle: @"Done" + style: UIBarButtonSystemItemCancel + target: self + action: @selector(doCancelButton)]; + [self.navigationItem setRightBarButtonItem:cancelButton]; + + // Do any additional setup after loading the view from its nib. + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + + self.view.frame = CGRectMake(0, 0, 320, 416); + self.contentSizeForViewInPopover = self.view.frame.size; + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + +// UISearchBar *newSearchBar = [[UISearchBar alloc] init]; +// newSearchBar.frame = CGRectMake(0,0,0,38); +// newSearchBar.placeholder = @"Search by username or email"; +// newSearchBar.delegate = self; +// self.friendSearchBar = newSearchBar; +// self.friendsTable.tableHeaderView = newSearchBar; + +} + +- (void)viewDidUnload +{ + [self setFriendSearchBar:nil]; + [self setSuggestedUserProfiles:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.friendSearchBar becomeFirstResponder]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [self.friendsTable reloadData]; +} + +- (void)doCancelButton { + [appDelegate.modalNavigationController dismissModalViewControllerAnimated:YES]; +} + +#pragma mark - UISearchBar delegate methods + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { + if (searchText.length == 0) { + self.userProfiles = nil; + self.inSearch_ = NO; + [self.friendsTable reloadData]; + } else { + self.inSearch_ = YES; + [self loadFriendsList:searchText]; + } +} + +- (void)searchBarTextDidEndEditing:(UISearchBar *)theSearchBar { + [theSearchBar resignFirstResponder]; +} + +- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { + [searchBar resignFirstResponder]; +} + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + searchBar.text = nil; +} + +- (void)loadFriendsList:(NSString *)query { + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/find_friends?query=%@&limit=10", + NEWSBLUR_URL, + query]; + NSURL *url = [NSURL URLWithString:urlString]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(requestFinished:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)loadSuggestedFriendsList { + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/load_user_friends", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(loadSuggestedFriendsListFinished:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)loadSuggestedFriendsListFinished:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData= [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // int statusCode = [request responseStatusCode]; + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + return; + } + self.suggestedUserProfiles = [results objectForKey:@"recommended_users"]; + [self.friendsTable reloadData]; +} + +- (void)requestFinished:(ASIHTTPRequest *)request { + if (self.inSearch_) { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + NSString *responseString = [request responseString]; + NSData *responseData= [responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // int statusCode = [request responseStatusCode]; + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + return; + } + + self.userProfiles = [results objectForKey:@"profiles"]; + + + [self.friendsTable reloadData]; + } + +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +- (BOOL)disablesAutomaticKeyboardDismissal { + return NO; +} + +#pragma mark - Table view data source + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 0; +// if (self.inSearch_){ +// return 0; +// } else { +// if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){ +// return 28; +// }else{ +// return 21; +// } +// } +} + +- (UIView *)tableView:(UITableView *)tableView +viewForHeaderInSection:(NSInteger)section { + int headerLabelHeight, folderImageViewY; + + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + headerLabelHeight = 28; + folderImageViewY = 3; + } else { + headerLabelHeight = 20; + folderImageViewY = 0; + } + + // create the parent view that will hold header Label + UIControl* customView = [[UIControl alloc] + initWithFrame:CGRectMake(0.0, 0.0, + tableView.bounds.size.width, headerLabelHeight + 1)]; + UIView *borderTop = [[UIView alloc] + initWithFrame:CGRectMake(0.0, 0, + tableView.bounds.size.width, 1.0)]; + borderTop.backgroundColor = UIColorFromRGB(0xe0e0e0); + borderTop.opaque = NO; + [customView addSubview:borderTop]; + + + UIView *borderBottom = [[UIView alloc] + initWithFrame:CGRectMake(0.0, headerLabelHeight, + tableView.bounds.size.width, 1.0)]; + borderBottom.backgroundColor = [UIColorFromRGB(0xB7BDC6) colorWithAlphaComponent:0.5]; + borderBottom.opaque = NO; + [customView addSubview:borderBottom]; + + UILabel * headerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + customView.opaque = NO; + headerLabel.backgroundColor = [UIColor clearColor]; + headerLabel.opaque = NO; + headerLabel.textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0]; + headerLabel.highlightedTextColor = [UIColor whiteColor]; + headerLabel.font = [UIFont boldSystemFontOfSize:11]; + headerLabel.frame = CGRectMake(36.0, 1.0, 286.0, headerLabelHeight); + headerLabel.shadowColor = [UIColor colorWithRed:.94 green:0.94 blue:0.97 alpha:1.0]; + headerLabel.shadowOffset = CGSizeMake(0.0, 1.0); + headerLabel.text = @"RECOMMENDED PEOPLE TO FOLLOW"; + + customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) + colorWithAlphaComponent:0.8]; + [customView addSubview:headerLabel]; + + UIImage *folderImage; + int folderImageViewX = 10; + + folderImage = [UIImage imageNamed:@"group.png"]; + folderImageViewX = 9; + UIImageView *folderImageView = [[UIImageView alloc] initWithImage:folderImage]; + folderImageView.frame = CGRectMake(folderImageViewX, folderImageViewY, 20, 20); + [customView addSubview:folderImageView]; + [customView setAutoresizingMask:UIViewAutoresizingNone]; + return customView; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 140; +} + +- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView { + tableView.rowHeight = 140.0f; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (self.inSearch_){ + int userCount = [self.userProfiles count]; + return userCount; + } else { + int userCount = [self.suggestedUserProfiles count]; + if (!userCount) { + return 3; + } + return userCount; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + CGRect vb = self.view.bounds; + + static NSString *CellIdentifier = @"ProfileBadgeCellIdentifier"; + UITableViewCell *cell = [tableView + dequeueReusableCellWithIdentifier:CellIdentifier]; + + if (cell == nil) { + cell = [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil]; + } else { + [[[cell contentView] subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)]; + } + + ProfileBadge *badge = [[ProfileBadge alloc] init]; + badge.frame = CGRectMake(5, 5, vb.size.width - 35, self.view.frame.size.height); + + + if (self.inSearch_){ + int userProfileCount = [self.userProfiles count]; + + if (userProfileCount) { + if (userProfileCount > indexPath.row) { + [badge refreshWithProfile:[self.userProfiles objectAtIndex:indexPath.row] showStats:NO withWidth:vb.size.width - 35 - 10]; + [cell.contentView addSubview:badge]; + cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; + } + } else { + + // add a NO FRIENDS TO SUGGEST message on either the first or second row depending on iphone/ipad + int row = 0; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + row = 1; + } + + if (indexPath.row == row) { + UILabel *msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vb.size.width, 140)]; + [cell.contentView addSubview:msg]; + msg.text = @"No results."; + msg.textColor = UIColorFromRGB(0x7a7a7a); + if (vb.size.width > 320) { + msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 20.0]; + } else { + msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 14.0]; + } + msg.textAlignment = UITextAlignmentCenter; + } + + } + + } +// else { +// +// int userCount = [self.suggestedUserProfiles count]; +// if (!userCount) { +// // add a NO FRIENDS TO SUGGEST message on either the first or second row depending on iphone/ipad +// int row = 0; +// if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { +// row = 1; +// } +// +// if (indexPath.row == row) { +// UILabel *msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vb.size.width, 140)]; +// [cell.contentView addSubview:msg]; +// msg.text = @"Nobody left to recommend. Good job!"; +// msg.textColor = UIColorFromRGB(0x7a7a7a); +// if (vb.size.width > 320) { +// msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 20.0]; +// } else { +// msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 14.0]; +// } +// msg.textAlignment = UITextAlignmentCenter; +// } +// } else { +// cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; +// [badge refreshWithProfile:[self.suggestedUserProfiles objectAtIndex:indexPath.row] showStats:NO withWidth:vb.size.width - 35 - 10]; +// [cell.contentView addSubview:badge]; +// } +// } + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + return cell; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + [self.friendSearchBar resignFirstResponder]; +} + +-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self.friendSearchBar resignFirstResponder]; +} + +- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { + NSInteger currentRow = indexPath.row; + int row = currentRow; + appDelegate.activeUserProfileId = [[self.userProfiles objectAtIndex:row] objectForKey:@"user_id"]; + appDelegate.activeUserProfileName = [[self.userProfiles objectAtIndex:row] objectForKey:@"username"]; + [self.friendSearchBar resignFirstResponder]; + + // adding Done button + UIBarButtonItem *donebutton = [[UIBarButtonItem alloc] + initWithTitle:@"Close" + style:UIBarButtonItemStyleDone + target:self + action:@selector(hideUserProfileModal)]; + + // instantiate a new userProfileController + UserProfileViewController *newUserProfile = [[UserProfileViewController alloc] init]; + newUserProfile.navigationItem.rightBarButtonItem = donebutton; + newUserProfile.navigationItem.title = appDelegate.activeUserProfileName; + appDelegate.userProfileViewController = newUserProfile; + [appDelegate.modalNavigationController pushViewController:newUserProfile animated:YES]; + [appDelegate.userProfileViewController getUserProfile]; +} + +- (void)hideUserProfileModal { + [appDelegate.modalNavigationController dismissModalViewControllerAnimated:YES]; + +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/GoogleReaderViewController.xib b/media/ios/Classes/GoogleReaderViewController.xib new file mode 100644 index 000000000..a35285365 --- /dev/null +++ b/media/ios/Classes/GoogleReaderViewController.xib @@ -0,0 +1,1692 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIWebView + IBUIBarButtonItem + IBUIToolbar + IBUIView + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 292 + + + + 274 + {{0, 44}, {540, 576}} + + + _NS:9 + + 1 + MSAxIDEAA + + IBIPadFramework + 1 + YES + + + + 266 + {540, 44} + + + + _NS:9 + NO + NO + IBIPadFramework + + + IBIPadFramework + + 5 + + + IBIPadFramework + 1 + + 1 + + + + + {540, 620} + + + + + 3 + MQA + + 2 + + + NO + + IBUIModalFormSheetSimulatedSizeMetrics + + YES + + + + + + {540, 620} + {540, 620} + + + IBIPadFramework + Form Sheet + + IBIPadFramework + + + + + + + view + + + + 3 + + + + webView + + + + 5 + + + + tapCancelButton: + + + + 9 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + + + 4 + + + + + 6 + + + + + + + + + 7 + + + + + 8 + + + + + + + GoogleReaderViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 9 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + + UIView + UIView + UIView + NewsBlurAppDelegate + UIButton + UIImageView + UIBarButtonItem + UIBarButtonItem + UIToolbar + UIButton + UIView + + + + addFriendsView + UIView + + + addNewsBlurView + UIView + + + addSitesView + UIView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + previousButton + UIBarButtonItem + + + toolbar + UIToolbar + + + toolbarTitle + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + GoogleReaderViewController + UIViewController + + tapCancelButton: + id + + + tapCancelButton: + + tapCancelButton: + id + + + + NewsBlurAppDelegate + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + webView + UIWebView + + + + IBProjectSource + ./Classes/GoogleReaderViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + GoogleReaderViewController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + googleReaderViewController + GoogleReaderViewController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + toolbarTitle + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + 1181 + + diff --git a/media/ios/Classes/InteractionCell.h b/media/ios/Classes/InteractionCell.h new file mode 100644 index 000000000..118ed674e --- /dev/null +++ b/media/ios/Classes/InteractionCell.h @@ -0,0 +1,23 @@ +// +// InteractionCell.h +// NewsBlur +// +// Created by Roy Yang on 7/16/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "OHAttributedLabel.h" + +@interface InteractionCell : UITableViewCell { + OHAttributedLabel *interactionLabel; + UIImageView *avatarView; +} + +@property (retain, nonatomic) OHAttributedLabel *interactionLabel; +@property (retain, nonatomic) UIImageView *avatarView; + +- (int)setInteraction:(NSDictionary *)interaction withWidth:(int)width; +- (NSString *)stripFormatting:(NSString *)str; + +@end \ No newline at end of file diff --git a/media/ios/Classes/InteractionCell.m b/media/ios/Classes/InteractionCell.m new file mode 100644 index 000000000..95cf708d9 --- /dev/null +++ b/media/ios/Classes/InteractionCell.m @@ -0,0 +1,151 @@ +// +// InteractionCell.m +// NewsBlur +// +// Created by Roy Yang on 7/16/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "InteractionCell.h" +#import "NSAttributedString+Attributes.h" +#import "UIImageView+AFNetworking.h" + +@implementation InteractionCell + +@synthesize interactionLabel; +@synthesize avatarView; + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + interactionLabel = nil; + avatarView = nil; + + // create the label and the avatar + UIImageView *avatar = [[UIImageView alloc] initWithFrame:CGRectZero]; + self.avatarView = avatar; + [self.contentView addSubview:avatar]; + + OHAttributedLabel *interaction = [[OHAttributedLabel alloc] initWithFrame:CGRectZero]; + interaction.backgroundColor = [UIColor whiteColor]; + interaction.automaticallyAddLinksForType = NO; + self.interactionLabel = interaction; + [self.contentView addSubview:interaction]; + + UIView *myBackView = [[UIView alloc] initWithFrame:self.frame]; + myBackView.backgroundColor = UIColorFromRGB(NEWSBLUR_HIGHLIGHT_COLOR); + self.selectedBackgroundView = myBackView; + } + + return self; +} + +- (void)layoutSubviews { + #define topMargin 15 + #define bottomMargin 15 + #define leftMargin 20 + #define rightMargin 20 + #define avatarSize 48 + + [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; +} + + +- (int)setInteraction:(NSDictionary *)interaction withWidth:(int)width { + // must set the height again for dynamic height in heightForRowAtIndexPath in + CGRect interactionLabelRect = self.interactionLabel.bounds; + interactionLabelRect.size.width = width - leftMargin - avatarSize - leftMargin - rightMargin; + interactionLabelRect.size.height = 300; + + self.interactionLabel.frame = interactionLabelRect; + self.avatarView.frame = CGRectMake(leftMargin, topMargin, avatarSize, avatarSize); + +// UIImage *placeholder = [UIImage imageNamed:@"user_light"]; + + // this is for the rare instance when the with_user doesn't return anything + if ([[interaction objectForKey:@"with_user"] class] == [NSNull class]) { + return 1; + } + + [self.avatarView setImageWithURL:[NSURL URLWithString:[[interaction objectForKey:@"with_user"] objectForKey:@"photo_url"]] + placeholderImage:nil]; + + NSString *category = [interaction objectForKey:@"category"]; + NSString *content = [interaction objectForKey:@"content"]; + NSString *title = [self stripFormatting:[NSString stringWithFormat:@"%@", [interaction objectForKey:@"title"]]]; + NSString *username = [[interaction objectForKey:@"with_user"] objectForKey:@"username"]; + NSString *time = [[NSString stringWithFormat:@"%@ ago", [interaction objectForKey:@"time_since"]] uppercaseString]; + NSString *comment = [NSString stringWithFormat:@"\"%@\"", content]; + NSString *txt; + + if ([category isEqualToString:@"follow"]) { + txt = [NSString stringWithFormat:@"%@ is now following you.", username]; + } else if ([category isEqualToString:@"comment_reply"]) { + txt = [NSString stringWithFormat:@"%@ replied to your comment on %@:\n%@", username, title, comment]; + } else if ([category isEqualToString:@"reply_reply"]) { + txt = [NSString stringWithFormat:@"%@ replied to your reply on %@:\n%@", username, title, comment]; + } else if ([category isEqualToString:@"story_reshare"]) { + if ([content isEqualToString:@""] || content == nil) { + txt = [NSString stringWithFormat:@"%@ re-shared %@.", username, title]; + } else { + txt = [NSString stringWithFormat:@"%@ re-shared %@:\n%@", username, title, comment]; + } + } else if ([category isEqualToString:@"comment_like"]) { + txt = [NSString stringWithFormat:@"%@ favorited your comments on %@:\n%@", username, title, comment]; + } + + NSString *txtWithTime = [NSString stringWithFormat:@"%@\n%@", txt, time]; + NSMutableAttributedString* attrStr = [NSMutableAttributedString attributedStringWithString:txtWithTime]; + + // for those calls we don't specify a range so it affects the whole string + [attrStr setFont:[UIFont fontWithName:@"Helvetica" size:14]]; + if (self.highlighted) { + [attrStr setTextColor:UIColorFromRGB(0xffffff)]; + } else { + [attrStr setTextColor:UIColorFromRGB(0x333333)]; + + } + + if (![username isEqualToString:@"You"]){ + [attrStr setTextColor:UIColorFromRGB(NEWSBLUR_LINK_COLOR) range:[txtWithTime rangeOfString:username]]; + [attrStr setTextBold:YES range:[txt rangeOfString:username]]; + } + + [attrStr setTextColor:UIColorFromRGB(NEWSBLUR_LINK_COLOR) range:[txtWithTime rangeOfString:title]]; + [attrStr setTextColor:UIColorFromRGB(0x666666) range:[txtWithTime rangeOfString:comment]]; + + [attrStr setTextColor:UIColorFromRGB(0x999999) range:[txtWithTime rangeOfString:time]]; + [attrStr setFont:[UIFont fontWithName:@"Helvetica" size:10] range:[txtWithTime rangeOfString:time]]; + [attrStr setTextAlignment:kCTLeftTextAlignment lineBreakMode:kCTLineBreakByWordWrapping lineHeight:4]; + + self.interactionLabel.attributedText = attrStr; + + [self.interactionLabel sizeToFit]; + + int height = self.interactionLabel.frame.size.height; + + return height; +} + +- (NSString *)stripFormatting:(NSString *)str { + while ([str rangeOfString:@" "].location != NSNotFound) { + str = [str stringByReplacingOccurrencesOfString:@" " withString:@" "]; + } + while ([str rangeOfString:@"\n"].location != NSNotFound) { + str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; + } + return str; +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/InteractionsModule.h b/media/ios/Classes/InteractionsModule.h new file mode 100644 index 000000000..8103b5250 --- /dev/null +++ b/media/ios/Classes/InteractionsModule.h @@ -0,0 +1,42 @@ +// +// InteractionsModule.h +// NewsBlur +// +// Created by Roy Yang on 7/11/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface InteractionsModule : UIView { + NewsBlurAppDelegate *appDelegate; + UITableView *interactionsTable; + NSMutableArray *interactionsArray; + UIPopoverController *popoverController; + + BOOL pageFetching; + BOOL pageFinished; + int interactionsPage; +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (nonatomic, strong) UITableView *interactionsTable; +@property (nonatomic) NSArray *interactionsArray; +@property (nonatomic, strong) UIPopoverController *popoverController; + +@property (nonatomic, readwrite) BOOL pageFetching; +@property (nonatomic, readwrite) BOOL pageFinished; +@property (readwrite) int interactionsPage; + +- (void)refreshWithInteractions:(NSArray *)interactions; + +- (void)fetchInteractionsDetail:(int)page; +- (void)finishLoadInteractions:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; + +- (void)checkScroll; + +@end \ No newline at end of file diff --git a/media/ios/Classes/InteractionsModule.m b/media/ios/Classes/InteractionsModule.m new file mode 100644 index 000000000..3584c66a3 --- /dev/null +++ b/media/ios/Classes/InteractionsModule.m @@ -0,0 +1,305 @@ +// +// InteractionsModule.m +// NewsBlur +// +// Created by Roy Yang on 7/11/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "InteractionsModule.h" +#import "NewsBlurAppDelegate.h" +#import "InteractionCell.h" +#import +#import "ASIHTTPRequest.h" +#import "UserProfileViewController.h" + +#define MINIMUM_INTERACTION_HEIGHT 78 + +@implementation InteractionsModule + +@synthesize appDelegate; +@synthesize interactionsTable; +@synthesize interactionsArray; +@synthesize popoverController; +@synthesize pageFetching; +@synthesize pageFinished; +@synthesize interactionsPage; + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + + } + return self; +} + + +- (void)layoutSubviews { + [super layoutSubviews]; + self.interactionsTable = [[UITableView alloc] init]; + self.interactionsTable.dataSource = self; + self.interactionsTable.delegate = self; + self.interactionsTable.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); + self.interactionsTable.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + [self addSubview:self.interactionsTable]; +} + +- (void)refreshWithInteractions:(NSArray *)interactions { + self.interactionsArray = interactions; + + [self.interactionsTable reloadData]; + + self.pageFetching = NO; + + [self performSelector:@selector(checkScroll) + withObject:nil + afterDelay:0.1]; +} + +- (void)checkScroll { + NSInteger currentOffset = self.interactionsTable.contentOffset.y; + NSInteger maximumOffset = self.interactionsTable.contentSize.height - self.interactionsTable.frame.size.height; + + if (maximumOffset - currentOffset <= 60.0) { + [self fetchInteractionsDetail:self.interactionsPage + 1]; + } +} + + +#pragma mark - +#pragma mark Get Interactions + +- (void)fetchInteractionsDetail:(int)page { + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + + // if there is no social profile, we are DONE +// if ([[appDelegate.dictUserProfile allKeys] count] == 0) { +// self.pageFinished = YES; +// [self.interactionsTable reloadData]; +// return; +// } else { +// if (page == 1) { +// self.pageFinished = NO; +// } +// } + + if (page == 1) { + self.pageFetching = NO; + self.pageFinished = NO; + appDelegate.userInteractionsArray = nil; + } + + if (!self.pageFetching && !self.pageFinished) { + self.interactionsPage = page; + self.pageFetching = YES; + + NSString *urlString = [NSString stringWithFormat:@ + "http://%@/social/interactions?user_id=%@&page=%i&limit=10" + "&category=follow&category=comment_reply&category=comment_like&category=reply_reply&category=story_reshare", + NEWSBLUR_URL, + [appDelegate.dictUserProfile objectForKey:@"user_id"], + page]; + + NSURL *url = [NSURL URLWithString:urlString]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + + [request setDidFinishSelector:@selector(finishLoadInteractions:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setDelegate:self]; + [request startAsynchronous]; + } +} + +- (void)finishLoadInteractions:(ASIHTTPRequest *)request { + self.pageFetching = NO; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + NSArray *newInteractions = [results objectForKey:@"interactions"]; + + // check for last page + if (![[results objectForKey:@"has_next_page"] intValue]) { + self.pageFinished = YES; + } + + NSMutableArray *confirmedInteractions = [NSMutableArray array]; + if ([appDelegate.userInteractionsArray count]) { + NSMutableSet *interactionsDates = [NSMutableSet set]; + for (id interaction in appDelegate.userInteractionsArray) { + [interactionsDates addObject:[interaction objectForKey:@"date"]]; + } + for (id interaction in newInteractions) { + if (![interactionsDates containsObject:[interaction objectForKey:@"date"]]) { + [confirmedInteractions addObject:interaction]; + } + } + } else { + confirmedInteractions = [newInteractions copy]; + } + + if (self.interactionsPage == 1) { + appDelegate.userInteractionsArray = confirmedInteractions; + } else { + appDelegate.userInteractionsArray = [appDelegate.userInteractionsArray arrayByAddingObjectsFromArray:newInteractions]; + } + + + [self refreshWithInteractions:appDelegate.userInteractionsArray]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +#pragma mark - +#pragma mark Table View - Interactions List + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + + +-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 0; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + int userInteractions = [appDelegate.userInteractionsArray count]; + if (indexPath.row >= userInteractions) { + return MINIMUM_INTERACTION_HEIGHT; + } + + InteractionCell *interactionCell = [[InteractionCell alloc] init]; + int height = [interactionCell setInteraction:[appDelegate.userInteractionsArray objectAtIndex:(indexPath.row)] withWidth:self.frame.size.width - 20] + 30; + if (height < MINIMUM_INTERACTION_HEIGHT) { + return MINIMUM_INTERACTION_HEIGHT; + } else { + return height; + } + +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UIView *blank = [[UIView alloc] init]; + return blank; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + int userInteractionsCount = [appDelegate.userInteractionsArray count]; + return userInteractionsCount + 1; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + InteractionCell *cell = [tableView + dequeueReusableCellWithIdentifier:@"InteractionCell"]; + if (cell == nil) { + cell = [[InteractionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"InteractionCell"]; + } + + if (indexPath.row >= [appDelegate.userInteractionsArray count]) { + // add in loading cell + return [self makeLoadingCell]; + } else { + NSDictionary *interaction = [appDelegate.userInteractionsArray objectAtIndex:(indexPath.row)]; + NSString *category = [interaction objectForKey:@"category"]; + if (![category isEqualToString:@"follow"]) { + cell.accessoryType= UITableViewCellAccessoryDisclosureIndicator; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + + // update the cell information + [cell setInteraction:interaction withWidth: self.frame.size.width - 20]; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + int userInteractions = [appDelegate.userInteractionsArray count]; + if (indexPath.row < userInteractions) { + NSDictionary *interaction = [appDelegate.userInteractionsArray objectAtIndex:indexPath.row]; + NSString *category = [interaction objectForKey:@"category"]; + if ([category isEqualToString:@"follow"]) { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + NSString *userId = [NSString stringWithFormat:@"%@", [[interaction objectForKey:@"with_user"] objectForKey:@"user_id"]]; + appDelegate.activeUserProfileId = userId; + + NSString *username = [NSString stringWithFormat:@"%@", [[interaction objectForKey:@"with_user"] objectForKey:@"username"]]; + appDelegate.activeUserProfileName = username; + + // pass cell to the show UserProfile + UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + [appDelegate showUserProfileModal:cell]; + } else if ([category isEqualToString:@"comment_reply"] || + [category isEqualToString:@"reply_reply"] || + [category isEqualToString:@"comment_like"]) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [interaction objectForKey:@"feed_id"]]; + NSString *contentIdStr = [NSString stringWithFormat:@"%@", [interaction objectForKey:@"content_id"]]; + [appDelegate loadTryFeedDetailView:feedIdStr + withStory:contentIdStr + isSocial:YES + withUser:[interaction objectForKey:@"with_user"] + showFindingStory:YES]; + appDelegate.tryFeedCategory = category; + } else if ([category isEqualToString:@"story_reshare"]) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [[interaction objectForKey:@"with_user"] objectForKey:@"id"]]; + NSString *contentIdStr = [NSString stringWithFormat:@"%@", [interaction objectForKey:@"content_id"]]; + [appDelegate loadTryFeedDetailView:feedIdStr withStory:contentIdStr isSocial:YES withUser:[interaction objectForKey:@"with_user"] showFindingStory:YES]; + appDelegate.tryFeedCategory = category; + } + + // have the selected cell deselect + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } +} + +- (UITableViewCell *)makeLoadingCell { + UITableViewCell *cell = [[UITableViewCell alloc] + initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:@"NoReuse"]; + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + if (self.pageFinished) { + UIImage *img = [UIImage imageNamed:@"fleuron.png"]; + UIImageView *fleuron = [[UIImageView alloc] initWithImage:img]; + int height = MINIMUM_INTERACTION_HEIGHT; + + fleuron.frame = CGRectMake(0, 0, self.frame.size.width, height); + fleuron.contentMode = UIViewContentModeCenter; + [cell.contentView addSubview:fleuron]; + fleuron.backgroundColor = [UIColor whiteColor]; + } else { + cell.textLabel.text = @"Loading..."; + + UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + UIImage *spacer = [UIImage imageNamed:@"spacer"]; + UIGraphicsBeginImageContext(spinner.frame.size); + [spacer drawInRect:CGRectMake(0, 0, spinner.frame.size.width,spinner.frame.size.height)]; + UIImage* resizedSpacer = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + cell.imageView.image = resizedSpacer; + [cell.imageView addSubview:spinner]; + [spinner startAnimating]; + } + + return cell; +} + +- (void)scrollViewDidScroll: (UIScrollView *)scroll { + [self checkScroll]; +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/LoginViewController.h b/media/ios/Classes/LoginViewController.h new file mode 100644 index 000000000..04b84412e --- /dev/null +++ b/media/ios/Classes/LoginViewController.h @@ -0,0 +1,74 @@ +// +// LoginViewController.h +// NewsBlur +// +// Created by Samuel Clay on 10/31/10. +// Copyright 2010 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" + +#define LANDSCAPE_MARGIN 128 + +@class NewsBlurAppDelegate; + +@interface LoginViewController : UIViewController + { + NewsBlurAppDelegate *appDelegate; + + BOOL isOnSignUpScreen; + UITextField *usernameInput; + UITextField *passwordInput; + UITextField *emailInput; + NSMutableData * jsonString; + + UIActivityIndicatorView *activityIndicator; + UILabel *authenticatingLabel; + UILabel *errorLabel; + UISegmentedControl *loginControl; + + UILabel *usernameLabel; + UILabel *usernameOrEmailLabel; + UILabel *passwordLabel; + UILabel *emailLabel; + UILabel *passwordOptionalLabel; +} + +- (void)checkPassword; +- (void)registerAccount; +- (IBAction)selectLoginSignup; + +- (IBAction)selectSignUp; +- (IBAction)selectLogin; +- (IBAction)tapLoginButton; +- (IBAction)tapSignUpButton; + + +- (void)animateLoop; + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; + +@property (nonatomic) IBOutlet UITextField *usernameInput; +@property (nonatomic) IBOutlet UITextField *passwordInput; +@property (nonatomic) IBOutlet UITextField *emailInput; +@property (nonatomic) IBOutlet UITextField *signUpUsernameInput; +@property (nonatomic) IBOutlet UITextField *signUpPasswordInput; +@property (nonatomic) IBOutlet UIButton *selectSignUpButton; +@property (nonatomic) IBOutlet UIButton *selectLoginButton; + +@property (nonatomic) IBOutlet UIView *signUpView; +@property (nonatomic) IBOutlet UIView *logInView; + +@property (nonatomic) NSMutableData * jsonString; +@property (nonatomic) IBOutlet UILabel *errorLabel; +@property (nonatomic) IBOutlet UISegmentedControl *loginControl; + +@property (nonatomic) IBOutlet UILabel *usernameLabel; +@property (nonatomic) IBOutlet UILabel *usernameOrEmailLabel; +@property (nonatomic) IBOutlet UILabel *passwordLabel; +@property (nonatomic) IBOutlet UILabel *emailLabel; +@property (nonatomic) IBOutlet UILabel *passwordOptionalLabel; + +@end diff --git a/media/ios/Classes/LoginViewController.m b/media/ios/Classes/LoginViewController.m new file mode 100644 index 000000000..525904002 --- /dev/null +++ b/media/ios/Classes/LoginViewController.m @@ -0,0 +1,399 @@ +// +// LoginViewController.m +// NewsBlur +// +// Created by Samuel Clay on 10/31/10. +// Copyright 2010 NewsBlur. All rights reserved. +// + +#import "LoginViewController.h" +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" +#import "ASIFormDataRequest.h" +#import + +@implementation LoginViewController + +@synthesize appDelegate; +@synthesize usernameInput; +@synthesize passwordInput; +@synthesize emailInput; +@synthesize signUpUsernameInput; +@synthesize signUpPasswordInput; +@synthesize selectSignUpButton; +@synthesize selectLoginButton; +@synthesize signUpView; +@synthesize logInView; + +@synthesize jsonString; +@synthesize errorLabel; +@synthesize loginControl; +@synthesize usernameLabel; +@synthesize usernameOrEmailLabel; +@synthesize passwordLabel; +@synthesize emailLabel; +@synthesize passwordOptionalLabel; + + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + + } + return self; + + } + +- (void)viewDidLoad { + self.usernameInput.borderStyle = UITextBorderStyleRoundedRect; + self.passwordInput.borderStyle = UITextBorderStyleRoundedRect; + self.emailInput.borderStyle = UITextBorderStyleRoundedRect; + self.signUpPasswordInput.borderStyle = UITextBorderStyleRoundedRect; + self.signUpUsernameInput.borderStyle = UITextBorderStyleRoundedRect; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { + self.logInView.frame = CGRectMake(134, 180, 500, 300); + self.signUpView.frame = CGRectMake(902, 180, 500, 300); + self.selectLoginButton.frame = CGRectMake(134, 80, 250, 50); + self.selectSignUpButton.frame = CGRectMake(384, 80, 250, 50); + self.errorLabel.frame = CGRectMake(244, 180, self.errorLabel.frame.size.width, self.errorLabel.frame.size.height); + } else { + self.logInView.frame = CGRectMake(134 + LANDSCAPE_MARGIN, 80, 500, 300); + self.signUpView.frame = CGRectMake(902 + LANDSCAPE_MARGIN, 80, 500, 300); + self.selectLoginButton.frame = CGRectMake(134 + LANDSCAPE_MARGIN, 20, 250, 50); + self.selectSignUpButton.frame = CGRectMake(384 + LANDSCAPE_MARGIN, 20, 250, 50); + self.errorLabel.frame = CGRectMake(244 + LANDSCAPE_MARGIN, 180 - 100, self.errorLabel.frame.size.width, self.errorLabel.frame.size.height); + } + } + + [super viewDidLoad]; +} + +- (void)viewDidUnload { + [self setSignUpView:nil]; + [self setLogInView:nil]; + [self setSignUpUsernameInput:nil]; + [self setSignUpPasswordInput:nil]; + [self setSelectSignUpButton:nil]; + [self setSelectLoginButton:nil]; + [super viewDidUnload]; +} + +- (void)viewWillAppear:(BOOL)animated { + [self.errorLabel setHidden:YES]; + [super viewWillAppear:animated]; + [usernameInput becomeFirstResponder]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + return NO; +} + +- (void)viewDidAppear:(BOOL)animated { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + [super viewDidAppear:animated]; +} + + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + int signUpX, loginX; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)){ + if (isOnSignUpScreen) { + loginX = -634; + signUpX = 134; + } else { + loginX = 134; + signUpX = 902; + } + self.logInView.frame = CGRectMake(loginX, 180, 500, 300); + self.signUpView.frame = CGRectMake(signUpX, 180, 500, 300); + self.selectLoginButton.frame = CGRectMake(134, 80, 250, 50); + self.selectSignUpButton.frame = CGRectMake(384, 80, 250, 50); + self.errorLabel.frame = CGRectMake(244, 180, self.errorLabel.frame.size.width, self.errorLabel.frame.size.height); + }else{ + if (isOnSignUpScreen) { + loginX = -634 + LANDSCAPE_MARGIN; + signUpX = 134 + LANDSCAPE_MARGIN; + } else { + loginX = 134 + LANDSCAPE_MARGIN; + signUpX = 902 + LANDSCAPE_MARGIN; + } + + self.logInView.frame = CGRectMake(loginX, 80, 500, 300); + self.signUpView.frame = CGRectMake(signUpX, 80, 500, 300); + self.selectLoginButton.frame = CGRectMake(134 + LANDSCAPE_MARGIN, 80 - 60, 250, 50); + self.selectSignUpButton.frame = CGRectMake(384 + LANDSCAPE_MARGIN, 80 - 60, 250, 50); + self.errorLabel.frame = CGRectMake(244 + LANDSCAPE_MARGIN, 180 - 100, self.errorLabel.frame.size.width, self.errorLabel.frame.size.height); + } + + } +} + +#pragma mark - +#pragma mark Loginp + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + if(textField == usernameInput) { + [passwordInput becomeFirstResponder]; + } else if (textField == passwordInput) { + [self checkPassword]; + } else if (textField == signUpUsernameInput){ + [signUpPasswordInput becomeFirstResponder]; + } else if (textField == signUpPasswordInput) { + [emailInput becomeFirstResponder]; + } else if (textField == emailInput) { + [self registerAccount]; + } + } else { + if(textField == usernameInput) { + [passwordInput becomeFirstResponder]; + } else if (textField == passwordInput && [self.loginControl selectedSegmentIndex] == 0) { + NSLog(@"Password return"); + NSLog(@"appdelegate:: %@", [self appDelegate]); + [self checkPassword]; + } else if (textField == passwordInput && [self.loginControl selectedSegmentIndex] == 1) { + [emailInput becomeFirstResponder]; + } else if (textField == emailInput) { + [self registerAccount]; + } + + } + return YES; +} + +- (void)checkPassword { + [self.errorLabel setHidden:YES]; + [MBProgressHUD hideHUDForView:self.view animated:YES]; + MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + HUD.labelText = @"Authenticating"; + + NSString *urlString = [NSString stringWithFormat:@"http://%@/api/login", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] + setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + [request setPostValue:[usernameInput text] forKey:@"username"]; + [request setPostValue:[passwordInput text] forKey:@"password"]; + [request setPostValue:@"login" forKey:@"submit"]; + [request setPostValue:@"1" forKey:@"api"]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(requestFinished:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + + +- (void)requestFinished:(ASIHTTPRequest *)request { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // int statusCode = [request responseStatusCode]; + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + NSDictionary *errors = [results valueForKey:@"errors"]; + if ([errors valueForKey:@"username"]) { + [self.errorLabel setText:[[errors valueForKey:@"username"] objectAtIndex:0]]; + } else if ([errors valueForKey:@"__all__"]) { + [self.errorLabel setText:[[errors valueForKey:@"__all__"] objectAtIndex:0]]; + } + [self.errorLabel setHidden:NO]; + } else { + [self.passwordInput setText:@""]; + [self.signUpPasswordInput setText:@""]; + [appDelegate reloadFeedsView:YES]; + } + +} + + +- (void)registerAccount { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + HUD.labelText = @"Registering..."; + [self.errorLabel setHidden:YES]; + NSString *urlString = [NSString stringWithFormat:@"http://%@/api/signup", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] + setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [request setPostValue:[signUpUsernameInput text] forKey:@"username"]; + [request setPostValue:[signUpPasswordInput text] forKey:@"password"]; + } else { + [request setPostValue:[usernameInput text] forKey:@"username"]; + [request setPostValue:[passwordInput text] forKey:@"password"]; + } + [request setPostValue:[emailInput text] forKey:@"email"]; + [request setPostValue:@"login" forKey:@"submit"]; + [request setPostValue:@"1" forKey:@"api"]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishRegistering:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; +} + +- (void)finishRegistering:(ASIHTTPRequest *)request { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // int statusCode = [request responseStatusCode]; + + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + NSDictionary *errors = [results valueForKey:@"errors"]; + if ([errors valueForKey:@"email"]) { + [self.errorLabel setText:[[errors valueForKey:@"email"] objectAtIndex:0]]; + } else if ([errors valueForKey:@"username"]) { + [self.errorLabel setText:[[errors valueForKey:@"username"] objectAtIndex:0]]; + } else if ([errors valueForKey:@"__all__"]) { + [self.errorLabel setText:[[errors valueForKey:@"__all__"] objectAtIndex:0]]; + } + + [self.errorLabel setHidden:NO]; + } else { + [self.passwordInput setText:@""]; + [self.signUpPasswordInput setText:@""]; +// [appDelegate showFirstTimeUser]; + [appDelegate reloadFeedsView:YES]; + [self dismissModalViewControllerAnimated:NO]; + } + +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +#pragma mark - +#pragma mark iPad: Sign Up/Login Toggle + +- (IBAction)selectSignUp { + isOnSignUpScreen = YES; + self.selectSignUpButton.selected = YES; + self.selectLoginButton.selected = NO; + [self.errorLabel setHidden:YES]; + if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { + [UIView animateWithDuration:0.35 animations:^{ + self.logInView.frame = CGRectMake(-634, 180, 500, 300); + self.signUpView.frame = CGRectMake(134, 180, 500, 300); + }]; + } else { + [UIView animateWithDuration:0.35 animations:^{ + self.logInView.frame = CGRectMake(-634 + LANDSCAPE_MARGIN, 80, 500, 300); + self.signUpView.frame = CGRectMake(134 + LANDSCAPE_MARGIN, 80, 500, 300); + }]; + } + + [self.signUpUsernameInput becomeFirstResponder]; + +} + +- (IBAction)selectLogin { + isOnSignUpScreen = NO; + self.selectSignUpButton.selected = NO; + self.selectLoginButton.selected = YES; + [self.errorLabel setHidden:YES]; + if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { + [UIView animateWithDuration:0.35 animations:^{ + self.logInView.frame = CGRectMake(134, 180, 500, 300); + self.signUpView.frame = CGRectMake(902, 180, 500, 300); + }]; + } else { + [UIView animateWithDuration:0.35 animations:^{ + self.logInView.frame = CGRectMake(134 + LANDSCAPE_MARGIN, 80, 500, 300); + self.signUpView.frame = CGRectMake(902 + LANDSCAPE_MARGIN, 80, 500, 300); + }]; + } + + [self.usernameInput becomeFirstResponder]; +} + +- (IBAction)tapLoginButton { + [self.view endEditing:YES]; + [self checkPassword]; + +} + +- (IBAction)tapSignUpButton { + [self.view endEditing:YES]; + [self registerAccount]; +} + +#pragma mark - +#pragma mark iPhone: Sign Up/Login Toggle + +- (IBAction)selectLoginSignup { + [self animateLoop]; +} + +- (void)animateLoop { + if ([self.loginControl selectedSegmentIndex] == 0) { + [UIView animateWithDuration:0.5 animations:^{ + // Login + usernameInput.frame = CGRectMake(20, 67, 280, 31); + usernameOrEmailLabel.alpha = 1.0; + + passwordInput.frame = CGRectMake(20, 129, 280, 31); + passwordLabel.frame = CGRectMake(21, 106, 212, 22); + passwordOptionalLabel.frame = CGRectMake(199, 112, 101, 16); + + emailInput.alpha = 0.0; + emailLabel.alpha = 0.0; + }]; + + passwordInput.returnKeyType = UIReturnKeyGo; + usernameInput.keyboardType = UIKeyboardTypeEmailAddress; + [usernameInput resignFirstResponder]; + [usernameInput becomeFirstResponder]; + } else { + [UIView animateWithDuration:0.5 animations:^{ + // Signup + usernameInput.frame = CGRectMake(20, 67, 130, 31); + usernameOrEmailLabel.alpha = 0.0; + + passwordInput.frame = CGRectMake(170, 67, 130, 31); + passwordLabel.frame = CGRectMake(171, 44, 212, 22); + passwordOptionalLabel.frame = CGRectMake(199, 50, 101, 16); + + emailInput.alpha = 1.0; + emailLabel.alpha = 1.0; + }]; + passwordInput.returnKeyType = UIReturnKeyNext; + usernameInput.keyboardType = UIKeyboardTypeAlphabet; + [usernameInput resignFirstResponder]; + [usernameInput becomeFirstResponder]; + } +} + +@end diff --git a/media/ios/Classes/MoveSiteViewController.h b/media/ios/Classes/MoveSiteViewController.h new file mode 100644 index 000000000..4f897b874 --- /dev/null +++ b/media/ios/Classes/MoveSiteViewController.h @@ -0,0 +1,43 @@ +// +// MoveSiteViewController.h +// NewsBlur +// +// Created by Samuel Clay on 12/2/2011. +// Copyright 2011 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" + +@class NewsBlurAppDelegate; + +@interface MoveSiteViewController : UIViewController + { + NewsBlurAppDelegate *appDelegate; +} + +- (void)reload; +- (IBAction)moveSite; +- (IBAction)moveFolder; +- (IBAction)doCancelButton; +- (IBAction)doMoveButton; +- (NSArray *)pickerFolders; + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UITextField *fromFolderInput; +@property (nonatomic) IBOutlet UITextField *toFolderInput; +@property (nonatomic) IBOutlet UILabel *titleLabel; + +@property (nonatomic) IBOutlet UIBarButtonItem *moveButton; +@property (nonatomic) IBOutlet UIBarButtonItem *cancelButton; +@property (nonatomic) IBOutlet UIPickerView *folderPicker; + +@property (nonatomic) IBOutlet UINavigationBar *navBar; +@property (nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; +@property (nonatomic) IBOutlet UILabel *movingLabel; +@property (nonatomic) IBOutlet UILabel *errorLabel; + +@property (nonatomic) NSMutableArray *folders; + +@end diff --git a/media/iphone/Classes/MoveSiteViewController.m b/media/ios/Classes/MoveSiteViewController.m similarity index 88% rename from media/iphone/Classes/MoveSiteViewController.m rename to media/ios/Classes/MoveSiteViewController.m index 9466a960a..693536a2c 100644 --- a/media/iphone/Classes/MoveSiteViewController.m +++ b/media/ios/Classes/MoveSiteViewController.m @@ -10,7 +10,6 @@ #import "NewsBlurAppDelegate.h" #import "ASIHTTPRequest.h" #import "ASIFormDataRequest.h" -#import "JSON.h" #import "StringHelper.h" @implementation MoveSiteViewController @@ -42,8 +41,6 @@ UIImageView *folderImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"folder.png"]]; [fromFolderInput setLeftView:folderImage2]; [fromFolderInput setLeftViewMode:UITextFieldViewModeAlways]; - [folderImage release]; - [folderImage2 release]; navBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; @@ -52,6 +49,17 @@ [super viewDidLoad]; } +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return YES; + } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { + return YES; + } + + return NO; +} + - (void)viewWillAppear:(BOOL)animated { [self.errorLabel setHidden:YES]; [self.movingLabel setHidden:YES]; @@ -61,11 +69,10 @@ [subview removeFromSuperview]; } UIView *label = [appDelegate makeFeedTitle:appDelegate.activeFeed]; - label.frame = CGRectMake(label.frame.origin.x, - label.frame.origin.y, - self.titleLabel.frame.size.width - - (self.titleLabel.frame.origin.x-label.frame.origin.x), - label.frame.size.height); + label.frame = CGRectMake(0, + 0, + 200, + 20); [self.titleLabel addSubview:label]; [self reload]; [super viewWillAppear:animated]; @@ -76,18 +83,6 @@ [super viewDidAppear:animated]; } -- (void)dealloc { - [appDelegate release]; - [toFolderInput release]; - [fromFolderInput release]; - [titleLabel release]; - [moveButton release]; - [cancelButton release]; - [folderPicker release]; - [navBar release]; - [folders release]; - [super dealloc]; -} - (void)reload { BOOL isTopLevel = [[appDelegate.activeFolder trim] isEqualToString:@""]; @@ -156,8 +151,12 @@ [self.movingLabel setHidden:YES]; [self.activityIndicator stopAnimating]; NSString *responseString = [request responseString]; - NSDictionary *results = [[NSDictionary alloc] - initWithDictionary:[responseString JSONValue]]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; // int statusCode = [request responseStatusCode]; int code = [[results valueForKey:@"code"] intValue]; if (code == -1) { @@ -168,7 +167,6 @@ [appDelegate.moveSiteViewController dismissModalViewControllerAnimated:YES]; [appDelegate reloadFeedsView:NO]; } - [results release]; } #pragma mark - @@ -199,8 +197,12 @@ [self.movingLabel setHidden:YES]; [self.activityIndicator stopAnimating]; NSString *responseString = [request responseString]; - NSDictionary *results = [[NSDictionary alloc] - initWithDictionary:[responseString JSONValue]]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; // int statusCode = [request responseStatusCode]; int code = [[results valueForKey:@"code"] intValue]; if (code == -1) { @@ -211,7 +213,6 @@ [appDelegate reloadFeedsView:NO]; } - [results release]; } - (void)requestFailed:(ASIHTTPRequest *)request { diff --git a/media/ios/Classes/NBContainerViewController.h b/media/ios/Classes/NBContainerViewController.h new file mode 100644 index 000000000..2d07ff360 --- /dev/null +++ b/media/ios/Classes/NBContainerViewController.h @@ -0,0 +1,39 @@ +// +// NBContainerViewController.h +// NewsBlur +// +// Created by Roy Yang on 7/24/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; + +@interface NBContainerViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; +} + +@property (readonly) BOOL storyTitlesOnLeft; +@property (readonly) int storyTitlesYCoordinate; +@property (atomic, strong) IBOutlet NewsBlurAppDelegate *appDelegate; + + +- (void)syncNextPreviousButtons; + +- (void)adjustDashboardScreen; +- (void)adjustFeedDetailScreen; +- (void)adjustFeedDetailScreenForStoryTitles; + +- (void)transitionToFeedDetail; +- (void)transitionFromFeedDetail; +- (void)transitionToShareView; +- (void)transitionFromShareView; + +- (void)dragStoryToolbar:(int)yCoordinate; +- (void)showUserProfilePopover:(id)sender; +- (void)showFeedMenuPopover:(id)sender; +- (void)showFontSettingsPopover:(id)sender; +- (void)showSitePopover:(id)sender; +- (void)hidePopover; +@end diff --git a/media/ios/Classes/NBContainerViewController.m b/media/ios/Classes/NBContainerViewController.m new file mode 100644 index 000000000..ec29820a0 --- /dev/null +++ b/media/ios/Classes/NBContainerViewController.m @@ -0,0 +1,792 @@ +// +// NBContainerViewController.m +// NewsBlur +// +// Created by Roy Yang on 7/24/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "NBContainerViewController.h" +#import "NewsBlurViewController.h" +#import "FeedDetailViewController.h" +#import "DashboardViewController.h" +#import "StoryDetailViewController.h" +#import "ShareViewController.h" +#import "UserProfileViewController.h" +#import "InteractionCell.h" +#import "ActivityCell.h" +#import "FeedsMenuViewController.h" +#import "FontSettingsViewController.h" +#import "AddSiteViewController.h" + +#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_SHARE_HEIGHT 144 +#define NB_DEFAULT_STORY_TITLE_SNAP_THRESHOLD 60 + +@interface NBContainerViewController () + +@property (nonatomic, strong) UINavigationController *masterNavigationController; +@property (nonatomic, strong) UINavigationController *storyNavigationController; +@property (nonatomic, strong) UINavigationController *shareNavigationController; +@property (nonatomic, strong) NewsBlurViewController *feedsViewController; +@property (nonatomic, strong) FeedDetailViewController *feedDetailViewController; +@property (nonatomic, strong) DashboardViewController *dashboardViewController; +@property (nonatomic, strong) StoryDetailViewController *storyDetailViewController; +@property (nonatomic, strong) ShareViewController *shareViewController; +@property (nonatomic, strong) UIView *storyTitlesStub; +@property (readwrite) BOOL storyTitlesOnLeft; +@property (readwrite) int storyTitlesYCoordinate; + +@property (readwrite) BOOL isSharingStory; +@property (readwrite) BOOL isHidingStory; +@property (readwrite) BOOL feedDetailIsVisible; + +@property (nonatomic, strong) UIPopoverController *popoverController; + +@end + +@implementation NBContainerViewController + +@synthesize appDelegate; +@synthesize masterNavigationController; +@synthesize shareNavigationController; +@synthesize feedsViewController; +@synthesize feedDetailViewController; +@synthesize dashboardViewController; +@synthesize storyDetailViewController; +@synthesize shareViewController; +@synthesize feedDetailIsVisible; +@synthesize storyNavigationController; +@synthesize storyTitlesYCoordinate; +@synthesize storyTitlesOnLeft; +@synthesize popoverController; +@synthesize storyTitlesStub; +@synthesize isSharingStory; +@synthesize isHidingStory; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillHideNotification object:nil]; + + self.view.backgroundColor = [UIColor blackColor]; + + self.masterNavigationController = appDelegate.navigationController; + self.feedsViewController = appDelegate.feedsViewController; + self.dashboardViewController = appDelegate.dashboardViewController; + self.feedDetailViewController = appDelegate.feedDetailViewController; + self.storyDetailViewController = appDelegate.storyDetailViewController; + self.shareViewController = appDelegate.shareViewController; + + // adding dashboardViewController + [self addChildViewController:self.dashboardViewController]; + [self.view addSubview:self.dashboardViewController.view]; + [self.dashboardViewController didMoveToParentViewController:self]; + + // adding master navigation controller + [self addChildViewController:self.masterNavigationController]; + [self.view addSubview:self.masterNavigationController.view]; + [self.masterNavigationController didMoveToParentViewController:self]; + + UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.storyDetailViewController]; + self.storyNavigationController = nav; + + UINavigationController *shareNav = [[UINavigationController alloc] initWithRootViewController:self.shareViewController]; + self.shareNavigationController = shareNav; + + // set default y coordinate for feedDetailY from saved preferences + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + NSInteger savedStoryTitlesYCoordinate = [userPreferences integerForKey:@"storyTitlesYCoordinate"]; + if (savedStoryTitlesYCoordinate == 1004) { + self.storyTitlesYCoordinate = savedStoryTitlesYCoordinate; + self.storyTitlesOnLeft = YES; + } else if (savedStoryTitlesYCoordinate) { + self.storyTitlesYCoordinate = savedStoryTitlesYCoordinate; + self.storyTitlesOnLeft = NO; + } else { + self.storyTitlesYCoordinate = NB_DEFAULT_STORY_TITLE_HEIGHT; + self.storyTitlesOnLeft = YES; + } + + // set up story titles stub + UIView * storyTitlesPlaceholder = [[UIView alloc] initWithFrame:CGRectZero]; + storyTitlesPlaceholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;; + storyTitlesPlaceholder.autoresizesSubviews = YES; + storyTitlesPlaceholder.backgroundColor = [UIColor whiteColor]; + + self.storyTitlesStub = storyTitlesPlaceholder; + [self.view addSubview:self.storyTitlesStub]; +} + +- (void)viewWillLayoutSubviews { + [self adjustDashboardScreen]; +} + +- (void)viewDidUnload { + [super viewDidUnload]; + // Release any retained subviews of the main view. +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + if (!self.feedDetailIsVisible) { + [self adjustDashboardScreen]; + } else { + [self adjustFeedDetailScreen]; + } +} + +# pragma mark Modals and Popovers + +- (void)showUserProfilePopover:(id)sender { + if (popoverController.isPopoverVisible) { + [popoverController dismissPopoverAnimated:NO]; + popoverController = nil; + return; + } + + popoverController = [[UIPopoverController alloc] + initWithContentViewController:appDelegate.userProfileNavigationController]; + + popoverController.delegate = self; + + [popoverController setPopoverContentSize:CGSizeMake(320, 454)]; + + if ([sender class] == [InteractionCell class] || + [sender class] == [ActivityCell class]) { + InteractionCell *cell = (InteractionCell *)sender; + + [popoverController presentPopoverFromRect:cell.bounds + inView:cell + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } else if ([sender class] == [UIBarButtonItem class]) { + [popoverController presentPopoverFromBarButtonItem:sender + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } else { + CGRect frame = [sender CGRectValue]; + [popoverController presentPopoverFromRect:frame + inView:self.storyDetailViewController.view + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } +} + +- (void)showSitePopover:(id)sender { + if (popoverController.isPopoverVisible) { + [popoverController dismissPopoverAnimated:NO]; + popoverController = nil; + return; + } + + popoverController = [[UIPopoverController alloc] + initWithContentViewController:appDelegate.addSiteViewController]; + + popoverController.delegate = self; + + [popoverController setPopoverContentSize:CGSizeMake(320, 454)]; + + if ([sender class] == [InteractionCell class] || + [sender class] == [ActivityCell class]) { + InteractionCell *cell = (InteractionCell *)sender; + + [popoverController presentPopoverFromRect:cell.bounds + inView:cell + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } else if ([sender class] == [UIBarButtonItem class]) { + [popoverController presentPopoverFromBarButtonItem:sender + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } else { + CGRect frame = [sender CGRectValue]; + [popoverController presentPopoverFromRect:frame + inView:self.feedsViewController.view + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } +} + + +- (void)showFeedMenuPopover:(id)sender { + if (popoverController.isPopoverVisible) { + [popoverController dismissPopoverAnimated:NO]; + popoverController = nil; + return; + } + + popoverController = [[UIPopoverController alloc] + initWithContentViewController:appDelegate.feedsMenuViewController]; + + popoverController.delegate = self; + + + [popoverController setPopoverContentSize:CGSizeMake(200, 86)]; +// UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] +// initWithCustomView:sender]; + [popoverController presentPopoverFromBarButtonItem:sender + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; +} + +- (void)showFontSettingsPopover:(id)sender { + if (popoverController.isPopoverVisible) { + [popoverController dismissPopoverAnimated:NO]; + popoverController = nil; + return; + } + + popoverController = [[UIPopoverController alloc] + initWithContentViewController:appDelegate.fontSettingsViewController]; + + popoverController.delegate = self; + + + [popoverController setPopoverContentSize:CGSizeMake(274.0, 130.0)]; + // UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] + // initWithCustomView:sender]; + [popoverController presentPopoverFromBarButtonItem:sender + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; +} + +- (void)hidePopover { + if (popoverController.isPopoverVisible) { + [popoverController dismissPopoverAnimated:YES]; + } + popoverController = nil; + [appDelegate.modalNavigationController dismissModalViewControllerAnimated:YES]; +} + + +- (void)syncNextPreviousButtons { + [self.storyDetailViewController setNextPreviousButtons]; +} + +# pragma mark Screen Transitions and Layout + +- (void)adjustDashboardScreen { + CGRect vb = [self.view bounds]; + self.masterNavigationController.view.frame = CGRectMake(0, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + self.dashboardViewController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, vb.size.height); +} + +- (void)adjustFeedDetailScreen { + CGRect vb = [self.view bounds]; + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { + // add the back button + self.storyDetailViewController.navigationItem.leftBarButtonItem = self.storyDetailViewController.buttonBack; + + // set center title + UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed]; + self.storyDetailViewController.navigationItem.titleView = titleLabel; + + if ([[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) { + [self.masterNavigationController popViewControllerAnimated:NO]; + } + 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 - self.storyTitlesYCoordinate); + [self.view insertSubview:self.feedDetailViewController.view atIndex:0]; + [self.masterNavigationController.view removeFromSuperview]; + } else { + // remove the back button + self.storyDetailViewController.navigationItem.leftBarButtonItem = nil; + + // remove center title + self.storyDetailViewController.navigationItem.titleView = nil; + + if (![[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) { + [self.masterNavigationController pushViewController:self.feedDetailViewController animated:NO]; + } + [self.view addSubview:self.masterNavigationController.view]; + self.masterNavigationController.view.frame = CGRectMake(0, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, vb.size.height); + } +} + +- (void)adjustFeedDetailScreenForStoryTitles { + CGRect vb = [self.view bounds]; + if (!self.storyTitlesOnLeft) { + if (self.storyTitlesYCoordinate > 890) { + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + // save coordinate + [userPreferences setInteger:1004 forKey:@"storyTitlesYCoordinate"]; + [userPreferences synchronize]; + self.storyTitlesYCoordinate = 1004; + // slide to the left + + self.storyTitlesOnLeft = YES; + + // remove the back button + self.storyDetailViewController.navigationItem.leftBarButtonItem = nil; + + // remove center title + self.storyDetailViewController.navigationItem.titleView = nil; + + if (![[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) { + [self.masterNavigationController pushViewController:self.feedDetailViewController animated:NO]; + } + + [self.view addSubview:self.masterNavigationController.view]; + self.masterNavigationController.view.frame = CGRectMake(-NB_DEFAULT_MASTER_WIDTH, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + [UIView animateWithDuration:NB_DEFAULT_SLIDER_INTERVAL delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + self.masterNavigationController.view.frame = CGRectMake(0, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, vb.size.height); + } completion:^(BOOL finished) { + [self.feedDetailViewController checkScroll]; + [appDelegate adjustStoryDetailWebView]; + [self.feedDetailViewController.storyTitlesTable reloadData]; + }]; + } + } else if (self.storyTitlesOnLeft) { + if (self.storyTitlesYCoordinate == 1004) { + return; + } else if (self.storyTitlesYCoordinate > 890) { + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + // save coordinate + [userPreferences setInteger:890 forKey:@"storyTitlesYCoordinate"]; + [userPreferences synchronize]; + self.storyTitlesYCoordinate = 890; + } + self.storyTitlesOnLeft = NO; + + // add the back button + self.storyDetailViewController.navigationItem.leftBarButtonItem = self.storyDetailViewController.buttonBack; + + // set center title + UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed]; + self.storyDetailViewController.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); + + [self.masterNavigationController.view removeFromSuperview]; + self.storyNavigationController.view.frame = CGRectMake(0, 0, vb.size.width, storyTitlesYCoordinate); + + self.storyTitlesStub.frame = CGRectMake(0, storyTitlesYCoordinate, vb.size.width, vb.size.height - storyTitlesYCoordinate); + } completion:^(BOOL finished) { + if ([[self.masterNavigationController viewControllers] containsObject:self.feedDetailViewController]) { + [self.masterNavigationController popViewControllerAnimated:NO]; + } + [self.view insertSubview:self.feedDetailViewController.view aboveSubview:self.storyTitlesStub]; + self.feedDetailViewController.view.frame = CGRectMake(0, storyTitlesYCoordinate, vb.size.width, vb.size.height - storyTitlesYCoordinate); + self.storyTitlesStub.hidden = YES; + [self.feedDetailViewController checkScroll]; + [appDelegate adjustStoryDetailWebView]; + [self.feedDetailViewController.storyTitlesTable reloadData]; + }]; + } +} + +- (void)transitionToFeedDetail { + NSLog(@"in transitionToFeedDetail"); + [self hidePopover]; + self.feedDetailIsVisible = YES; + CGRect vb = [self.view bounds]; + + // adding feedDetailViewController + [self addChildViewController:self.feedDetailViewController]; + [self.view addSubview:self.feedDetailViewController.view]; + [self.feedDetailViewController didMoveToParentViewController:self]; + + // adding storyDetailViewController + [self addChildViewController:self.storyNavigationController]; + [self.view addSubview:self.storyNavigationController.view]; + [self.storyNavigationController didMoveToParentViewController:self]; + + // reset the storyDetailViewController components + self.storyDetailViewController.webView.hidden = YES; + self.storyDetailViewController.bottomPlaceholderToolbar.hidden = NO; + self.storyDetailViewController.progressViewContainer.hidden = YES; + self.storyDetailViewController.navigationItem.rightBarButtonItems = nil; + int unreadCount = appDelegate.unreadCount; + if (unreadCount == 0) { + self.storyDetailViewController.progressView.progress = 1; + } else { + self.storyDetailViewController.progressView.progress = 0; + } + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { + // CASE: story titles on bottom + self.storyDetailViewController.navigationItem.leftBarButtonItem = self.storyDetailViewController.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]; + }]; + }]; + + + // set center title + UIView *titleLabel = [appDelegate makeFeedTitle:appDelegate.activeFeed]; + self.storyDetailViewController.navigationItem.titleView = titleLabel; + +// // set right avatar title image +// if (appDelegate.isSocialView) { +// UIButton *titleImageButton = [appDelegate makeRightFeedTitle:appDelegate.activeFeed]; +// [titleImageButton addTarget:self action:@selector(showUserProfilePopover) forControlEvents:UIControlEventTouchUpInside]; +// UIBarButtonItem *titleImageBarButton = [[UIBarButtonItem alloc] +// initWithCustomView:titleImageButton]; +// self.storyDetailViewController.navigationItem.rightBarButtonItem = titleImageBarButton; +// } else { +// self.storyDetailViewController.navigationItem.rightBarButtonItem = nil; +// } + + } else { + // CASE: story titles on left + self.storyDetailViewController.navigationItem.leftBarButtonItem = nil; + + [self.masterNavigationController pushViewController:self.feedDetailViewController animated:YES]; + self.storyNavigationController.view.frame = CGRectMake(vb.size.width, 0, vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, vb.size.height); + + + // CASE: story titles on left + [UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, + 0, + vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, + vb.size.height); + } completion:^(BOOL finished) { + [self.dashboardViewController.view removeFromSuperview]; + }]; + + // remove center title + self.storyDetailViewController.navigationItem.titleView = nil; + } +} + +- (void)transitionFromFeedDetail { + if (!self.feedDetailIsVisible) { + return; + } + + [self hidePopover]; + + if (self.isSharingStory) { + [self transitionFromShareView]; + } + + [self.storyDetailViewController clearStory]; + self.feedDetailIsVisible = NO; + CGRect vb = [self.view bounds]; + + // adding dashboardViewController and masterNavigationController + [self.view insertSubview:self.dashboardViewController.view atIndex:0]; + [self.view addSubview:self.masterNavigationController.view]; + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { + // CASE: story titles on bottom + self.dashboardViewController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH + 1, 0, vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, vb.size.height); + self.masterNavigationController.view.frame = CGRectMake(-NB_DEFAULT_MASTER_WIDTH, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + + float smallTimeInterval = NB_DEFAULT_SLIDER_INTERVAL_OUT * NB_DEFAULT_MASTER_WIDTH / vb.size.width; + float largeTimeInterval = NB_DEFAULT_SLIDER_INTERVAL_OUT * ( vb.size.width - NB_DEFAULT_MASTER_WIDTH) / vb.size.width; + + [UIView animateWithDuration:smallTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.masterNavigationController.view.frame = CGRectMake(0, 0, NB_DEFAULT_MASTER_WIDTH, vb.size.height); + self.storyNavigationController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH, + 0, + vb.size.width, + self.storyTitlesYCoordinate); + self.feedDetailViewController.view.frame = CGRectMake(NB_DEFAULT_MASTER_WIDTH, + self.storyTitlesYCoordinate, + vb.size.width, + vb.size.height - storyTitlesYCoordinate); + } completion:^(BOOL finished) { + [UIView animateWithDuration:largeTimeInterval delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.storyNavigationController.view.frame = CGRectMake(vb.size.width, 0, vb.size.width, self.storyTitlesYCoordinate); + self.feedDetailViewController.view.frame = CGRectMake(vb.size.width, + self.storyTitlesYCoordinate, + vb.size.width, + vb.size.height - storyTitlesYCoordinate); + } completion:^(BOOL finished) { + [self.storyNavigationController.view removeFromSuperview]; + [self.feedDetailViewController.view removeFromSuperview]; + }]; + }]; + } else { + // CASE: story titles on left + [UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + self.storyNavigationController.view.frame = CGRectMake(vb.size.width, + 0, + self.storyNavigationController.view.frame.size.width, + self.storyNavigationController.view.frame.size.height); + self.dashboardViewController.view.frame = CGRectMake(0, + 0, + vb.size.width - NB_DEFAULT_MASTER_WIDTH - 1, + vb.size.height); + } completion:^(BOOL finished) { + [self.storyNavigationController.view removeFromSuperview]; + [self.feedDetailViewController.view removeFromSuperview]; + }]; + } +} + +- (void)transitionToShareView { + if (isSharingStory) { + return; + } + + [self hidePopover]; + CGRect vb = [self.view bounds]; + self.isSharingStory = YES; + + // adding shareViewController + [self addChildViewController:self.shareNavigationController]; + [self.view insertSubview:self.shareNavigationController.view aboveSubview:self.storyNavigationController.view]; + [self.shareNavigationController didMoveToParentViewController:self]; + + self.shareNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + vb.size.height, + self.storyDetailViewController.view.frame.size.width, + NB_DEFAULT_SHARE_HEIGHT); + + self.shareViewController.view.frame = CGRectMake(0, + 0, + self.shareNavigationController.view.frame.size.width, + self.shareNavigationController.view.frame.size.height - 44); + [self.shareViewController.commentField becomeFirstResponder]; +} + +- (void)transitionFromShareView { + if (!isSharingStory) { + return; + } + + [self hidePopover]; + CGRect vb = [self.view bounds]; + self.isSharingStory = NO; + + if ([self.shareViewController.commentField isFirstResponder]) { + self.isHidingStory = YES; // the flag allows the keyboard animation to also slide down the share view + [self.shareViewController.commentField resignFirstResponder]; + } else { + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + storyTitlesYCoordinate); + } else { + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + vb.size.height); + } + + [UIView animateWithDuration:NB_DEFAULT_SLIDER_INTERVAL animations:^{ + self.shareNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + vb.size.height, + self.storyNavigationController.view.frame.size.width, + NB_DEFAULT_SHARE_HEIGHT); + } completion:^(BOOL finished) { + [self.shareNavigationController.view removeFromSuperview]; + }]; + } +} + +- (void)dragStoryToolbar:(int)yCoordinate { + + CGRect vb = [self.view bounds]; + // account for top toolbar and status bar + yCoordinate = yCoordinate + 44 + 20; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + if (yCoordinate > 344 && yCoordinate <= (vb.size.height)) { + + // save coordinate + self.storyTitlesYCoordinate = yCoordinate; + [userPreferences setInteger:yCoordinate forKey:@"storyTitlesYCoordinate"]; + [userPreferences synchronize]; + + // change frames + + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + yCoordinate); + if (self.storyTitlesOnLeft) { + self.storyTitlesStub.hidden = NO; + self.storyTitlesStub.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + yCoordinate, + self.storyNavigationController.view.frame.size.width, + vb.size.height - yCoordinate); + } else { + self.feedDetailViewController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + yCoordinate, + self.storyNavigationController.view.frame.size.width, + vb.size.height - yCoordinate); + [self.feedDetailViewController checkScroll]; + } + } else if (yCoordinate >= (vb.size.height)){ + // save coordinate + [userPreferences setInteger:1004 forKey:@"storyTitlesYCoordinate"]; + [userPreferences synchronize]; + self.storyTitlesYCoordinate = 1004; + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + vb.size.height); + if (self.storyTitlesOnLeft) { + self.storyTitlesStub.hidden = NO; + self.storyTitlesStub.frame = CGRectMake(self.feedDetailViewController.view.frame.origin.x, + 0, + self.feedDetailViewController.view.frame.size.width, + 0); + } + } +} + +-(void)keyboardWillShowOrHide:(NSNotification*)notification { + NSDictionary *userInfo = notification.userInfo; + NSTimeInterval duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]; + + CGRect vb = [self.view bounds]; + CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + self.shareNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + vb.size.height, + self.storyNavigationController.view.frame.size.width, + NB_DEFAULT_SHARE_HEIGHT); + + CGRect storyNavigationFrame = self.storyNavigationController.view.frame; + CGRect shareViewFrame = self.shareNavigationController.view.frame; + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + + if ([notification.name isEqualToString:@"UIKeyboardWillShowNotification"]) { + 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; + } else { + 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 { + if (UIInterfaceOrientationIsPortrait(orientation)) { + storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 44; + shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT; + } else { + storyNavigationFrame.size.height = vb.size.height - NB_DEFAULT_SHARE_HEIGHT + 44; + NSLog(@"storyNavigationFrame.size.height %f", storyNavigationFrame.size.height); + shareViewFrame.origin.y = vb.size.height - NB_DEFAULT_SHARE_HEIGHT; + } + } + + // CASE: when dismissing the keyboard but not dismissing the share view + if ([notification.name isEqualToString:@"UIKeyboardWillHideNotification"] && !self.isHidingStory) { + self.storyNavigationController.view.frame = storyNavigationFrame; + // CASE: when dismissing the keyboard AND dismissing the share view + } else if ([notification.name isEqualToString:@"UIKeyboardWillHideNotification"] && self.isHidingStory) { + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { +// self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, +// 0, +// self.storyNavigationController.view.frame.size.width, +// vb.size.height); + } else { + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + vb.size.height); + } + } + + int newStoryNavigationFrameHeight = vb.size.height - NB_DEFAULT_SHARE_HEIGHT - keyboardFrame.size.height + 44; + + + [UIView animateWithDuration:duration + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState | curve + animations:^{ + if (self.isHidingStory) { + self.shareNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + vb.size.height, + self.storyNavigationController.view.frame.size.width, + NB_DEFAULT_SHARE_HEIGHT); + if (UIInterfaceOrientationIsPortrait(orientation)) { + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + storyTitlesYCoordinate); + } else { + self.storyNavigationController.view.frame = CGRectMake(self.storyNavigationController.view.frame.origin.x, + 0, + self.storyNavigationController.view.frame.size.width, + vb.size.height); + } + } else { + self.shareNavigationController.view.frame = shareViewFrame; + // if the toolbar is higher, animate + if (UIInterfaceOrientationIsPortrait(orientation) && !self.storyTitlesOnLeft) { + if (self.storyNavigationController.view.frame.size.height < newStoryNavigationFrameHeight) { + self.storyNavigationController.view.frame = storyNavigationFrame; + } + } + } + + } completion:^(BOOL finished) { + if ([notification.name isEqualToString:@"UIKeyboardWillShowNotification"]) { + self.storyNavigationController.view.frame = storyNavigationFrame; + [self.storyDetailViewController scrolltoComment]; + } else { + // remove the shareNavigationController after keyboard slides down + if (self.isHidingStory) { + self.isHidingStory = NO; + [self.shareNavigationController.view removeFromSuperview]; + } + } + }]; +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/NewsBlurAppDelegate.h b/media/ios/Classes/NewsBlurAppDelegate.h new file mode 100644 index 000000000..37149d97d --- /dev/null +++ b/media/ios/Classes/NewsBlurAppDelegate.h @@ -0,0 +1,258 @@ +// +// NewsBlurAppDelegate.h +// NewsBlur +// +// Created by Samuel Clay on 6/16/10. +// Copyright NewsBlur 2010. All rights reserved. +// + +#import +#import "BaseViewController.h" + +#define FEED_DETAIL_VIEW_TAG 1000001 +#define STORY_DETAIL_VIEW_TAG 1000002 +#define FEED_TITLE_GRADIENT_TAG 100003 +#define FEED_DASHBOARD_VIEW_TAG 100004 +#define SHARE_MODAL_HEIGHT 120 +#define STORY_TITLES_HEIGHT 240 +#define DASHBOARD_TITLE @"NewsBlur" + +@class NewsBlurViewController; +@class DashboardViewController; +@class FeedDetailViewController; +@class FeedsMenuViewController; +@class FeedDashboardViewController; +@class FirstTimeUserViewController; +@class FirstTimeUserAddSitesViewController; +@class FirstTimeUserAddFriendsViewController; +@class FirstTimeUserAddNewsBlurViewController; +@class FriendsListViewController; +@class FontSettingsViewController; +@class StoryDetailViewController; +@class ShareViewController; +@class LoginViewController; +@class AddSiteViewController; +@class MoveSiteViewController; +@class OriginalStoryViewController; +@class UserProfileViewController; +@class NBContainerViewController; +@class FindSitesViewController; + + +@interface NewsBlurAppDelegate : BaseViewController { + UIWindow *window; + UINavigationController *ftuxNavigationController; + UINavigationController *navigationController; + UINavigationController *modalNavigationController; + UINavigationController *shareNavigationController; + UINavigationController *userProfileNavigationController; + NBContainerViewController *masterContainerViewController; + + FirstTimeUserViewController *firstTimeUserViewController; + FirstTimeUserAddSitesViewController *firstTimeUserAddSitesViewController; + FirstTimeUserAddFriendsViewController *firstTimeUserAddFriendsViewController; + FirstTimeUserAddNewsBlurViewController *firstTimeUserAddNewsBlurViewController; + + DashboardViewController *dashboardViewController; + NewsBlurViewController *feedsViewController; + FeedsMenuViewController *feedsMenuViewController; + FeedDashboardViewController *feedDashboardViewController; + FriendsListViewController *friendsListViewController; + FontSettingsViewController *fontSettingsViewController; + FeedDetailViewController *feedDetailViewController; + + StoryDetailViewController *storyDetailViewController; + ShareViewController *shareViewController; + LoginViewController *loginViewController; + AddSiteViewController *addSiteViewController; + FindSitesViewController *findSitesViewController; + MoveSiteViewController *moveSiteViewController; + OriginalStoryViewController *originalStoryViewController; + UserProfileViewController *userProfileViewController; + + NSString * activeUsername; + NSString * activeUserProfileId; + NSString * activeUserProfileName; + BOOL isRiverView; + BOOL isSocialView; + BOOL isSocialRiverView; + BOOL isTryFeedView; + BOOL popoverHasFeedView; + BOOL inFeedDetail; + BOOL inStoryDetail; + BOOL inFindingStoryMode; + NSString *tryFeedStoryId; + NSDictionary * activeFeed; + NSString * activeFolder; + NSDictionary * activeComment; + NSString * activeShareType; + NSArray * activeFolderFeeds; + NSArray * activeFeedStories; + NSArray * activeFeedUserProfiles; + NSMutableArray * activeFeedStoryLocations; + NSMutableArray * activeFeedStoryLocationIds; + NSDictionary * activeStory; + NSURL * activeOriginalStoryURL; + + int feedDetailPortraitYCoordinate; + int storyCount; + int originalStoryCount; + NSInteger selectedIntelligence; + int visibleUnreadCount; + NSMutableArray * recentlyReadStories; + NSMutableSet * recentlyReadFeeds; + NSMutableArray * readStories; + + NSDictionary * dictFolders; + NSMutableDictionary * dictFeeds; + NSMutableDictionary * dictActiveFeeds; + NSDictionary * dictSocialFeeds; + NSDictionary * dictUserProfile; + NSArray * userInteractionsArray; + NSArray * userActivitiesArray; + NSMutableArray * dictFoldersArray; + + NSArray *categories; + NSDictionary *categoryFeeds; +} + +@property (nonatomic) IBOutlet UIWindow *window; +@property (nonatomic) IBOutlet UINavigationController *ftuxNavigationController; +@property (nonatomic) IBOutlet UINavigationController *navigationController; +@property (nonatomic) UINavigationController *modalNavigationController; +@property (nonatomic) UINavigationController *shareNavigationController; +@property (nonatomic) UINavigationController *userProfileNavigationController; +@property (nonatomic) IBOutlet NBContainerViewController *masterContainerViewController; +@property (nonatomic) IBOutlet DashboardViewController *dashboardViewController; +@property (nonatomic) IBOutlet NewsBlurViewController *feedsViewController; +@property (nonatomic) IBOutlet FeedsMenuViewController *feedsMenuViewController; +@property (nonatomic) IBOutlet FeedDashboardViewController *feedDashboardViewController; +@property (nonatomic) IBOutlet FeedDetailViewController *feedDetailViewController; +@property (nonatomic) IBOutlet FriendsListViewController *friendsListViewController; +@property (nonatomic) IBOutlet StoryDetailViewController *storyDetailViewController; +@property (nonatomic) IBOutlet LoginViewController *loginViewController; +@property (nonatomic) IBOutlet AddSiteViewController *addSiteViewController; +@property (nonatomic) IBOutlet FindSitesViewController *findSitesViewController; +@property (nonatomic) IBOutlet MoveSiteViewController *moveSiteViewController; +@property (nonatomic) IBOutlet OriginalStoryViewController *originalStoryViewController; +@property (nonatomic) IBOutlet ShareViewController *shareViewController; +@property (nonatomic) IBOutlet FontSettingsViewController *fontSettingsViewController; +@property (nonatomic) IBOutlet UserProfileViewController *userProfileViewController; + +@property (nonatomic) IBOutlet FirstTimeUserViewController *firstTimeUserViewController; +@property (nonatomic) IBOutlet FirstTimeUserAddSitesViewController *firstTimeUserAddSitesViewController; +@property (nonatomic) IBOutlet FirstTimeUserAddFriendsViewController *firstTimeUserAddFriendsViewController; +@property (nonatomic) IBOutlet FirstTimeUserAddNewsBlurViewController *firstTimeUserAddNewsBlurViewController; + +@property (readwrite) NSString * activeUsername; +@property (readwrite) NSString * activeUserProfileId; +@property (readwrite) NSString * activeUserProfileName; +@property (nonatomic, readwrite) BOOL isRiverView; +@property (nonatomic, readwrite) BOOL isSocialView; +@property (nonatomic, readwrite) BOOL isSocialRiverView; +@property (nonatomic, readwrite) BOOL isTryFeedView; +@property (nonatomic, readwrite) BOOL inFindingStoryMode; +@property (nonatomic) NSString *tryFeedStoryId; +@property (nonatomic) NSString *tryFeedCategory; +@property (nonatomic, readwrite) BOOL popoverHasFeedView; +@property (nonatomic, readwrite) BOOL inFeedDetail; +@property (nonatomic, readwrite) BOOL inStoryDetail; +@property (readwrite) NSDictionary * activeFeed; +@property (readwrite) NSString * activeFolder; +@property (readwrite) NSDictionary * activeComment; +@property (readwrite) NSString * activeShareType; +@property (readwrite) NSArray * activeFolderFeeds; +@property (readwrite) NSArray * activeFeedStories; +@property (readwrite) NSArray * activeFeedUserProfiles; +@property (readwrite) NSMutableArray * activeFeedStoryLocations; +@property (readwrite) NSMutableArray * activeFeedStoryLocationIds; +@property (readwrite) NSDictionary * activeStory; +@property (readwrite) NSURL * activeOriginalStoryURL; +@property (readwrite) int feedDetailPortraitYCoordinate; +@property (readwrite) int storyCount; +@property (readwrite) int originalStoryCount; +@property (readwrite) int visibleUnreadCount; +@property (readwrite) NSInteger selectedIntelligence; +@property (readwrite) NSMutableArray * recentlyReadStories; +@property (readwrite) NSMutableSet * recentlyReadFeeds; +@property (readwrite) NSMutableArray * readStories; + +@property (nonatomic) NSDictionary *dictFolders; +@property (nonatomic, strong) NSMutableDictionary *dictFeeds; +@property (nonatomic) NSMutableDictionary *dictActiveFeeds; +@property (nonatomic) NSDictionary *dictSocialFeeds; +@property (nonatomic) NSDictionary *dictUserProfile; +@property (nonatomic) NSArray *userInteractionsArray; +@property (nonatomic) NSArray *userActivitiesArray; +@property (nonatomic) NSMutableArray *dictFoldersArray; + +@property (nonatomic) NSArray *categories; +@property (nonatomic) NSDictionary *categoryFeeds; + ++ (NewsBlurAppDelegate*) sharedAppDelegate; + +- (void)showFirstTimeUser; +- (void)showLogin; + +// social +- (void)showUserProfileModal:(id)sender; +- (void)pushUserProfile; +- (void)hideUserProfileModal; +- (void)showFindFriends; + +- (void)showAddSiteModal:(id)sender; +- (void)showMoveSite; +- (void)loadFeedDetailView; +- (void)loadTryFeedDetailView:(NSString *)feedId withStory:(NSString *)contentId isSocial:(BOOL)social withUser:(NSDictionary *)user showFindingStory:(BOOL)showHUD; +- (void)loadRiverFeedDetailView; +- (void)loadStoryDetailView; +- (void)adjustStoryDetailWebView; +- (void)calibrateStoryTitles; +- (void)reloadFeedsView:(BOOL)showLoader; +- (void)setTitle:(NSString *)title; +- (void)showOriginalStory:(NSURL *)url; +- (void)closeOriginalStory; +- (void)hideStoryDetailView; +- (void)changeActiveFeedDetailRow; +- (void)dragFeedDetailView:(float)y; +- (void)showShareView:(NSString *)type setUserId:(NSString *)userId setUsername:(NSString *)username setReplyId:(NSString *)commentIndex; +- (void)hideShareView:(BOOL)resetComment; +- (void)resetShareComments; +- (BOOL)isSocialFeed:(NSString *)feedIdStr; +- (BOOL)isPortrait; +- (void)confirmLogout; + +- (int)indexOfNextUnreadStory; +- (int)indexOfNextStory; +- (int)indexOfPreviousStory; +- (int)indexOfActiveStory; +- (int)locationOfActiveStory; +- (void)pushReadStory:(id)storyId; +- (id)popReadStory; +- (int)locationOfStoryId:(id)storyId; + +- (void)setStories:(NSArray *)activeFeedStoriesValue; +- (void)setFeedUserProfiles:(NSArray *)activeFeedUserProfilesValue; +- (void)addStories:(NSArray *)stories; +- (void)addFeedUserProfiles:(NSArray *)activeFeedUserProfilesValue; +- (int)unreadCount; +- (int)allUnreadCount; +- (int)unreadCountForFeed:(NSString *)feedId; +- (int)unreadCountForFolder:(NSString *)folderName; +- (void)markActiveStoryRead; +- (NSDictionary *)markVisibleStoriesRead; +- (void)markStoryRead:(NSString *)storyId feedId:(id)feedId; +- (void)markStoryRead:(NSDictionary *)story feed:(NSDictionary *)feed; +- (void)markActiveFeedAllRead; +- (void)markActiveFolderAllRead; +- (void)markFeedAllRead:(id)feedId; +- (void)calculateStoryLocations; ++ (int)computeStoryScore:(NSDictionary *)intelligence; +- (NSString *)extractFolderName:(NSString *)folderName; +- (NSString *)extractParentFolderName:(NSString *)folderName; ++ (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; +@end + diff --git a/media/ios/Classes/NewsBlurAppDelegate.m b/media/ios/Classes/NewsBlurAppDelegate.m new file mode 100644 index 000000000..9cd6fa4cd --- /dev/null +++ b/media/ios/Classes/NewsBlurAppDelegate.m @@ -0,0 +1,1207 @@ +// +// NewsBlurAppDelegate.m +// NewsBlur +// +// Created by Samuel Clay on 6/16/10. +// Copyright NewsBlur 2010. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "NewsBlurViewController.h" +#import "NBContainerViewController.h" +#import "FeedDetailViewController.h" +#import "DashboardViewController.h" +#import "FeedsMenuViewController.h" +#import "StoryDetailViewController.h" +#import "FirstTimeUserViewController.h" +#import "FriendsListViewController.h" +#import "LoginViewController.h" +#import "AddSiteViewController.h" +#import "FindSitesViewController.h" +#import "MoveSiteViewController.h" +#import "OriginalStoryViewController.h" +#import "ShareViewController.h" +#import "UserProfileViewController.h" +#import "NBContainerViewController.h" +#import "AFJSONRequestOperation.h" +#import "findSitesViewController.h" +#import "InteractionsModule.h" +#import "ActivityModule.h" +#import "FirstTimeUserViewController.h" +#import "FirstTimeUserAddSitesViewController.h" +#import "FirstTimeUserAddFriendsViewController.h" +#import "FirstTimeUserAddNewsBlurViewController.h" +#import "MBProgressHUD.h" +#import "Utilities.h" +#import "StringHelper.h" + +@implementation NewsBlurAppDelegate + +@synthesize window; + +@synthesize ftuxNavigationController; +@synthesize navigationController; +@synthesize modalNavigationController; +@synthesize shareNavigationController; +@synthesize userProfileNavigationController; +@synthesize masterContainerViewController; +@synthesize dashboardViewController; +@synthesize feedsViewController; +@synthesize feedsMenuViewController; +@synthesize feedDetailViewController; +@synthesize feedDashboardViewController; +@synthesize friendsListViewController; +@synthesize fontSettingsViewController; +@synthesize storyDetailViewController; +@synthesize shareViewController; +@synthesize loginViewController; +@synthesize addSiteViewController; +@synthesize findSitesViewController; +@synthesize moveSiteViewController; +@synthesize originalStoryViewController; +@synthesize userProfileViewController; + +@synthesize firstTimeUserViewController; +@synthesize firstTimeUserAddSitesViewController; +@synthesize firstTimeUserAddFriendsViewController; +@synthesize firstTimeUserAddNewsBlurViewController; + +@synthesize feedDetailPortraitYCoordinate; +@synthesize activeUsername; +@synthesize activeUserProfileId; +@synthesize activeUserProfileName; +@synthesize isRiverView; +@synthesize isSocialView; +@synthesize isSocialRiverView; +@synthesize isTryFeedView; + +@synthesize inFindingStoryMode; +@synthesize tryFeedStoryId; +@synthesize tryFeedCategory; +@synthesize popoverHasFeedView; +@synthesize inFeedDetail; +@synthesize inStoryDetail; +@synthesize activeComment; +@synthesize activeShareType; + +@synthesize activeFeed; +@synthesize activeFolder; +@synthesize activeFolderFeeds; +@synthesize activeFeedStories; +@synthesize activeFeedStoryLocations; +@synthesize activeFeedStoryLocationIds; +@synthesize activeFeedUserProfiles; +@synthesize activeStory; +@synthesize storyCount; +@synthesize visibleUnreadCount; +@synthesize originalStoryCount; +@synthesize selectedIntelligence; +@synthesize activeOriginalStoryURL; +@synthesize recentlyReadStories; +@synthesize recentlyReadFeeds; +@synthesize readStories; + +@synthesize dictFolders; +@synthesize dictFeeds; +@synthesize dictActiveFeeds; +@synthesize dictSocialFeeds; +@synthesize dictUserProfile; +@synthesize userInteractionsArray; +@synthesize userActivitiesArray; +@synthesize dictFoldersArray; + +@synthesize categories; +@synthesize categoryFeeds; + ++ (NewsBlurAppDelegate*) sharedAppDelegate { + return (NewsBlurAppDelegate*) [UIApplication sharedApplication].delegate; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + +// [TestFlight takeOff:@"101dd20fb90f7355703b131d9af42633_MjQ0NTgyMDExLTA4LTIxIDIzOjU3OjEzLjM5MDcyOA"]; + + NSString *currentiPhoneVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; + + self.navigationController.viewControllers = [NSArray arrayWithObject:self.feedsViewController]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [ASIHTTPRequest setDefaultUserAgentString:[NSString stringWithFormat:@"NewsBlur iPad App v%@", + currentiPhoneVersion]]; + [window addSubview:self.masterContainerViewController.view]; + self.window.rootViewController = self.masterContainerViewController; + } else { + [ASIHTTPRequest setDefaultUserAgentString:[NSString stringWithFormat:@"NewsBlur iPhone App v%@", + currentiPhoneVersion]]; + [window addSubview:self.navigationController.view]; + self.window.rootViewController = self.navigationController; + } + + [window makeKeyAndVisible]; + [self.feedsViewController fetchFeedList:YES]; + +// [self showFirstTimeUser]; + return YES; +} + +- (void)viewDidLoad { + self.visibleUnreadCount = 0; + [self setRecentlyReadStories:[NSMutableArray array]]; +} + + +#pragma mark - +#pragma mark FeedsView + +- (void)showAddSiteModal:(id)sender { +// FindSitesViewController *sitesVC = [[FindSitesViewController alloc] init]; +// self.findSitesViewController = sitesVC; +// +// UINavigationController *sitesNav = [[UINavigationController alloc] initWithRootViewController:sitesVC]; +// self.modalNavigationController = sitesNav; +// self.modalNavigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + + [self.addSiteViewController reload]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController showSitePopover:sender]; + } else { + [navigationController presentModalViewController:self.addSiteViewController animated:YES]; + } +} + +#pragma mark - +#pragma mark Social Views + +- (void)showUserProfileModal:(id)sender { + UserProfileViewController *newUserProfile = [[UserProfileViewController alloc] init]; + self.userProfileViewController = newUserProfile; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.userProfileViewController]; + self.userProfileNavigationController = navController; + + + // adding Done button + UIBarButtonItem *donebutton = [[UIBarButtonItem alloc] + initWithTitle:@"Close" + style:UIBarButtonItemStyleDone + target:self + action:@selector(hideUserProfileModal)]; + + newUserProfile.navigationItem.rightBarButtonItem = donebutton; + newUserProfile.navigationItem.title = self.activeUserProfileName; + newUserProfile.navigationItem.backBarButtonItem.title = self.activeUserProfileName; + [newUserProfile getUserProfile]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController showUserProfilePopover:sender]; + } else { + [self.navigationController presentModalViewController:navController animated:YES]; + } + +} + +- (void)pushUserProfile { + UserProfileViewController *userProfileView = [[UserProfileViewController alloc] init]; + + + // adding Done button + UIBarButtonItem *donebutton = [[UIBarButtonItem alloc] + initWithTitle:@"Close" + style:UIBarButtonItemStyleDone + target:self + action:@selector(hideUserProfileModal)]; + + userProfileView.navigationItem.rightBarButtonItem = donebutton; + userProfileView.navigationItem.title = self.activeUserProfileName; + userProfileView.navigationItem.backBarButtonItem.title = self.activeUserProfileName; + [userProfileView getUserProfile]; + if (self.modalNavigationController.view.window == nil) { + [self.userProfileNavigationController pushViewController:userProfileView animated:YES]; + } else { + [self.modalNavigationController pushViewController:userProfileView animated:YES]; + }; + +} + +- (void)hideUserProfileModal { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController hidePopover]; + } else { + [self.navigationController dismissModalViewControllerAnimated:YES]; + } +} + +- (void)showFindFriends { + FriendsListViewController *friendsBVC = [[FriendsListViewController alloc] init]; + UINavigationController *friendsNav = [[UINavigationController alloc] initWithRootViewController:friendsListViewController]; + + self.friendsListViewController = friendsBVC; + self.modalNavigationController = friendsNav; + self.modalNavigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.modalNavigationController.modalPresentationStyle = UIModalPresentationFormSheet; + [masterContainerViewController presentModalViewController:modalNavigationController animated:YES]; + } else { + [navigationController presentModalViewController:modalNavigationController animated:YES]; + } + [self.friendsListViewController loadSuggestedFriendsList]; +} + +- (void)showShareView:(NSString *)type + setUserId:(NSString *)userId + setUsername:(NSString *)username + setReplyId:(NSString *)replyId { + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController transitionToShareView]; + [self.shareViewController setSiteInfo:type setUserId:userId setUsername:username setReplyId:replyId]; + } else { + if (self.shareNavigationController == nil) { + UINavigationController *shareNav = [[UINavigationController alloc] initWithRootViewController:self.shareViewController]; + self.shareNavigationController = shareNav; + } + [self.navigationController presentModalViewController:self.shareNavigationController animated:YES]; + [self.shareViewController setSiteInfo:type setUserId:userId setUsername:username setReplyId:replyId]; + } +} + +- (void)hideShareView:(BOOL)resetComment { + if (resetComment) { + self.shareViewController.commentField.text = @""; + self.shareViewController.currentType = nil; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController transitionFromShareView]; + } else { + [self.navigationController dismissModalViewControllerAnimated:YES]; + [self.shareViewController.commentField resignFirstResponder]; + } +} + +- (void)resetShareComments { + [shareViewController clearComments]; +} + +#pragma mark - +#pragma mark Views + +- (void)showLogin { + self.dictFeeds = nil; + self.dictSocialFeeds = nil; + self.dictFolders = nil; + self.dictFoldersArray = nil; + self.userActivitiesArray = nil; + self.userInteractionsArray = nil; + + [self.feedsViewController.feedTitlesTable reloadData]; + [self.feedsViewController resetToolbar]; + + [self.dashboardViewController.interactionsModule.interactionsTable reloadData]; + [self.dashboardViewController.activitiesModule.activitiesTable reloadData]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + [userPreferences setInteger:-1 forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController presentModalViewController:loginViewController animated:NO]; + } else { + [feedsMenuViewController dismissModalViewControllerAnimated:NO]; + [self.navigationController presentModalViewController:loginViewController animated:NO]; + } +} + +- (void)showFirstTimeUser { +// [self.feedsViewController changeToAllMode]; + + UINavigationController *ftux = [[UINavigationController alloc] initWithRootViewController:self.firstTimeUserViewController]; + + ftux.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + + self.ftuxNavigationController = ftux; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.ftuxNavigationController.modalPresentationStyle = UIModalPresentationFormSheet; + [self.masterContainerViewController presentModalViewController:self.ftuxNavigationController animated:YES]; + + self.ftuxNavigationController.view.superview.frame = CGRectMake(0, 0, 540, 540);//it's important to do this after + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (UIInterfaceOrientationIsPortrait(orientation)) { + self.ftuxNavigationController.view.superview.center = self.view.center; + } else { + self.ftuxNavigationController.view.superview.center = CGPointMake(self.view.center.y, self.view.center.x); + } + + } else { + [self.navigationController presentModalViewController:self.ftuxNavigationController animated:YES]; + } +} + +- (void)showMoveSite { + UINavigationController *navController = self.navigationController; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + moveSiteViewController.modalPresentationStyle=UIModalPresentationFormSheet; + [navController presentModalViewController:moveSiteViewController animated:YES]; + } else { + [navController presentModalViewController:moveSiteViewController animated:YES]; + } +} + +- (void)reloadFeedsView:(BOOL)showLoader { + [feedsViewController fetchFeedList:showLoader]; + [loginViewController dismissModalViewControllerAnimated:NO]; + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; +} + +- (void)loadFeedDetailView { + [self setStories:nil]; + [self setFeedUserProfiles:nil]; + + self.inFeedDetail = YES; + + // navController.navigationBar.tintColor = UIColorFromRGB(0x59f6c1); + + popoverHasFeedView = YES; + + [feedDetailViewController resetFeedDetail]; + [feedDetailViewController fetchFeedDetail:1 withCallback:nil]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController transitionToFeedDetail]; + } else { + + UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: @"All" + style: UIBarButtonItemStyleBordered + target: nil + action: nil]; + [feedsViewController.navigationItem setBackBarButtonItem: newBackButton]; + UINavigationController *navController = self.navigationController; + [navController pushViewController:feedDetailViewController animated:YES]; + navController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + } +} + +- (void)loadTryFeedDetailView:(NSString *)feedId withStory:(NSString *)contentId isSocial:(BOOL)social withUser:(NSDictionary *)user showFindingStory:(BOOL)showHUD { + if (showHUD) { + [self.storyDetailViewController showShareHUD:@"Loading story"]; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [self.navigationController popToRootViewControllerAnimated:NO]; + [self.navigationController dismissModalViewControllerAnimated:YES]; + } + + NSDictionary *feed = nil; + + if (social) { + feed = [self.dictSocialFeeds objectForKey:feedId]; + self.isSocialView = YES; + self.inFindingStoryMode = YES; + + if (feed == nil) { + feed = user; + self.isTryFeedView = YES; + } + } else { + feed = [self.dictFeeds objectForKey:feedId]; + if (feed == nil) { + feed = user; + self.isTryFeedView = YES; + + } + [self setIsSocialView:NO]; + [self setInFindingStoryMode:NO]; + } + + [self setTryFeedStoryId:contentId]; + [self setActiveFeed:feed]; + [self setActiveFolder:nil]; + + [self loadFeedDetailView]; +} + +- (BOOL)isSocialFeed:(NSString *)feedIdStr { + if ([feedIdStr length] > 6) { + NSString *feedIdSubStr = [feedIdStr substringToIndex:6]; + if ([feedIdSubStr isEqualToString:@"social"]) { + return YES; + } + } + return NO; +} + +- (BOOL)isPortrait { + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) { + return YES; + } else { + return NO; + } +} + +- (void)confirmLogout { + UIAlertView *logoutConfirm = [[UIAlertView alloc] initWithTitle:@"Positive?" + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Logout", nil]; + [logoutConfirm show]; + [logoutConfirm setTag:1]; +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (alertView.tag == 1) { // this is logout + if (buttonIndex == 0) { + return; + } else { + NSLog(@"Logging out..."); + NSString *urlS = [NSString stringWithFormat:@"http://%@/reader/logout?api=1", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlS]; + + __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setResponseEncoding:NSUTF8StringEncoding]; + [request setDefaultResponseEncoding:NSUTF8StringEncoding]; + [request setFailedBlock:^(void) { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + }]; + [request setCompletionBlock:^(void) { + NSLog(@"Logout successful"); + [MBProgressHUD hideHUDForView:self.view animated:YES]; + [self showLogin]; + }]; + [request setTimeOutSeconds:30]; + [request startAsynchronous]; + + [ASIHTTPRequest setSessionCookies:nil]; + + [MBProgressHUD hideHUDForView:self.view animated:YES]; + MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + HUD.labelText = @"Logging out..."; + } + } +} + +- (void)loadRiverFeedDetailView { + [self setStories:nil]; + [self setFeedUserProfiles:nil]; + self.inFeedDetail = YES; + + [feedDetailViewController resetFeedDetail]; + [feedDetailViewController fetchRiverPage:1 withCallback:nil]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController transitionToFeedDetail]; + } else { + UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: @"All" + style: UIBarButtonItemStyleBordered + target: nil + action: nil]; + [feedsViewController.navigationItem setBackBarButtonItem: newBackButton]; + UINavigationController *navController = self.navigationController; + [navController pushViewController:feedDetailViewController animated:YES]; + navController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + } +} + +- (void)adjustStoryDetailWebView { + // change UIWebView + int contentWidth = storyDetailViewController.view.frame.size.width; + NSLog(@"contentWidth is %i", contentWidth); + [storyDetailViewController changeWebViewWidth:contentWidth]; + +} + +- (void)calibrateStoryTitles { + [self.feedDetailViewController checkScroll]; + [self.feedDetailViewController changeActiveFeedDetailRow]; + +} + +- (void)dragFeedDetailView:(float)y { + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + if (UIInterfaceOrientationIsPortrait(storyDetailViewController.interfaceOrientation)) { + y = y + 20; + + if(y > 955) { + self.feedDetailPortraitYCoordinate = 960; + } else if(y < 950 && y > 200) { + self.feedDetailPortraitYCoordinate = y; + } + + [userPreferences setInteger:self.feedDetailPortraitYCoordinate forKey:@"feedDetailPortraitYCoordinate"]; + [userPreferences synchronize]; + [self adjustStoryDetailWebView]; + } +} + +- (void)changeActiveFeedDetailRow { + [feedDetailViewController changeActiveFeedDetailRow]; +} + +- (void)loadStoryDetailView { + NSString *feedTitle; + if (self.isRiverView) { + feedTitle = self.activeFolder; + } else { + feedTitle = [activeFeed objectForKey:@"feed_title"]; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:feedTitle style: UIBarButtonItemStyleBordered target: nil action: nil]; + [feedDetailViewController.navigationItem setBackBarButtonItem: newBackButton]; + UINavigationController *navController = self.navigationController; + [navController pushViewController:storyDetailViewController animated:YES]; + //self.storyDetailViewController.navigationItem.titleView = nil; + [navController.navigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:feedTitle style:UIBarButtonItemStyleBordered target:nil action:nil]]; + navController.navigationItem.hidesBackButton = YES; + navController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + } + + [self.storyDetailViewController initStory]; +} + +- (void)navigationController:(UINavigationController *)navController + willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { + if (viewController == feedDetailViewController) { + UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0,0,70,35)]; + UIButton *myBackButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [myBackButton setFrame:CGRectMake(0,0,70,35)]; + [myBackButton setImage:[UIImage imageNamed:@"toolbar_back_button.png"] forState:UIControlStateNormal]; + [myBackButton setEnabled:YES]; + [myBackButton addTarget:viewController.navigationController action:@selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside]; + [backButtonView addSubview:myBackButton]; + UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:backButtonView]; + viewController.navigationItem.leftBarButtonItem = backButton; + navController.navigationItem.leftBarButtonItem = backButton; + viewController.navigationItem.hidesBackButton = YES; + navController.navigationItem.hidesBackButton = YES; + } +} + +- (void)setTitle:(NSString *)title { + UILabel *label = [[UILabel alloc] init]; + [label setFont:[UIFont boldSystemFontOfSize:16.0]]; + [label setBackgroundColor:[UIColor clearColor]]; + [label setTextColor:[UIColor whiteColor]]; + [label setText:title]; + [label sizeToFit]; + [navigationController.navigationBar.topItem setTitleView:label]; +} + +- (void)showOriginalStory:(NSURL *)url { + self.activeOriginalStoryURL = url; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController presentModalViewController:originalStoryViewController animated:YES]; + } else { + [self.navigationController presentModalViewController:originalStoryViewController animated:YES]; + } +} + +- (void)closeOriginalStory { + [originalStoryViewController dismissModalViewControllerAnimated:YES]; +} + +- (void)hideStoryDetailView { +// [self.storyDetailViewController clearStory]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self.masterContainerViewController transitionFromFeedDetail]; + } else { + [self.navigationController popViewControllerAnimated:YES]; + } +} + +- (int)indexOfNextUnreadStory { + int activeLocation = [self locationOfActiveStory]; + int readStatus = -1; + for (int i=activeLocation+1; i < [self.activeFeedStoryLocations count]; i++) { + int location = [[self.activeFeedStoryLocations objectAtIndex:i] intValue]; + NSDictionary *story = [activeFeedStories objectAtIndex:location]; + readStatus = [[story objectForKey:@"read_status"] intValue]; + if (readStatus == 0) { + return location; + } + } + if (activeLocation > 0) { + for (int i=activeLocation-1; i >= 0; i--) { + int location = [[self.activeFeedStoryLocations objectAtIndex:i] intValue]; + NSDictionary *story = [activeFeedStories objectAtIndex:location]; + readStatus = [[story objectForKey:@"read_status"] intValue]; + if (readStatus == 0) { + return location; + } + } + } + return -1; +} + +- (int)indexOfNextStory { + int activeLocation = [self locationOfActiveStory]; + int nextStoryLocation = activeLocation + 1; + if (nextStoryLocation < [self.activeFeedStoryLocations count]) { + int location = [[self.activeFeedStoryLocations objectAtIndex:nextStoryLocation] intValue]; + return location; + } + return -1; +} + +- (int)indexOfPreviousStory { + NSInteger activeIndex = [self indexOfActiveStory]; + return MAX(-1, activeIndex-1); +} + +- (int)indexOfActiveStory { + for (int i=0; i < self.storyCount; i++) { + NSDictionary *story = [activeFeedStories objectAtIndex:i]; + if ([activeStory objectForKey:@"id"] == [story objectForKey:@"id"]) { + return i; + } + } + return -1; +} + +- (int)locationOfActiveStory { + for (int i=0; i < [activeFeedStoryLocations count]; i++) { + if ([activeFeedStoryLocationIds objectAtIndex:i] == + [self.activeStory objectForKey:@"id"]) { + return i; + } + } + return -1; +} + +- (void)pushReadStory:(id)storyId { + if ([self.readStories lastObject] != storyId) { + [self.readStories addObject:storyId]; + } +} + +- (id)popReadStory { + if (storyCount == 0) { + return nil; + } else { + [self.readStories removeLastObject]; + id lastStory = [self.readStories lastObject]; + return lastStory; + } +} + +- (int)locationOfStoryId:(id)storyId { + for (int i=0; i < [activeFeedStoryLocations count]; i++) { + if ([activeFeedStoryLocationIds objectAtIndex:i] == storyId) { + return [[activeFeedStoryLocations objectAtIndex:i] intValue]; + } + } + return -1; +} + +- (int)unreadCount { + if (self.isRiverView || self.isSocialRiverView) { + return [self unreadCountForFolder:nil]; + } else { + return [self unreadCountForFeed:nil]; + } +} + +- (int)allUnreadCount { + int total = 0; + for (id key in self.dictSocialFeeds) { + NSDictionary *feed = [self.dictSocialFeeds objectForKey:key]; + total += [[feed objectForKey:@"ps"] intValue]; + total += [[feed objectForKey:@"nt"] intValue]; + NSLog(@"feed title and number is %@ %i", [feed objectForKey:@"feed_title"], ([[feed objectForKey:@"ps"] intValue] + [[feed objectForKey:@"nt"] intValue])); + NSLog(@"total is %i", total); + } + + for (id key in self.dictFeeds) { + NSDictionary *feed = [self.dictFeeds objectForKey:key]; + total += [[feed objectForKey:@"ps"] intValue]; + total += [[feed objectForKey:@"nt"] intValue]; + NSLog(@"feed title and number is %@ %i", [feed objectForKey:@"feed_title"], ([[feed objectForKey:@"ps"] intValue] + [[feed objectForKey:@"nt"] intValue])); + NSLog(@"total is %i", total); + } + + return total; +} + +- (int)unreadCountForFeed:(NSString *)feedId { + int total = 0; + NSDictionary *feed; + + if (feedId) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + if (self.isSocialView || self.isSocialRiverView) { + feed = [self.dictSocialFeeds objectForKey:feedIdStr]; + } else { + feed = [self.dictFeeds objectForKey:feedIdStr]; + } + + } else { + feed = self.activeFeed; + } + + total += [[feed objectForKey:@"ps"] intValue]; + if ([self selectedIntelligence] <= 0) { + total += [[feed objectForKey:@"nt"] intValue]; + } + if ([self selectedIntelligence] <= -1) { + total += [[feed objectForKey:@"ng"] intValue]; + } + + return total; +} + +- (int)unreadCountForFolder:(NSString *)folderName { + int total = 0; + NSArray *folder; + + if (!folderName && self.activeFolder == @"ALL BLURBLOG STORIES") { + for (id feedId in self.dictSocialFeeds) { + total += [self unreadCountForFeed:feedId]; + } + } else if (!folderName && self.activeFolder == @"ALL STORIES STORIES") { + for (id feedId in self.dictFeeds) { + total += [self unreadCountForFeed:feedId]; + } + } else { + if (!folderName) { + folder = [self.dictFolders objectForKey:self.activeFolder]; + } else { + folder = [self.dictFolders objectForKey:folderName]; + } + + for (id feedId in folder) { + total += [self unreadCountForFeed:feedId]; + } + } + + return total; +} + +- (void)addStories:(NSArray *)stories { + self.activeFeedStories = [self.activeFeedStories arrayByAddingObjectsFromArray:stories]; + self.storyCount = [self.activeFeedStories count]; + [self calculateStoryLocations]; +} + +- (void)setStories:(NSArray *)activeFeedStoriesValue { + self.activeFeedStories = activeFeedStoriesValue; + self.storyCount = [self.activeFeedStories count]; + self.recentlyReadStories = [NSMutableArray array]; + self.recentlyReadFeeds = [NSMutableSet set]; + [self calculateStoryLocations]; +} + +- (void)setFeedUserProfiles:(NSArray *)activeFeedUserProfilesValue{ + self.activeFeedUserProfiles = activeFeedUserProfilesValue; +} + +- (void)addFeedUserProfiles:(NSArray *)activeFeedUserProfilesValue { + self.activeFeedUserProfiles = [self.activeFeedUserProfiles arrayByAddingObjectsFromArray:activeFeedUserProfilesValue]; +} + +- (void)markActiveStoryRead { + int activeLocation = [self locationOfActiveStory]; + NSLog(@"activeLocation is %i", activeLocation); + if (activeLocation == -1) { + return; + } + + // changes the story layout in story feed detail + [self.feedDetailViewController changeActiveStoryTitleCellLayout]; + + int activeIndex = [[activeFeedStoryLocations objectAtIndex:activeLocation] intValue]; + + NSDictionary *feed; + NSDictionary *friendFeed; + id feedId; + NSString *feedIdStr; + NSDictionary *story = [activeFeedStories objectAtIndex:activeIndex]; + NSMutableArray *otherFriendShares = [[self.activeStory objectForKey:@"shared_by_friends"] mutableCopy]; + NSMutableArray *otherFriendComments = [[self.activeStory objectForKey:@"commented_by_friends"] mutableCopy]; + + if (self.isSocialView) { + feedId = [self.activeStory objectForKey:@"social_user_id"]; + feedIdStr = [NSString stringWithFormat:@"social:%@",feedId]; + feed = [self.dictSocialFeeds objectForKey:feedIdStr]; + + [otherFriendShares removeObject:feedId]; + NSLog(@"otherFriendFeeds is %@", otherFriendShares); + [otherFriendComments removeObject:feedId]; + NSLog(@"otherFriendFeeds is %@", otherFriendComments); + + // make sure we set the active feed + self.activeFeed = feed; + } else if (self.isSocialRiverView) { + feedId = [[self.activeStory objectForKey:@"friend_user_ids"] objectAtIndex:0]; + feedIdStr = [NSString stringWithFormat:@"social:%@",feedId]; + feed = [self.dictSocialFeeds objectForKey:feedIdStr]; + + [otherFriendShares removeObject:feedId]; + NSLog(@"otherFriendFeeds is %@", otherFriendShares); + [otherFriendComments removeObject:feedId]; + NSLog(@"otherFriendFeeds is %@", otherFriendComments); + + // make sure we set the active feed + self.activeFeed = feed; + } else { + feedId = [self.activeStory objectForKey:@"story_feed_id"]; + feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + feed = [self.dictFeeds objectForKey:feedIdStr]; + + // make sure we set the active feed + self.activeFeed = feed; + } + + // decrement all other friend feeds if they have the same story + if (self.isSocialView || self.isSocialRiverView) { + for (int i = 0; i < otherFriendShares.count; i++) { + feedIdStr = [NSString stringWithFormat:@"social:%@", + [otherFriendShares objectAtIndex:i]]; + friendFeed = [self.dictSocialFeeds objectForKey:feedIdStr]; + [self markStoryRead:story feed:friendFeed]; + } + + for (int i = 0; i < otherFriendComments.count; i++) { + feedIdStr = [NSString stringWithFormat:@"social:%@", + [otherFriendComments objectAtIndex:i]]; + friendFeed = [self.dictSocialFeeds objectForKey:feedIdStr]; + [self markStoryRead:story feed:friendFeed]; + } + } + + [self.recentlyReadStories addObject:[NSNumber numberWithInt:activeLocation]]; + [self markStoryRead:story feed:feed]; +} + +- (NSDictionary *)markVisibleStoriesRead { + NSMutableDictionary *feedsStories = [NSMutableDictionary dictionary]; + for (NSDictionary *story in self.activeFeedStories) { + if ([[story objectForKey:@"read_status"] intValue] != 0) { + continue; + } + NSString *feedIdStr = [NSString stringWithFormat:@"%@",[story objectForKey:@"story_feed_id"]]; + NSDictionary *feed = [self.dictFeeds objectForKey:feedIdStr]; + if (![feedsStories objectForKey:feedIdStr]) { + [feedsStories setObject:[NSMutableArray array] forKey:feedIdStr]; + } + NSMutableArray *stories = [feedsStories objectForKey:feedIdStr]; + [stories addObject:[story objectForKey:@"id"]]; + [self markStoryRead:story feed:feed]; + } + return feedsStories; +} + +- (void)markStoryRead:(NSString *)storyId feedId:(id)feedId { + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + NSDictionary *feed = [self.dictFeeds objectForKey:feedIdStr]; + NSDictionary *story = nil; + for (NSDictionary *s in self.activeFeedStories) { + if ([[s objectForKey:@"story_guid"] isEqualToString:storyId]) { + story = s; + break; + } + } + [self markStoryRead:story feed:feed]; +} + +- (void)markStoryRead:(NSDictionary *)story feed:(NSDictionary *)feed { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [feed objectForKey:@"id"]]; + + NSMutableDictionary *newStory = [story mutableCopy]; + [newStory setValue:[NSNumber numberWithInt:1] forKey:@"read_status"]; + + // make the story as read in self.activeFeedStories + NSString *newStoryIdStr = [NSString stringWithFormat:@"%@", [newStory valueForKey:@"id"]]; + NSMutableArray *newActiveFeedStories = [self.activeFeedStories mutableCopy]; + for (int i = 0; i < [newActiveFeedStories count]; i++) { + NSMutableArray *thisStory = [[newActiveFeedStories objectAtIndex:i] mutableCopy]; + NSString *thisStoryIdStr = [NSString stringWithFormat:@"%@", [thisStory valueForKey:@"id"]]; + if ([newStoryIdStr isEqualToString:thisStoryIdStr]) { + [newActiveFeedStories replaceObjectAtIndex:i withObject:newStory]; + break; + } + } + self.activeFeedStories = newActiveFeedStories; + + self.visibleUnreadCount -= 1; + if (![self.recentlyReadFeeds containsObject:[newStory objectForKey:@"story_feed_id"]]) { + [self.recentlyReadFeeds addObject:[newStory objectForKey:@"story_feed_id"]]; + } + + NSMutableDictionary *newFeed = [feed mutableCopy]; + int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; + if (score > 0) { + int unreads = MAX(0, [[newFeed objectForKey:@"ps"] intValue] - 1); + [newFeed setValue:[NSNumber numberWithInt:unreads] forKey:@"ps"]; + } else if (score == 0) { + int unreads = MAX(0, [[newFeed objectForKey:@"nt"] intValue] - 1); + [newFeed setValue:[NSNumber numberWithInt:unreads] forKey:@"nt"]; + } else if (score < 0) { + int unreads = MAX(0, [[newFeed objectForKey:@"ng"] intValue] - 1); + [newFeed setValue:[NSNumber numberWithInt:unreads] forKey:@"ng"]; + } + + if (self.isSocialView || self.isSocialRiverView) { + [self.dictSocialFeeds setValue:newFeed forKey:feedIdStr]; + } else { + [self.dictFeeds setValue:newFeed forKey:feedIdStr]; + } + + self.activeFeed = newFeed; +} + +- (void)markActiveFeedAllRead { + id feedId = [self.activeFeed objectForKey:@"id"]; + [self markFeedAllRead:feedId]; +} + +- (void)markActiveFolderAllRead { + if (self.activeFolder == @"Everything") { + for (NSString *folderName in self.dictFoldersArray) { + for (id feedId in [self.dictFolders objectForKey:folderName]) { + [self markFeedAllRead:feedId]; + } + } + } else { + for (id feedId in [self.dictFolders objectForKey:self.activeFolder]) { + [self markFeedAllRead:feedId]; + } + } +} + +- (void)markFeedAllRead:(id)feedId { + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + NSMutableDictionary *feed = self.isSocialView ? [[self.dictSocialFeeds objectForKey:feedIdStr] mutableCopy] : [[self.dictFeeds objectForKey:feedIdStr] mutableCopy]; + + [feed setValue:[NSNumber numberWithInt:0] forKey:@"ps"]; + [feed setValue:[NSNumber numberWithInt:0] forKey:@"nt"]; + [feed setValue:[NSNumber numberWithInt:0] forKey:@"ng"]; + if (self.isSocialView) { + [self.dictSocialFeeds setValue:feed forKey:feedIdStr]; + } else { + [self.dictFeeds setValue:feed forKey:feedIdStr]; + } +} + +- (void)calculateStoryLocations { + self.visibleUnreadCount = 0; + self.activeFeedStoryLocations = [NSMutableArray array]; + self.activeFeedStoryLocationIds = [NSMutableArray array]; + for (int i=0; i < self.storyCount; i++) { + NSDictionary *story = [self.activeFeedStories objectAtIndex:i]; + int score = [NewsBlurAppDelegate computeStoryScore:[story objectForKey:@"intelligence"]]; + if (score >= self.selectedIntelligence) { + NSNumber *location = [NSNumber numberWithInt:i]; + [self.activeFeedStoryLocations addObject:location]; + [self.activeFeedStoryLocationIds addObject:[story objectForKey:@"id"]]; + if ([[story objectForKey:@"read_status"] intValue] == 0) { + self.visibleUnreadCount += 1; + } + } + } +} + ++ (int)computeStoryScore:(NSDictionary *)intelligence { + int score = 0; + int title = [[intelligence objectForKey:@"title"] intValue]; + int author = [[intelligence objectForKey:@"author"] intValue]; + int tags = [[intelligence objectForKey:@"tags"] intValue]; + + int score_max = MAX(title, MAX(author, tags)); + int score_min = MIN(title, MIN(author, tags)); + + if (score_max > 0) score = score_max; + else if (score_min < 0) score = score_min; + + if (score == 0) score = [[intelligence objectForKey:@"feed"] integerValue]; + +// NSLog(@"%d/%d -- %d: %@", score_max, score_min, score, intelligence); + return score; +} + + + +- (NSString *)extractParentFolderName:(NSString *)folderName { + if ([folderName containsString:@"Top Level"]) { + folderName = @""; + } + + if ([folderName containsString:@" - "]) { + int lastFolderLoc = [folderName rangeOfString:@" - " options:NSBackwardsSearch].location; + folderName = [folderName substringToIndex:lastFolderLoc]; + } else { + folderName = @"— Top Level —"; + } + + return folderName; +} + +- (NSString *)extractFolderName:(NSString *)folderName { + if ([folderName containsString:@"Top Level"]) { + folderName = @""; + } + + if ([folderName containsString:@" - "]) { + int folder_loc = [folderName rangeOfString:@" - " options:NSBackwardsSearch].location; + folderName = [folderName substringFromIndex:(folder_loc + 3)]; + } + + return folderName; +} + +#pragma mark - +#pragma mark Feed Templates + ++ (UIView *)makeGradientView:(CGRect)rect startColor:(NSString *)start endColor:(NSString *)end { + UIView *gradientView = [[UIView alloc] initWithFrame:rect]; + + CAGradientLayer *gradient = [CAGradientLayer layer]; + gradient.frame = CGRectMake(0, 1, rect.size.width, rect.size.height-1); + gradient.opacity = 0.7; + unsigned int color = 0; + unsigned int colorFade = 0; + if ([start class] == [NSNull class]) { + start = @"505050"; + } + if ([end class] == [NSNull class]) { + end = @"303030"; + } + NSScanner *scanner = [NSScanner scannerWithString:start]; + [scanner scanHexInt:&color]; + NSScanner *scannerFade = [NSScanner scannerWithString:end]; + [scannerFade scanHexInt:&colorFade]; + gradient.colors = [NSArray arrayWithObjects:(id)[UIColorFromRGB(color) CGColor], (id)[UIColorFromRGB(colorFade) CGColor], nil]; + + CALayer *whiteBackground = [CALayer layer]; + whiteBackground.frame = CGRectMake(0, 1, rect.size.width, rect.size.height-1); + whiteBackground.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7].CGColor; + [gradientView.layer addSublayer:whiteBackground]; + + [gradientView.layer addSublayer:gradient]; + + CALayer *topBorder = [CALayer layer]; + topBorder.frame = CGRectMake(0, 1, rect.size.width, 1); + topBorder.backgroundColor = [UIColorFromRGB(colorFade) colorWithAlphaComponent:0.7].CGColor; + topBorder.opacity = 1; + [gradientView.layer addSublayer:topBorder]; + + CALayer *bottomBorder = [CALayer layer]; + bottomBorder.frame = CGRectMake(0, rect.size.height-1, rect.size.width, 1); + bottomBorder.backgroundColor = [UIColorFromRGB(colorFade) colorWithAlphaComponent:0.7].CGColor; + bottomBorder.opacity = 1; + [gradientView.layer addSublayer:bottomBorder]; + + return gradientView; +} + +- (UIView *)makeFeedTitleGradient:(NSDictionary *)feed withRect:(CGRect)rect { + UIView *gradientView; + if (self.isRiverView || self.isSocialView || self.isSocialRiverView) { + gradientView = [NewsBlurAppDelegate + makeGradientView:rect + startColor:[feed objectForKey:@"favicon_fade"] + endColor:[feed objectForKey:@"favicon_color"]]; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = [feed objectForKey:@"feed_title"]; + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.textAlignment = UITextAlignmentLeft; + titleLabel.lineBreakMode = UILineBreakModeTailTruncation; + titleLabel.numberOfLines = 1; + titleLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:11.0]; + titleLabel.shadowOffset = CGSizeMake(0, 1); + if ([[feed objectForKey:@"favicon_text_color"] class] != [NSNull class]) { + titleLabel.textColor = [[feed objectForKey:@"favicon_text_color"] + isEqualToString:@"white"] ? + [UIColor whiteColor] : + [UIColor blackColor]; + titleLabel.shadowColor = [[feed objectForKey:@"favicon_text_color"] + isEqualToString:@"white"] ? + UIColorFromRGB(0x202020) : + UIColorFromRGB(0xd0d0d0); + } else { + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.shadowColor = [UIColor blackColor]; + } + titleLabel.frame = CGRectMake(32, 1, rect.size.width-32, 20); + + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [feed objectForKey:@"id"]]; + UIImage *titleImage = [Utilities getImage:feedIdStr]; + UIImageView *titleImageView = [[UIImageView alloc] initWithImage:titleImage]; + titleImageView.frame = CGRectMake(8, 3, 16.0, 16.0); + [titleLabel addSubview:titleImageView]; + + [gradientView addSubview:titleLabel]; + [gradientView addSubview:titleImageView]; + } else { + gradientView = [NewsBlurAppDelegate + makeGradientView:CGRectMake(0, -1, 1024, 10) + // hard coding the 1024 as a hack for window.frame.size.width + startColor:[feed objectForKey:@"favicon_fade"] + endColor:[feed objectForKey:@"favicon_color"]]; + } + + gradientView.opaque = YES; + + return gradientView; +} + +- (UIView *)makeFeedTitle:(NSDictionary *)feed { + UILabel *titleLabel = [[UILabel alloc] init]; + if (self.isSocialRiverView) { + titleLabel.text = [NSString stringWithFormat:@" All Blurblog Stories"]; + } else if (self.isRiverView) { + titleLabel.text = [NSString stringWithFormat:@" %@", self.activeFolder]; + } else if (self.isSocialView) { + titleLabel.text = [NSString stringWithFormat:@" %@", [feed objectForKey:@"feed_title"]]; + } else { + titleLabel.text = [NSString stringWithFormat:@" %@", [feed objectForKey:@"feed_title"]]; + } + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.textAlignment = UITextAlignmentLeft; + titleLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:15.0]; + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.lineBreakMode = UILineBreakModeTailTruncation; + titleLabel.numberOfLines = 1; + titleLabel.shadowColor = [UIColor blackColor]; + titleLabel.shadowOffset = CGSizeMake(0, -1); + titleLabel.center = CGPointMake(0, -2); + [titleLabel sizeToFit]; + + if (!self.isSocialView) { + titleLabel.center = CGPointMake(28, -2); + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [feed objectForKey:@"id"]]; + UIImage *titleImage; + if (self.isSocialRiverView) { + titleImage = [UIImage imageNamed:@"group_white.png"]; + } else if (self.isRiverView) { + titleImage = [UIImage imageNamed:@"folder_white.png"]; + } else { + titleImage = [Utilities getImage:feedIdStr]; + } + UIImageView *titleImageView = [[UIImageView alloc] initWithImage:titleImage]; + titleImageView.frame = CGRectMake(0.0, 2.0, 16.0, 16.0); + [titleLabel addSubview:titleImageView]; + } + return titleLabel; +} + +- (UIButton *)makeRightFeedTitle:(NSDictionary *)feed { + + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [feed objectForKey:@"id"]]; + UIImage *titleImage = [Utilities getImage:feedIdStr]; + + titleImage = [Utilities roundCorneredImage:titleImage radius:6]; + + UIButton *titleImageButton = [UIButton buttonWithType:UIButtonTypeCustom]; + titleImageButton.bounds = CGRectMake(0, 0, 32, 32); + + [titleImageButton setImage:titleImage forState:UIControlStateNormal]; + return titleImageButton; +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/NewsBlurViewController.h b/media/ios/Classes/NewsBlurViewController.h new file mode 100644 index 000000000..3668d04b3 --- /dev/null +++ b/media/ios/Classes/NewsBlurViewController.h @@ -0,0 +1,97 @@ +// +// NewsBlurViewController.h +// NewsBlur +// +// Created by Samuel Clay on 6/16/10. +// Copyright NewsBlur 2010. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" +#import "ASIHTTPRequest.h" +#import "PullToRefreshView.h" +#import "BaseViewController.h" +#import "WEPopoverController.h" + +@class NewsBlurAppDelegate; + +@interface NewsBlurViewController : BaseViewController + { + NewsBlurAppDelegate *appDelegate; + + NSMutableDictionary * activeFeedLocations; + NSMutableDictionary *visibleFeeds; + NSMutableDictionary *stillVisibleFeeds; + BOOL viewShowingAllFeeds; + BOOL hasNoSites; + PullToRefreshView *pull; + NSDate *lastUpdate; + NSCache *imageCache; + + UIView *innerView; + UITableView * feedTitlesTable; + UIToolbar * feedViewToolbar; + UISlider * feedScoreSlider; + UIBarButtonItem * homeButton; + UISegmentedControl * intelligenceControl; + WEPopoverController *popoverController; + Class popoverClass; +} + +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIView *innerView; +@property (nonatomic) IBOutlet UITableView *feedTitlesTable; +@property (nonatomic) IBOutlet UIToolbar *feedViewToolbar; +@property (nonatomic) IBOutlet UISlider * feedScoreSlider; +@property (nonatomic) IBOutlet UIBarButtonItem * homeButton; +@property (nonatomic) NSMutableDictionary *activeFeedLocations; +@property (nonatomic) NSMutableDictionary *visibleFeeds; +@property (nonatomic) NSMutableDictionary *stillVisibleFeeds; +@property (nonatomic, readwrite) BOOL viewShowingAllFeeds; +@property (nonatomic, readwrite) BOOL hasNoSites; +@property (nonatomic) PullToRefreshView *pull; +@property (nonatomic) NSDate *lastUpdate; +@property (nonatomic) NSCache *imageCache; +@property (nonatomic) IBOutlet UISegmentedControl * intelligenceControl; +@property (nonatomic, retain) WEPopoverController *popoverController; +@property (nonatomic) NSIndexPath *currentRowAtIndexPath; +@property (strong, nonatomic) IBOutlet UIView *noFocusMessage; +@property (strong, nonatomic) IBOutlet UIBarButtonItem *toolbarLeftMargin; + +- (void)returnToApp; +- (void)fetchFeedList:(BOOL)showLoader; +- (void)finishedWithError:(ASIHTTPRequest *)request; +- (void)finishLoadingFeedList:(ASIHTTPRequest *)request; +- (void)finishRefreshingFeedList:(ASIHTTPRequest *)request; +- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation; +- (void)didSelectSectionHeader:(UIButton *)button; +- (IBAction)selectIntelligence; +- (void)changeToAllMode; +- (void)updateFeedsWithIntelligence:(int)previousLevel newLevel:(int)newLevel; +- (void)calculateFeedLocations:(BOOL)markVisible; +- (IBAction)sectionTapped:(UIButton *)button; +- (IBAction)sectionUntapped:(UIButton *)button; +- (IBAction)sectionUntappedOutside:(UIButton *)button; +- (void)redrawUnreadCounts; ++ (int)computeMaxScoreForFeed:(NSDictionary *)feed; +- (void)switchSitesUnread; +- (void)loadFavicons; +- (void)loadAvatars; +- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)refreshFeedList; +- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; +- (void)showUserProfile; +- (void)showSettingsPopover:(id)sender; +- (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view; +- (void)fadeSelectedCell; +- (IBAction)tapAddSite:(id)sender; + +- (void)resetToolbar; + + +@end diff --git a/media/ios/Classes/NewsBlurViewController.m b/media/ios/Classes/NewsBlurViewController.m new file mode 100644 index 000000000..08bfe9bfe --- /dev/null +++ b/media/ios/Classes/NewsBlurViewController.m @@ -0,0 +1,1457 @@ +// +// NewsBlurViewController.m +// NewsBlur +// +// Created by Samuel Clay on 6/16/10. +// Copyright NewsBlur 2010. All rights reserved. +// + +#import "NewsBlurViewController.h" +#import "NewsBlurAppDelegate.h" +#import "NBContainerViewController.h" +#import "DashboardViewController.h" +#import "FeedTableCell.h" +#import "FeedsMenuViewController.h" +#import "UserProfileViewController.h" +#import "StoryDetailViewController.h" +#import "ASIHTTPRequest.h" +#import "PullToRefreshView.h" +#import "MBProgressHUD.h" +#import "Base64.h" +#import "Utilities.h" +#import "UIBarButtonItem+WEPopover.h" + + +#define kPhoneTableViewRowHeight 36; +#define kTableViewRowHeight 36; +#define kBlurblogTableViewRowHeight 47; +#define kPhoneBlurblogTableViewRowHeight 39; + +@interface NewsBlurViewController () + +@property (nonatomic, strong) NSMutableDictionary *updatedDictSocialFeeds_; +@property (nonatomic, strong) NSMutableDictionary *updatedDictFeeds_; +@property (readwrite) BOOL inPullToRefresh_; + +@end + +@implementation NewsBlurViewController + +@synthesize appDelegate; +@synthesize innerView; +@synthesize feedTitlesTable; +@synthesize feedViewToolbar; +@synthesize feedScoreSlider; +@synthesize homeButton; +@synthesize intelligenceControl; +@synthesize activeFeedLocations; +@synthesize visibleFeeds; +@synthesize stillVisibleFeeds; +@synthesize viewShowingAllFeeds; +@synthesize pull; +@synthesize lastUpdate; +@synthesize imageCache; +@synthesize popoverController; +@synthesize currentRowAtIndexPath; +@synthesize noFocusMessage; +@synthesize toolbarLeftMargin; +@synthesize hasNoSites; +@synthesize updatedDictFeeds_; +@synthesize updatedDictSocialFeeds_; +@synthesize inPullToRefresh_; + +#pragma mark - +#pragma mark Globals + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + popoverClass = [WEPopoverController class]; + + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + pull = [[PullToRefreshView alloc] initWithScrollView:self.feedTitlesTable]; + [pull setDelegate:self]; + [self.feedTitlesTable addSubview:pull]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(returnToApp) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + imageCache = [[NSCache alloc] init]; + [imageCache setDelegate:self]; + + [self.intelligenceControl setWidth:50 forSegmentAtIndex:0]; + [self.intelligenceControl setWidth:68 forSegmentAtIndex:1]; + [self.intelligenceControl setWidth:62 forSegmentAtIndex:2]; + self.intelligenceControl.hidden = YES; + + +} + +- (void)viewWillAppear:(BOOL)animated { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController transitionFromFeedDetail]; + } + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + [self setUserAvatarLayout:orientation]; + + [super viewWillAppear:animated]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + if ([userPreferences integerForKey:@"selectedIntelligence"] == 1) { + self.viewShowingAllFeeds = NO; + [self.intelligenceControl setSelectedSegmentIndex:2]; + [appDelegate setSelectedIntelligence:1]; + } else if ([userPreferences integerForKey:@"selectedIntelligence"] == 0) { + self.viewShowingAllFeeds = NO; + [self.intelligenceControl setSelectedSegmentIndex:1]; + [appDelegate setSelectedIntelligence:0]; + } else { // default state, ALL BLURBLOG STORIES + self.viewShowingAllFeeds = YES; + [self.intelligenceControl setSelectedSegmentIndex:0]; + [appDelegate setSelectedIntelligence:0]; + } + +// self.feedTitlesTable.separatorStyle = UITableViewCellSeparatorStyleNone; // DO NOT USE. THIS BREAKS SHIT. + UIColor *bgColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1.0]; + self.feedTitlesTable.backgroundColor = bgColor; + + self.feedTitlesTable.separatorColor = [UIColor clearColor]; + + // reset all feed detail specific data + appDelegate.activeFeed = nil; + appDelegate.isSocialView = NO; + appDelegate.isRiverView = NO; + appDelegate.inFindingStoryMode = NO; + [MBProgressHUD hideHUDForView:appDelegate.storyDetailViewController.view animated:NO]; + + if (appDelegate.activeFeed || appDelegate.isRiverView) { + [self.feedTitlesTable beginUpdates]; + [self.feedTitlesTable + reloadRowsAtIndexPaths:[self.feedTitlesTable indexPathsForVisibleRows] + withRowAnimation:UITableViewRowAnimationNone]; + [self.feedTitlesTable endUpdates]; + + NSInteger previousLevel = [self.intelligenceControl selectedSegmentIndex] - 1; + NSInteger newLevel = [appDelegate selectedIntelligence]; + if (newLevel != previousLevel) { + [appDelegate setSelectedIntelligence:newLevel]; + if (!self.viewShowingAllFeeds) { + [self updateFeedsWithIntelligence:previousLevel newLevel:newLevel]; + } + [self redrawUnreadCounts]; + } + } + + + // perform these only if coming from the feed detail view + if (appDelegate.inFeedDetail) { + appDelegate.inFeedDetail = NO; + // reload the data and then set the highlight again + [self.feedTitlesTable reloadData]; + [self redrawUnreadCounts]; + [self.feedTitlesTable selectRowAtIndexPath:self.currentRowAtIndexPath + animated:NO + scrollPosition:UITableViewScrollPositionNone]; + } + +} + +- (void)viewDidAppear:(BOOL)animated { +// [self.feedTitlesTable selectRowAtIndexPath:self.currentRowAtIndexPath +// animated:NO +// scrollPosition:UITableViewScrollPositionNone]; + + [super viewDidAppear:animated]; + [self performSelector:@selector(fadeSelectedCell) withObject:self afterDelay:0.6]; + self.navigationController.navigationBar.backItem.title = @"All Sites"; +} + +- (void)fadeSelectedCell { + [self.feedTitlesTable deselectRowAtIndexPath:[self.feedTitlesTable indexPathForSelectedRow] + animated:YES]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [self.popoverController dismissPopoverAnimated:YES]; + self.popoverController = nil; + [super viewWillDisappear:animated]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return YES; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + [self setUserAvatarLayout:toInterfaceOrientation]; +} + +- (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + if (UIInterfaceOrientationIsPortrait(orientation)) { + UIButton *avatar = (UIButton *)self.navigationItem.leftBarButtonItem.customView; + CGRect buttonFrame = avatar.frame; + buttonFrame.size = CGSizeMake(32, 32); + avatar.frame = buttonFrame; + } else { + UIButton *avatar = (UIButton *)self.navigationItem.leftBarButtonItem.customView; + CGRect buttonFrame = avatar.frame; + buttonFrame.size = CGSizeMake(28, 28); + avatar.frame = buttonFrame; + } + } +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [self.feedTitlesTable reloadData]; +} + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + [self setToolbarLeftMargin:nil]; + [self setNoFocusMessage:nil]; + [self setInnerView:nil]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + + +#pragma mark - +#pragma mark Initialization + +- (void)returnToApp { + NSDate *decayDate = [[NSDate alloc] initWithTimeIntervalSinceNow:(BACKGROUND_REFRESH_SECONDS)]; + NSLog(@"Last Update: %@ - %f", self.lastUpdate, [self.lastUpdate timeIntervalSinceDate:decayDate]); + if ([self.lastUpdate timeIntervalSinceDate:decayDate] < 0) { + [self fetchFeedList:YES]; + } + +} + +-(void)fetchFeedList:(BOOL)showLoader { + if (showLoader && appDelegate.navigationController.topViewController == appDelegate.feedsViewController) { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + HUD.labelText = @"On its way..."; + } + + NSURL *urlFeedList; + + if (self.inPullToRefresh_) { + urlFeedList = [NSURL URLWithString: + [NSString stringWithFormat:@"http://%@/reader/feeds?flat=true", + NEWSBLUR_URL]]; + } else { + urlFeedList = [NSURL URLWithString: + [NSString stringWithFormat:@"http://%@/reader/feeds?flat=true&update_counts=false", + NEWSBLUR_URL]]; + } + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:urlFeedList]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] + setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; + [request setDelegate:self]; + [request setResponseEncoding:NSUTF8StringEncoding]; + [request setDefaultResponseEncoding:NSUTF8StringEncoding]; + [request setDidFinishSelector:@selector(finishLoadingFeedList:)]; + [request setDidFailSelector:@selector(finishedWithError:)]; + [request setTimeOutSeconds:30]; + [request startAsynchronous]; + NSLog(@"urlFeedList is %@", urlFeedList); + self.lastUpdate = [NSDate date]; +} + +- (void)finishedWithError:(ASIHTTPRequest *)request { + [MBProgressHUD hideHUDForView:self.view animated:YES]; + [pull finishedLoading]; + + // User clicking on another link before the page loads is OK. + [self informError:[request error]]; + self.inPullToRefresh_ = NO; +} + +- (void)finishLoadingFeedList:(ASIHTTPRequest *)request { + if ([request responseStatusCode] == 403) { + return [appDelegate showLogin]; + } else if ([request responseStatusCode] >= 500) { + [pull finishedLoading]; + return [self informError:@"The server barfed!"]; + } + + self.hasNoSites = NO; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + +// NSLog(@"results are %@", results); + [MBProgressHUD hideHUDForView:self.view animated:YES]; + self.stillVisibleFeeds = [NSMutableDictionary dictionary]; + self.visibleFeeds = [NSMutableDictionary dictionary]; + [pull finishedLoading]; + [self loadFavicons]; + + appDelegate.activeUsername = [results objectForKey:@"user"]; + + // set title only if on currestont controller + if (appDelegate.feedsViewController.view.window && [results objectForKey:@"user"]) { + [appDelegate setTitle:[results objectForKey:@"user"]]; + } + + // adding user avatar to left + NSString *url = [NSString stringWithFormat:@"%@", [[results objectForKey:@"social_profile"] objectForKey:@"photo_url"]]; + NSURL * imageURL = [NSURL URLWithString:url]; + NSData * imageData = [NSData dataWithContentsOfURL:imageURL]; + UIImage * userAvatarImage = [UIImage imageWithData:imageData]; + userAvatarImage = [Utilities roundCorneredImage:userAvatarImage radius:6]; + + UIButton *userAvatarButton = [UIButton buttonWithType:UIButtonTypeCustom]; + + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + userAvatarButton.bounds = CGRectMake(0, 0, 32, 32); + [userAvatarButton addTarget:self action:@selector(showUserProfile) forControlEvents:UIControlEventTouchUpInside]; + [userAvatarButton setImage:userAvatarImage forState:UIControlStateNormal]; + + + UIBarButtonItem *userAvatar = [[UIBarButtonItem alloc] + initWithCustomView:userAvatarButton]; + + self.navigationItem.leftBarButtonItem = userAvatar; + [self setUserAvatarLayout:orientation]; + + // adding settings button to right + +// UIImage *settingsImage = [UIImage imageNamed:@"settings.png"]; +// UIButton *settings = [UIButton buttonWithType:UIButtonTypeCustom]; +// settings.bounds = CGRectMake(0, 0, 32, 32); +// [settings addTarget:self action:@selector(showSettingsPopover:) forControlEvents:UIControlEventTouchUpInside]; +// [settings setImage:settingsImage forState:UIControlStateNormal]; +// +// UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] +// initWithCustomView:settings]; + + UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"settings.png"] style:UIBarButtonItemStylePlain target:self action:@selector(showSettingsPopover:)]; + + + self.navigationItem.rightBarButtonItem = settingsButton; + + NSMutableDictionary *sortedFolders = [[NSMutableDictionary alloc] init]; + NSArray *sortedArray; + + // Set up dictUserProfile and userActivitiesArray + appDelegate.dictUserProfile = [results objectForKey:@"social_profile"]; + appDelegate.userActivitiesArray = [results objectForKey:@"activities"]; + + // Only update the dashboard if there is a social profile + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.dashboardViewController refreshInteractions]; + [appDelegate.dashboardViewController refreshActivity]; + } + + // Set up dictSocialFeeds + NSArray *socialFeedsArray = [results objectForKey:@"social_feeds"]; + NSMutableArray *socialFolder = [[NSMutableArray alloc] init]; + NSMutableDictionary *socialDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *tempActiveFeeds = [[NSMutableDictionary alloc] init]; + appDelegate.dictActiveFeeds = tempActiveFeeds; + + for (int i = 0; i < socialFeedsArray.count; i++) { + NSString *userKey = [NSString stringWithFormat:@"%@", + [[socialFeedsArray objectAtIndex:i] objectForKey:@"id"]]; + [socialFolder addObject: [[socialFeedsArray objectAtIndex:i] objectForKey:@"id"]]; + [socialDict setObject:[socialFeedsArray objectAtIndex:i] + forKey:userKey]; + } + + appDelegate.dictSocialFeeds = socialDict; + [self loadAvatars]; + + // set up dictFolders + NSMutableDictionary * allFolders = [[NSMutableDictionary alloc] init]; + + if (![[results objectForKey:@"flat_folders"] isKindOfClass:[NSArray class]]) { + allFolders = [[results objectForKey:@"flat_folders"] mutableCopy]; + } + + [allFolders setValue:socialFolder forKey:@""]; + + if (![[allFolders allKeys] containsObject:@" "]) { + [allFolders setValue:[[NSArray alloc] init] forKey:@" "]; + } + + appDelegate.dictFolders = allFolders; + + // set up dictFeeds + appDelegate.dictFeeds = [[results objectForKey:@"feeds"] mutableCopy]; + + // sort all the folders + appDelegate.dictFoldersArray = [NSMutableArray array]; + for (id f in appDelegate.dictFolders) { + [appDelegate.dictFoldersArray addObject:f]; + NSArray *folder = [appDelegate.dictFolders objectForKey:f]; + sortedArray = [folder sortedArrayUsingComparator:^NSComparisonResult(id id1, id id2) { + NSString *feedTitleA; + NSString *feedTitleB; + + if ([appDelegate isSocialFeed:[NSString stringWithFormat:@"%@", id1]]) { + feedTitleA = [[appDelegate.dictSocialFeeds + objectForKey:[NSString stringWithFormat:@"%@", id1]] + objectForKey:@"feed_title"]; + feedTitleB = [[appDelegate.dictSocialFeeds + objectForKey:[NSString stringWithFormat:@"%@", id2]] + objectForKey:@"feed_title"]; + } else { + feedTitleA = [[appDelegate.dictFeeds + objectForKey:[NSString stringWithFormat:@"%@", id1]] + objectForKey:@"feed_title"]; + feedTitleB = [[appDelegate.dictFeeds + objectForKey:[NSString stringWithFormat:@"%@", id2]] + objectForKey:@"feed_title"]; + } + return [feedTitleA caseInsensitiveCompare:feedTitleB]; + }]; + [sortedFolders setValue:sortedArray forKey:f]; + } + appDelegate.dictFolders = sortedFolders; + [appDelegate.dictFoldersArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + if (self.viewShowingAllFeeds) { + [self calculateFeedLocations:NO]; + } else { + [self calculateFeedLocations:YES]; + } + + // test for empty + + if ([[appDelegate.dictFeeds allKeys] count] == 0 && + [[appDelegate.dictSocialFeeds allKeys] count] == 0) { + self.hasNoSites = YES; + } + + [self.feedTitlesTable reloadData]; + + // assign categories for FTUX + + if (![[results objectForKey:@"categories"] isKindOfClass:[NSNull class]]){ + appDelegate.categories = [[results objectForKey:@"categories"] objectForKey:@"categories"]; + appDelegate.categoryFeeds = [[results objectForKey:@"categories"] objectForKey:@"feeds"]; + } + + // test for latest version of app + NSString *serveriPhoneVersion = [results objectForKey:@"iphone_version"]; + NSString *currentiPhoneVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; + + float serveriPhoneVersionFloat = [serveriPhoneVersion floatValue]; + float currentiPhoneVersionFloat = [currentiPhoneVersion floatValue]; + + if (currentiPhoneVersionFloat < serveriPhoneVersionFloat) { + NSLog(@"Version: %f - %f", serveriPhoneVersionFloat, currentiPhoneVersionFloat); + NSString *title = [NSString stringWithFormat:@ + "You should download the new version of NewsBlur.\n\nNew version: v%@\nYou have: v%@", + serveriPhoneVersion, + currentiPhoneVersion]; + UIAlertView *upgradeConfirm = [[UIAlertView alloc] initWithTitle:title + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Upgrade!", nil]; + [upgradeConfirm show]; + [upgradeConfirm setTag:2]; + } + + if (!self.inPullToRefresh_) { + [self refreshFeedList]; + } else { + self.inPullToRefresh_ = NO; + } + + // start up the first time user experience + if ([[results objectForKey:@"social_feeds"] count] == 0 && + [[[results objectForKey:@"feeds"] allKeys] count] == 0) { + [appDelegate showFirstTimeUser]; + return; + } + + BOOL hasFocusStory = NO; + for (id feedId in appDelegate.dictFeeds) { + NSDictionary *feed = [appDelegate.dictFeeds objectForKey:feedId]; + if ([[feed objectForKey:@"ps"] intValue] > 0) { + hasFocusStory = YES; + break; + } + } + + if (!hasFocusStory) { + [self.intelligenceControl removeSegmentAtIndex:2 animated:NO]; + [self.intelligenceControl setWidth:90 forSegmentAtIndex:0]; + [self.intelligenceControl setWidth:90 forSegmentAtIndex:1]; + } else { + UIImage *green = [UIImage imageNamed:@"green_focus.png"]; + if (self.intelligenceControl.numberOfSegments == 2) { + [self.intelligenceControl insertSegmentWithImage:green atIndex:2 animated:NO]; + [self.intelligenceControl setWidth:50 forSegmentAtIndex:0]; + [self.intelligenceControl setWidth:68 forSegmentAtIndex:1]; + [self.intelligenceControl setWidth:62 forSegmentAtIndex:2]; + } + } + + self.intelligenceControl.hidden = NO; +} + +- (void)showUserProfile { + appDelegate.activeUserProfileId = [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"user_id"]]; + appDelegate.activeUserProfileName = [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"username"]]; +// appDelegate.activeUserProfileName = @"You"; + [appDelegate showUserProfileModal:self.navigationItem.leftBarButtonItem]; +} + +- (IBAction)tapAddSite:(id)sender { + [appDelegate showAddSiteModal:sender]; +} + +- (void)showSettingsPopover:(id)sender { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController showFeedMenuPopover:sender]; + } else { + if (self.popoverController == nil) { + self.popoverController = [[WEPopoverController alloc] + initWithContentViewController:appDelegate.feedsMenuViewController]; + + self.popoverController.delegate = self; + } else { + [self.popoverController dismissPopoverAnimated:YES]; + self.popoverController = nil; + } + + if ([self.popoverController respondsToSelector:@selector(setContainerViewProperties:)]) { + [self.popoverController setContainerViewProperties:[self improvedContainerViewProperties]]; + } + [self.popoverController setPopoverContentSize:CGSizeMake(200, 86)]; + [self.popoverController presentPopoverFromBarButtonItem:self.navigationItem.rightBarButtonItem + permittedArrowDirections:UIPopoverArrowDirectionAny + animated:YES]; + } +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (alertView.tag == 2) { + if (buttonIndex == 0) { + return; + } else { + // this doesn't work in simulator!!! because simulator has no app store + NSURL *url = [NSURL URLWithString:@"http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=463981119&mt=8"]; + [[UIApplication sharedApplication] openURL:url]; + } + } +} + +- (void)switchSitesUnread { + NSDictionary *feed; + + NSInteger intelligenceLevel = [appDelegate selectedIntelligence]; + NSMutableArray *indexPaths = [NSMutableArray array]; + + // if show all sites, calculate feeds and mark visible + if (self.viewShowingAllFeeds) { + [self calculateFeedLocations:NO]; + } + + // NSLog(@"View showing all: %d and %@", self.viewShowingAllFeeds, self.stillVisibleFeeds); + + for (int s=0; s < [appDelegate.dictFoldersArray count]; s++) { + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:s]; + NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; + NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; + for (int f=0; f < [activeFolderFeeds count]; f++) { + int location = [[activeFolderFeeds objectAtIndex:f] intValue]; + id feedId = [originalFolder objectAtIndex:location]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:f inSection:s]; + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + if ([appDelegate isSocialFeed:feedIdStr]) { + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + + int maxScore = [NewsBlurViewController computeMaxScoreForFeed:feed]; + +// BOOL isUser = [[NSString stringWithFormat:@"%@", feedId] +// isEqualToString: +// [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"id"]]]; + + // if unread + if (!self.viewShowingAllFeeds) { + if (maxScore < intelligenceLevel) { + [indexPaths addObject:indexPath]; + } + } else if (self.viewShowingAllFeeds && ![self.stillVisibleFeeds objectForKey:feedIdStr]) { + if (maxScore < intelligenceLevel) { + [indexPaths addObject:indexPath]; + } + } + } + } + + // if show unreads, calculate feeds and mark visible + if (!self.viewShowingAllFeeds) { + [self calculateFeedLocations:YES]; + } + + [self.feedTitlesTable beginUpdates]; + if ([indexPaths count] > 0) { + if (self.viewShowingAllFeeds) { + [self.feedTitlesTable insertRowsAtIndexPaths:indexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } else { + + [self.feedTitlesTable deleteRowsAtIndexPaths:indexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } + } + [self.feedTitlesTable endUpdates]; + + CGPoint offset = CGPointMake(0, 0); + [self.feedTitlesTable setContentOffset:offset animated:YES]; + + // Forget still visible feeds, since they won't be populated when + // all feeds are showing, and shouldn't be populated after this + // hide/show runs. + self.stillVisibleFeeds = [NSMutableDictionary dictionary]; + [self redrawUnreadCounts]; +} + +#pragma mark - +#pragma mark Table View - Feed List + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + if (self.hasNoSites) { + return 2; + } + return [appDelegate.dictFoldersArray count]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [appDelegate.dictFoldersArray objectAtIndex:section]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (self.hasNoSites) { + return 1; + } + + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:section]; + return [[self.activeFeedLocations objectForKey:folderName] count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + // messaging when there are no sites + if (self.hasNoSites) { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EmptyCell"]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + } + cell.textLabel.font=[UIFont systemFontOfSize:14.0]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + if (indexPath.section == 0) { + cell.textLabel.text = @"Tap the settings to find friends."; + } else { + cell.textLabel.text = @"Tap + to add sites."; + } + + return cell; + } + + + NSDictionary *feed; + + NSString *CellIdentifier; + + if (indexPath.section == 0) { + CellIdentifier = @"BlurblogCellIdentifier"; + } else { + CellIdentifier = @"FeedCellIdentifier"; + } + + FeedTableCell *cell = (FeedTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[FeedTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; + cell.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + } + + + + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + NSArray *feeds = [appDelegate.dictFolders objectForKey:folderName]; + NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; + int location = [[activeFolderFeeds objectAtIndex:indexPath.row] intValue]; + id feedId = [feeds objectAtIndex:location]; + + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + BOOL isSocial = [appDelegate isSocialFeed:feedIdStr]; + + + + if (isSocial) { + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + cell.feedFavicon = [Utilities getImage:feedIdStr isSocial:YES]; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + cell.feedFavicon = [Utilities getImage:feedIdStr]; + } + cell.feedTitle = [feed objectForKey:@"feed_title"]; + cell.positiveCount = [[feed objectForKey:@"ps"] intValue]; + cell.neutralCount = [[feed objectForKey:@"nt"] intValue]; + cell.negativeCount = [[feed objectForKey:@"ng"] intValue]; + cell.isSocial = isSocial; + + return cell; +} + +- (void)tableView:(UITableView *)tableView + didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + + if (self.hasNoSites) { + return; + } + + // set the current row pointer + self.currentRowAtIndexPath = indexPath; + + NSDictionary *feed; + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + NSArray *feeds = [appDelegate.dictFolders objectForKey:folderName]; + NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; + int location = [[activeFolderFeeds objectAtIndex:indexPath.row] intValue]; + id feedId = [feeds objectAtIndex:location]; + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + + if ([appDelegate isSocialFeed:feedIdStr]) { + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + appDelegate.isSocialView = YES; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + appDelegate.isSocialView = NO; + } + + // If all feeds are already showing, no need to remember this one. + if (!self.viewShowingAllFeeds) { + [self.stillVisibleFeeds setObject:indexPath forKey:feedIdStr]; + } + + [appDelegate setActiveFeed:feed]; + [appDelegate setActiveFolder:folderName]; + appDelegate.readStories = [NSMutableArray array]; + appDelegate.isRiverView = NO; + appDelegate.isSocialRiverView = NO; + + [appDelegate loadFeedDetailView]; +} + +- (CGFloat)tableView:(UITableView *)tableView + heightForRowAtIndexPath:(NSIndexPath *)indexPath { + + if (self.hasNoSites) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return kBlurblogTableViewRowHeight; + } else { + return kPhoneBlurblogTableViewRowHeight; + } + } + + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + + if ([folderName isEqualToString:@""]) { // blurblogs + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return kBlurblogTableViewRowHeight; + } else { + return kPhoneBlurblogTableViewRowHeight; + } + } else { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return kTableViewRowHeight; + } else { + return kPhoneTableViewRowHeight; + } + } +} + +- (UIView *)tableView:(UITableView *)tableView + viewForHeaderInSection:(NSInteger)section { + + int headerLabelHeight, folderImageViewY, disclosureImageViewY; + +// if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + headerLabelHeight = 27; + folderImageViewY = 3; + disclosureImageViewY = 7; +// } else { +// headerLabelHeight = 20; +// folderImageViewY = 0; +// disclosureImageViewY = 4; +// } + + // create the parent view that will hold header Label + UIControl* customView = [[UIControl alloc] + initWithFrame:CGRectMake(0.0, 0.0, + tableView.bounds.size.width, headerLabelHeight + 1)]; + UIView *borderTop = [[UIView alloc] + initWithFrame:CGRectMake(0.0, 0, + tableView.bounds.size.width, 1.0)]; + borderTop.backgroundColor = UIColorFromRGB(0xe0e0e0); + borderTop.opaque = NO; + [customView addSubview:borderTop]; + + + UIView *borderBottom = [[UIView alloc] + initWithFrame:CGRectMake(0.0, headerLabelHeight, + tableView.bounds.size.width, 1.0)]; + borderBottom.backgroundColor = [UIColorFromRGB(0xB7BDC6) colorWithAlphaComponent:0.5]; + borderBottom.opaque = NO; + [customView addSubview:borderBottom]; + + UILabel * headerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + customView.opaque = NO; + headerLabel.backgroundColor = [UIColor clearColor]; + headerLabel.opaque = NO; + headerLabel.textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0]; + headerLabel.highlightedTextColor = [UIColor whiteColor]; + headerLabel.font = [UIFont boldSystemFontOfSize:11]; + headerLabel.frame = CGRectMake(36.0, 1.0, 286.0, headerLabelHeight); + headerLabel.shadowColor = [UIColor colorWithRed:.94 green:0.94 blue:0.97 alpha:1.0]; + headerLabel.shadowOffset = CGSizeMake(0.0, 1.0); + if (section == 0) { + headerLabel.text = @"ALL BLURBLOG STORIES"; +// customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) +// colorWithAlphaComponent:0.8]; + } else if (section == 1) { + headerLabel.text = @"ALL STORIES"; +// customView.backgroundColor = [UIColorFromRGB(0xE6DDD7) +// colorWithAlphaComponent:0.8]; + } else { + headerLabel.text = [[appDelegate.dictFoldersArray objectAtIndex:section] uppercaseString]; +// customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) +// colorWithAlphaComponent:0.8]; + } + + customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) + colorWithAlphaComponent:0.8]; + [customView addSubview:headerLabel]; + + UIImage *folderImage; + int folderImageViewX = 10; + + if (section == 0) { + folderImage = [UIImage imageNamed:@"group.png"]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + folderImageViewX = 10; + } else { + folderImageViewX = 8; + } + } else if (section == 1) { + folderImage = [UIImage imageNamed:@"archive.png"]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + folderImageViewX = 10; + } else { + folderImageViewX = 7; + } + } else { + folderImage = [UIImage imageNamed:@"folder_2.png"]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + } else { + folderImageViewX = 7; + } + } + UIImageView *folderImageView = [[UIImageView alloc] initWithImage:folderImage]; + folderImageView.frame = CGRectMake(folderImageViewX, folderImageViewY, 20, 20); + [customView addSubview:folderImageView]; + + if (!self.hasNoSites) { + UIImage *disclosureImage = [UIImage imageNamed:@"disclosure.png"]; + UIImageView *disclosureImageView = [[UIImageView alloc] initWithImage:disclosureImage]; + disclosureImageView.frame = CGRectMake(customView.frame.size.width - 20, disclosureImageViewY, 9.0, 14.0); + [customView addSubview:disclosureImageView]; + } + + UIButton *invisibleHeaderButton = [UIButton buttonWithType:UIButtonTypeCustom]; + invisibleHeaderButton.frame = CGRectMake(0, 0, customView.frame.size.width, customView.frame.size.height); + invisibleHeaderButton.alpha = .1; + invisibleHeaderButton.tag = section; + [invisibleHeaderButton addTarget:self action:@selector(didSelectSectionHeader:) forControlEvents:UIControlEventTouchUpInside]; + [customView addSubview:invisibleHeaderButton]; + + [invisibleHeaderButton addTarget:self action:@selector(sectionTapped:) forControlEvents:UIControlEventTouchDown]; + [invisibleHeaderButton addTarget:self action:@selector(sectionUntapped:) forControlEvents:UIControlEventTouchUpInside]; + [invisibleHeaderButton addTarget:self action:@selector(sectionUntappedOutside:) forControlEvents:UIControlEventTouchUpOutside]; + + [customView setAutoresizingMask:UIViewAutoresizingNone]; + return customView; +} + +- (IBAction)sectionTapped:(UIButton *)button { + button.backgroundColor =[UIColor colorWithRed:0.15 green:0.55 blue:0.95 alpha:1.0]; +} + +- (IBAction)sectionUntapped:(UIButton *)button { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.15 * NSEC_PER_SEC), + dispatch_get_current_queue(), ^{ + button.backgroundColor = [UIColor clearColor]; + }); +} + +- (IBAction)sectionUntappedOutside:(UIButton *)button { + button.backgroundColor = [UIColor clearColor]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { +// NSString *folder = [appDelegate.dictFoldersArray objectAtIndex:section]; +// if ([[folder stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) { +// return 0; +// } + + if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0 && + section != 1) { + return 0; + } + + return 28; +} + +- (void)didSelectSectionHeader:(UIButton *)button { + // reset pointer to the cells + self.currentRowAtIndexPath = nil; + + appDelegate.readStories = [NSMutableArray array]; + + NSMutableArray *feeds = [NSMutableArray array]; + + if (button.tag == 0) { + appDelegate.isSocialRiverView = YES; + appDelegate.isRiverView = YES; + // add all the feeds from every NON blurblog folder + [appDelegate setActiveFolder:@"All Blurblog Stories"]; + for (NSString *folderName in self.activeFeedLocations) { + if ([folderName isEqualToString:@""]) { // remove all blurblugs which is a blank folder name + NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; + NSArray *folderFeeds = [self.activeFeedLocations objectForKey:folderName]; + for (int l=0; l < [folderFeeds count]; l++) { + [feeds addObject:[originalFolder objectAtIndex:[[folderFeeds objectAtIndex:l] intValue]]]; + } + } + } + } else if (button.tag == 1) { + appDelegate.isSocialRiverView = NO; + appDelegate.isRiverView = YES; + // add all the feeds from every NON blurblog folder + [appDelegate setActiveFolder:@"All Stories"]; + for (NSString *folderName in self.activeFeedLocations) { + if (![folderName isEqualToString:@""]) { // remove all blurblugs which is a blank folder name + NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; + NSArray *folderFeeds = [self.activeFeedLocations objectForKey:folderName]; + for (int l=0; l < [folderFeeds count]; l++) { + [feeds addObject:[originalFolder objectAtIndex:[[folderFeeds objectAtIndex:l] intValue]]]; + } + } + } + } else { + appDelegate.isSocialRiverView = NO; + appDelegate.isRiverView = YES; + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:button.tag]; + + [appDelegate setActiveFolder:folderName]; + NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; + NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; + for (int l=0; l < [activeFolderFeeds count]; l++) { + [feeds addObject:[originalFolder objectAtIndex:[[activeFolderFeeds objectAtIndex:l] intValue]]]; + } + + } + appDelegate.activeFolderFeeds = feeds; + + [appDelegate loadRiverFeedDetailView]; +} + +- (void)changeToAllMode { + [self.intelligenceControl setSelectedSegmentIndex:0]; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + [userPreferences setInteger:-1 forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; +} + +- (IBAction)selectIntelligence { + [MBProgressHUD hideHUDForView:self.feedTitlesTable animated:NO]; + MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.innerView animated:YES]; + hud.mode = MBProgressHUDModeText; + hud.removeFromSuperViewOnHide = YES; + + int selectedSegmentIndex = [self.intelligenceControl selectedSegmentIndex]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if (selectedSegmentIndex == 0) { + hud.labelText = @"All Stories"; + [userPreferences setInteger:-1 forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; + + if (appDelegate.selectedIntelligence != 0) { + int previousLevel = appDelegate.selectedIntelligence; + [appDelegate setSelectedIntelligence:0]; + [self updateFeedsWithIntelligence:previousLevel newLevel:0]; + [self redrawUnreadCounts]; + } + self.viewShowingAllFeeds = YES; + [self switchSitesUnread]; + } else if(selectedSegmentIndex == 1) { +// NSString *unreadStr = [NSString stringWithFormat:@"%i Unread Stories", appDelegate.allUnreadCount]; + hud.labelText = @"Unread Stories"; + [userPreferences setInteger:0 forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; + + if (appDelegate.selectedIntelligence != 0) { + int previousLevel = appDelegate.selectedIntelligence; + [appDelegate setSelectedIntelligence:0]; + [self updateFeedsWithIntelligence:previousLevel newLevel:0]; + [self redrawUnreadCounts]; + } + self.viewShowingAllFeeds = NO; + [self switchSitesUnread]; + } else { + hud.labelText = @"Focus Stories"; + [userPreferences setInteger:1 forKey:@"selectedIntelligence"]; + [userPreferences synchronize]; + + if (self.viewShowingAllFeeds == YES) { + self.viewShowingAllFeeds = NO; + [self switchSitesUnread]; + } + [appDelegate setSelectedIntelligence:1]; + [self updateFeedsWithIntelligence:0 newLevel:1]; + [self redrawUnreadCounts]; + } + + [hud hide:YES afterDelay:0.75]; + +// [self.feedTitlesTable reloadData]; +} + +- (void)updateFeedsWithIntelligence:(int)previousLevel newLevel:(int)newLevel { + NSMutableArray *insertIndexPaths = [NSMutableArray array]; + NSMutableArray *deleteIndexPaths = [NSMutableArray array]; + NSMutableDictionary *addToVisibleFeeds = [NSMutableDictionary dictionary]; + + if (newLevel <= previousLevel) { + [self calculateFeedLocations:NO]; + } + + for (int s=0; s < [appDelegate.dictFoldersArray count]; s++) { + NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:s]; + NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; + NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; + +// if (s == 9) { +// NSLog(@"Section %d: %@. %d to %d", s, folderName, previousLevel, newLevel); +// } + + for (int f=0; f < [originalFolder count]; f++) { + NSNumber *feedId = [originalFolder objectAtIndex:f]; + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + NSDictionary *feed; + +// BOOL isUser = [feedIdStr isEqualToString: +// [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"id"]]]; + + if ([appDelegate isSocialFeed:feedIdStr]) { + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + int maxScore = [NewsBlurViewController computeMaxScoreForFeed:feed]; + +// if (s == 9) { +// NSLog(@"MaxScore: %d for %@ (%@/%@/%@). Visible: %@", maxScore, +// [feed objectForKey:@"feed_title"], +// [feed objectForKey:@"ng"], [feed objectForKey:@"nt"], [feed objectForKey:@"ng"], +// [self.visibleFeeds objectForKey:feedIdStr]); +// } + + if ([self.visibleFeeds objectForKey:feedIdStr]) { + if (maxScore < newLevel) { + for (int l=0; l < [activeFolderFeeds count]; l++) { + if ([originalFolder objectAtIndex:[[activeFolderFeeds objectAtIndex:l] intValue]] == feedId) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:l inSection:s]; + [deleteIndexPaths addObject:indexPath]; + if ([self.stillVisibleFeeds objectForKey:feedIdStr]) { + [self.stillVisibleFeeds removeObjectForKey:feedIdStr]; + } + break; + } + } + } + } else { + if (maxScore >= newLevel) { + for (int l=0; l < [activeFolderFeeds count]; l++) { + if ([originalFolder objectAtIndex:[[activeFolderFeeds objectAtIndex:l] intValue]] == feedId) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:l inSection:s]; + [addToVisibleFeeds setObject:[NSNumber numberWithBool:YES] forKey:feedIdStr]; + [insertIndexPaths addObject:indexPath]; + break; + } + } + } + + } + } + } + + for (id feedIdStr in addToVisibleFeeds) { + [self.visibleFeeds setObject:[addToVisibleFeeds objectForKey:feedIdStr] forKey:feedIdStr]; + } + + for (id feedIdStr in [self.stillVisibleFeeds allKeys]) { + NSDictionary *feed; + if ([appDelegate isSocialFeed:feedIdStr]) { + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + + int maxScore = [NewsBlurViewController computeMaxScoreForFeed:feed]; + if (previousLevel != newLevel && maxScore < newLevel) { + [deleteIndexPaths addObject:[self.stillVisibleFeeds objectForKey:feedIdStr]]; + [self.stillVisibleFeeds removeObjectForKey:feedIdStr]; + [self.visibleFeeds removeObjectForKey:feedIdStr]; + } + } + + if (newLevel > previousLevel) { + [self calculateFeedLocations:NO]; + } + + [self.feedTitlesTable beginUpdates]; + if ([deleteIndexPaths count] > 0) { + [self.feedTitlesTable deleteRowsAtIndexPaths:deleteIndexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } + if ([insertIndexPaths count] > 0) { + [self.feedTitlesTable insertRowsAtIndexPaths:insertIndexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } + [self.feedTitlesTable endUpdates]; + + // scrolls to the top and fixes header rendering bug + CGPoint offsetOne = CGPointMake(0, 1); + CGPoint offset = CGPointMake(0, 0); + [self.feedTitlesTable setContentOffset:offsetOne animated:NO]; + [self.feedTitlesTable setContentOffset:offset animated:NO]; + + [self calculateFeedLocations:YES]; +} + +- (void)redrawUnreadCounts { + for (UITableViewCell *cell in self.feedTitlesTable.visibleCells) { + [cell setNeedsDisplay]; + } +} + +- (void)calculateFeedLocations:(BOOL)markVisible { + NSDictionary *feed; + self.activeFeedLocations = [NSMutableDictionary dictionary]; + if (markVisible) { + self.visibleFeeds = [NSMutableDictionary dictionary]; + } + for (NSString *folderName in appDelegate.dictFoldersArray) { + NSArray *folder = [appDelegate.dictFolders objectForKey:folderName]; + NSMutableArray *feedLocations = [NSMutableArray array]; + for (int f = 0; f < [folder count]; f++) { + id feedId = [folder objectAtIndex:f]; + NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; + + if ([folderName isEqualToString:@""]){ + feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; + } else { + feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; + } + +// BOOL isUser = [[NSString stringWithFormat:@"%@", feedId] +// isEqualToString: +// [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"id"]]]; + + if (self.viewShowingAllFeeds) { + NSNumber *location = [NSNumber numberWithInt:f]; + [feedLocations addObject:location]; + } else { + int maxScore = [NewsBlurViewController computeMaxScoreForFeed:feed]; +// if ([folderName isEqualToString:@""]){ +// NSLog(@"Computing score for %@: %d in %d (markVisible: %d)", +// [feed objectForKey:@"feed_title"], maxScore, appDelegate.selectedIntelligence, markVisible); +// } + + if (maxScore >= appDelegate.selectedIntelligence) { + NSNumber *location = [NSNumber numberWithInt:f]; + [feedLocations addObject:location]; + if (markVisible) { + [self.visibleFeeds setObject:[NSNumber numberWithBool:YES] forKey:feedIdStr]; + } + } + } + + } + if ([folderName isEqualToString:@""]){ +// NSLog(@"feedLocations count is %i: ", [feedLocations count]); + } +// NSLog(@"feedLocations %@", feedLocations); + [self.activeFeedLocations setObject:feedLocations forKey:folderName]; + + } +// NSLog(@"Active feed locations %@", self.activeFeedLocations); +} + ++ (int)computeMaxScoreForFeed:(NSDictionary *)feed { + int maxScore = -2; + if ([[feed objectForKey:@"ng"] intValue] > 0) maxScore = -1; + if ([[feed objectForKey:@"nt"] intValue] > 0) maxScore = 0; + if ([[feed objectForKey:@"ps"] intValue] > 0) maxScore = 1; + return maxScore; +} + +#pragma mark - +#pragma mark Favicons + + +- (void)loadFavicons { + NSString *urlString = [NSString stringWithFormat:@"http://%@/reader/favicons", + NEWSBLUR_URL]; + NSURL *url = [NSURL URLWithString:urlString]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + + [request setDidFinishSelector:@selector(saveAndDrawFavicons:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setDelegate:self]; + [request startAsynchronous]; +} + +- (void)loadAvatars { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); + dispatch_async(queue, ^{ + for (NSString *feed_id in [appDelegate.dictSocialFeeds allKeys]) { + NSDictionary *feed = [appDelegate.dictSocialFeeds objectForKey:feed_id]; + NSURL *imageURL = [NSURL URLWithString:[feed objectForKey:@"photo_url"]]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + UIImage *faviconImage = [UIImage imageWithData:imageData]; + + faviconImage = [Utilities roundCorneredImage:faviconImage radius:6]; + + [Utilities saveImage:faviconImage feedId:feed_id]; + } + + [Utilities saveimagesToDisk]; + + dispatch_sync(dispatch_get_main_queue(), ^{ + [self.feedTitlesTable reloadData]; + }); + }); +} + + + +- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); + dispatch_async(queue, ^{ + for (id feed_id in results) { + NSMutableDictionary *feed = [[appDelegate.dictFeeds objectForKey:feed_id] mutableCopy]; + [feed setValue:[results objectForKey:feed_id] forKey:@"favicon"]; + [appDelegate.dictFeeds setValue:feed forKey:feed_id]; + + NSString *favicon = [feed objectForKey:@"favicon"]; + if ((NSNull *)favicon != [NSNull null] && [favicon length] > 0) { + NSData *imageData = [NSData dataWithBase64EncodedString:favicon]; + UIImage *faviconImage = [UIImage imageWithData:imageData]; + [Utilities saveImage:faviconImage feedId:feed_id]; + } + } + [Utilities saveimagesToDisk]; + + dispatch_sync(dispatch_get_main_queue(), ^{ + [self.feedTitlesTable reloadData]; + }); + }); + +} + +- (void)requestFailed:(ASIHTTPRequest *)request { + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +#pragma mark - +#pragma mark PullToRefresh + +// called when the user pulls-to-refresh +- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view { + self.inPullToRefresh_ = YES; + [self fetchFeedList:NO]; +} + +- (void)refreshFeedList { + // refresh the feed + NSURL *urlFeedList = [NSURL URLWithString: + [NSString stringWithFormat:@"http://%@/reader/refresh_feeds", + NEWSBLUR_URL]]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:urlFeedList]; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] + setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; + [request setDelegate:self]; + [request setResponseEncoding:NSUTF8StringEncoding]; + [request setDefaultResponseEncoding:NSUTF8StringEncoding]; + [request setDidFinishSelector:@selector(finishRefreshingFeedList:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request setTimeOutSeconds:30]; + [request startAsynchronous]; +} + +- (void)finishRefreshingFeedList:(ASIHTTPRequest *)request { + if ([request responseStatusCode] == 403) { + return [appDelegate showLogin]; + } else if ([request responseStatusCode] >= 500) { + [pull finishedLoading]; + return [self informError:@"The server barfed!"]; + } + + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + NSMutableDictionary *updatedDictFeeds = [appDelegate.dictFeeds mutableCopy]; + NSDictionary *newFeedCounts = [results objectForKey:@"feeds"]; + for (id feed in newFeedCounts) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", feed]; + NSMutableDictionary *newFeed = [[appDelegate.dictFeeds objectForKey:feedIdStr] mutableCopy]; + NSMutableDictionary *newFeedCount = [newFeedCounts objectForKey:feed]; + + if ([newFeed isKindOfClass:[NSDictionary class]]) { + + [newFeed setObject:[newFeedCount objectForKey:@"ng"] forKey:@"ng"]; + [newFeed setObject:[newFeedCount objectForKey:@"nt"] forKey:@"nt"]; + [newFeed setObject:[newFeedCount objectForKey:@"ps"] forKey:@"ps"]; + [updatedDictFeeds setObject:newFeed forKey:feedIdStr]; + } + } + + NSMutableDictionary *updatedDictSocialFeeds = [appDelegate.dictSocialFeeds mutableCopy]; + NSDictionary *newSocialFeedCounts = [results objectForKey:@"social_feeds"]; + for (id feed in newSocialFeedCounts) { + NSString *feedIdStr = [NSString stringWithFormat:@"%@", feed]; + NSMutableDictionary *newFeed = [[appDelegate.dictSocialFeeds objectForKey:feedIdStr] mutableCopy]; + NSMutableDictionary *newFeedCount = [newSocialFeedCounts objectForKey:feed]; + + if ([newFeed isKindOfClass:[NSDictionary class]]) { + [newFeed setObject:[newFeedCount objectForKey:@"ng"] forKey:@"ng"]; + [newFeed setObject:[newFeedCount objectForKey:@"nt"] forKey:@"nt"]; + [newFeed setObject:[newFeedCount objectForKey:@"ps"] forKey:@"ps"]; + [updatedDictSocialFeeds setObject:newFeed forKey:feedIdStr]; + } + } + + appDelegate.dictSocialFeeds = updatedDictSocialFeeds; + appDelegate.dictFeeds = updatedDictFeeds; + [self.feedTitlesTable reloadData]; +} + +// called when the date shown needs to be updated, optional +- (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view { + return self.lastUpdate; +} + +#pragma mark - +#pragma mark WEPopoverControllerDelegate implementation + +- (void)popoverControllerDidDismissPopover:(WEPopoverController *)thePopoverController { + //Safe to release the popover here + self.popoverController = nil; +} + +- (BOOL)popoverControllerShouldDismissPopover:(WEPopoverController *)thePopoverController { + //The popover is automatically dismissed if you click outside it, unless you return NO here + return YES; +} + + +/** + Thanks to Paul Solt for supplying these background images and container view properties + */ +- (WEPopoverContainerViewProperties *)improvedContainerViewProperties { + + WEPopoverContainerViewProperties *props = [WEPopoverContainerViewProperties alloc]; + NSString *bgImageName = nil; + CGFloat bgMargin = 0.0; + CGFloat bgCapSize = 0.0; + CGFloat contentMargin = 5.0; + + bgImageName = @"popoverBg.png"; + + // These constants are determined by the popoverBg.png image file and are image dependent + bgMargin = 13; // margin width of 13 pixels on all sides popoverBg.png (62 pixels wide - 36 pixel background) / 2 == 26 / 2 == 13 + bgCapSize = 31; // ImageSize/2 == 62 / 2 == 31 pixels + + props.leftBgMargin = bgMargin; + props.rightBgMargin = bgMargin; + props.topBgMargin = bgMargin; + props.bottomBgMargin = bgMargin; + props.leftBgCapSize = bgCapSize; + props.topBgCapSize = bgCapSize; + props.bgImageName = bgImageName; + props.leftContentMargin = contentMargin; + props.rightContentMargin = contentMargin - 1; // Need to shift one pixel for border to look correct + props.topContentMargin = contentMargin; + props.bottomContentMargin = contentMargin; + + props.arrowMargin = 4.0; + + props.upArrowImageName = @"popoverArrowUp.png"; + props.downArrowImageName = @"popoverArrowDown.png"; + props.leftArrowImageName = @"popoverArrowLeft.png"; + props.rightArrowImageName = @"popoverArrowRight.png"; + return props; +} + +- (void)resetToolbar { + self.navigationItem.leftBarButtonItem = nil; + self.navigationItem.titleView = nil; + self.navigationItem.rightBarButtonItem = nil; +} + + +@end \ No newline at end of file diff --git a/media/iphone/Classes/OriginalStoryViewController.h b/media/ios/Classes/OriginalStoryViewController.h similarity index 65% rename from media/iphone/Classes/OriginalStoryViewController.h rename to media/ios/Classes/OriginalStoryViewController.h index 4da20afcd..b59900c64 100644 --- a/media/iphone/Classes/OriginalStoryViewController.h +++ b/media/ios/Classes/OriginalStoryViewController.h @@ -36,16 +36,16 @@ static const CGFloat kButtonWidth = 48.0f; UIToolbar *toolbar; } -@property (nonatomic, retain) IBOutlet NewsBlurAppDelegate *appDelegate; -@property (nonatomic, retain) IBOutlet UIBarButtonItem *closeButton; -@property (nonatomic, retain) IBOutlet UIWebView *webView; -@property (nonatomic, retain) IBOutlet UIBarButtonItem* back; -@property (nonatomic, retain) IBOutlet UIBarButtonItem* forward; -@property (nonatomic, retain) IBOutlet UIBarButtonItem* refresh; -@property (nonatomic, retain) IBOutlet UIBarButtonItem* pageAction; -@property (nonatomic, retain) IBOutlet UILabel *pageTitle; -@property (nonatomic, retain) IBOutlet UITextField *pageUrl; -@property (nonatomic, retain) IBOutlet UIToolbar *toolbar; +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIBarButtonItem *closeButton; +@property (nonatomic) IBOutlet UIWebView *webView; +@property (nonatomic) IBOutlet UIBarButtonItem* back; +@property (nonatomic) IBOutlet UIBarButtonItem* forward; +@property (nonatomic) IBOutlet UIBarButtonItem* refresh; +@property (nonatomic) IBOutlet UIBarButtonItem* pageAction; +@property (nonatomic) IBOutlet UILabel *pageTitle; +@property (nonatomic) IBOutlet UITextField *pageUrl; +@property (nonatomic) IBOutlet UIToolbar *toolbar; - (IBAction) doCloseOriginalStoryViewController; - (IBAction) doOpenActionSheet; diff --git a/media/iphone/Classes/OriginalStoryViewController.m b/media/ios/Classes/OriginalStoryViewController.m similarity index 94% rename from media/iphone/Classes/OriginalStoryViewController.m rename to media/ios/Classes/OriginalStoryViewController.m index 904d92645..0e9c270d8 100644 --- a/media/iphone/Classes/OriginalStoryViewController.m +++ b/media/ios/Classes/OriginalStoryViewController.m @@ -11,6 +11,7 @@ #import "NSString+HTML.h" #import "TransparentToolbar.h" #import "SHK.h" +#import "MBProgressHUD.h" @implementation OriginalStoryViewController @@ -34,12 +35,16 @@ - (void)viewWillAppear:(BOOL)animated { // NSLog(@"Original Story View: %@", [appDelegate activeOriginalStoryURL]); - [appDelegate showNavigationBar:NO]; + toolbar.tintColor = UIColorFromRGB(0x183353); NSURLRequest *request = [[NSURLRequest alloc] initWithURL:appDelegate.activeOriginalStoryURL] ; [self updateAddress:request]; [self.pageTitle setText:[[appDelegate activeStory] objectForKey:@"story_title"]]; - [request release]; + + [MBProgressHUD hideHUDForView:self.webView animated:YES]; + MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.webView animated:YES]; + HUD.labelText = @"On its way..."; + [HUD hide:YES afterDelay:2]; } - (void)viewWillDisappear:(BOOL)animated { @@ -48,8 +53,11 @@ } } +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + - (void)viewDidLoad { - CGRect navBarFrame = self.view.bounds; navBarFrame.size.height = kNavBarHeight; UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:navBarFrame]; @@ -69,7 +77,6 @@ label.text = [[appDelegate activeStory] objectForKey:@"story_title"]; [navBar addSubview:label]; self.pageTitle = label; - [label release]; UIBarButtonItem *close = [[UIBarButtonItem alloc] @@ -87,8 +94,6 @@ [tools setItems:[NSArray arrayWithObject:close] animated:NO]; [tools setTintColor:UIColorFromRGB(0x183353)]; [navBar addSubview:tools]; - [close release]; - [tools release]; CGRect addressFrame = CGRectMake(closeButtonFrame.origin.x + @@ -123,11 +128,11 @@ navBar.tintColor = UIColorFromRGB(0x183353); - [navBar release]; - [address release]; } + + - (BOOL)textFieldShouldReturn:(UITextField *)textField { [self loadAddress:nil]; return YES; @@ -156,6 +161,8 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + + if ([[[request URL] scheme] isEqual:@"mailto"]) { [[UIApplication sharedApplication] openURL:[request URL]]; return NO; @@ -163,6 +170,11 @@ [self updateAddress:request]; return NO; } + + + NSURL* mainUrl = [request mainDocumentURL]; + NSString* absoluteString = [mainUrl absoluteString]; + self.pageUrl.text = absoluteString; return YES; } @@ -171,12 +183,15 @@ [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; [self updateButtons]; } + - (void)webViewDidFinishLoad:(UIWebView *)aWebView { + [MBProgressHUD hideHUDForView:self.webView animated:YES]; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; [self updateButtons]; [self updateTitle:aWebView]; } + - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; @@ -187,17 +202,20 @@ [self informError:error]; } } + - (void)updateTitle:(UIWebView*)aWebView { NSString *pageTitleValue = [aWebView stringByEvaluatingJavaScriptFromString:@"document.title"]; self.pageTitle.text = [pageTitleValue stringByDecodingHTMLEntities]; } + - (void)updateAddress:(NSURLRequest*)request { NSURL *url = [request URL]; self.pageUrl.text = [url absoluteString]; [self loadAddress:nil]; } + - (void)updateButtons { self.forward.enabled = self.webView.canGoForward; @@ -211,19 +229,6 @@ // Release any cached data, images, etc that aren't in use. } -- (void)dealloc { - [appDelegate release]; - [closeButton release]; - [webView release]; - [back release]; - [forward release]; - [refresh release]; - [pageAction release]; - [pageTitle release]; - [pageUrl release]; - [toolbar release]; - [super dealloc]; -} - (IBAction)doCloseOriginalStoryViewController { // NSLog(@"Close Original Story: %@", appDelegate); diff --git a/media/ios/Classes/ProfileBadge.h b/media/ios/Classes/ProfileBadge.h new file mode 100644 index 000000000..644d5bc10 --- /dev/null +++ b/media/ios/Classes/ProfileBadge.h @@ -0,0 +1,48 @@ +// +// ProfileBadge.h +// NewsBlur +// +// Created by Roy Yang on 7/2/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface ProfileBadge : UITableViewCell { + NewsBlurAppDelegate *appDelegate; + + UIImageView *UserAvatar; + UILabel *username; + UILabel *userLocation; + UILabel *userDescription; + UILabel *userStats; + UIButton *followButton; + NSDictionary *activeProfile; + + UIActivityIndicatorView *activityIndicator; +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property ( nonatomic) UIImageView *userAvatar; +@property ( nonatomic) UILabel *username; +@property ( nonatomic) UILabel *userLocation; +@property ( nonatomic) UILabel *userDescription; +@property ( nonatomic) UILabel *userStats; +@property ( nonatomic) UIButton *followButton; +@property (nonatomic) UIActivityIndicatorView *activityIndicator; + +@property ( nonatomic) NSDictionary *activeProfile; + + +- (void)refreshWithProfile:(NSDictionary *)profile showStats:(BOOL)showStats withWidth:(int)newWidth; + +- (IBAction)doFollowButton:(id)sender; +- (void)finishFollowing:(ASIHTTPRequest *)request; +- (void)finishUnfollowing:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)initProfile; + +@end diff --git a/media/ios/Classes/ProfileBadge.m b/media/ios/Classes/ProfileBadge.m new file mode 100644 index 000000000..656817edb --- /dev/null +++ b/media/ios/Classes/ProfileBadge.m @@ -0,0 +1,405 @@ +// +// ProfileBadge.m +// NewsBlur +// +// Created by Roy Yang on 7/2/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ProfileBadge.h" +#import "NewsBlurAppDelegate.h" +#import "Utilities.h" +#import "ASIHTTPRequest.h" +#import "UIImageView+AFNetworking.h" +#import + +#define kTopBadgeHeight 125 +#define kTopBadgeTextXCoordinate 100 +#define kFollowColor 0x0a6720 +#define kFollowTextColor 0xffffff +#define kFollowingColor 0xcccccc +#define kFollowingTextColor 0x333333 + +@interface ProfileBadge () + +@property (readwrite) int moduleWidth; +@property (readwrite) BOOL shouldShowStats; + +@end + +@implementation ProfileBadge + +@synthesize shouldShowStats; +@synthesize appDelegate; +@synthesize userAvatar; +@synthesize username; +@synthesize userLocation; +@synthesize userDescription; +@synthesize userStats; +@synthesize followButton; +@synthesize activeProfile; +@synthesize activityIndicator; +@synthesize moduleWidth; + + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + userAvatar = nil; + username = nil; + userLocation = nil; + userDescription = nil; + userStats = nil; + followButton = nil; + activeProfile = nil; + activityIndicator = nil; + } + + return self; +} + + +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect +{ + // Drawing code +} +*/ + +- (void)layoutSubviews { + [super layoutSubviews]; +} + +- (void)refreshWithProfile:(NSDictionary *)profile showStats:(BOOL)showStats withWidth:(int)newWidth { + int width; + + if (newWidth) { + width = newWidth; + self.moduleWidth = newWidth; + } else { + width = self.moduleWidth; + for (UIView *subview in [self.contentView subviews]) { + [subview removeFromSuperview]; + } + } + + if (showStats) { + shouldShowStats = showStats; + } + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + self.activeProfile = profile; + int yCoordinatePointer = 0; + + // AVATAR + NSString *photo_url = [profile objectForKey:@"photo_url"]; + + if ([photo_url rangeOfString:@"graph.facebook.com"].location != NSNotFound) { + photo_url = [photo_url stringByAppendingFormat:@"?type=large"]; + } + + if ([photo_url rangeOfString:@"twimg"].location != NSNotFound) { + photo_url = [photo_url stringByReplacingOccurrencesOfString:@"_normal" withString:@""]; + } + +// UIImage *placeholder = [UIImage imageNamed:@"user_light"]; + UIImageView *avatar = [[UIImageView alloc] init]; + avatar.frame = CGRectMake(10, 10, 80, 80); + + [avatar setImageWithURL:[NSURL URLWithString:photo_url] + placeholderImage:nil]; + + // scale and crop image + [avatar setContentMode:UIViewContentModeScaleAspectFill]; + [avatar setClipsToBounds:YES]; + [self.contentView addSubview:avatar]; + + // username + UILabel *user = [[UILabel alloc] initWithFrame:CGRectZero]; + user.textColor = UIColorFromRGB(NEWSBLUR_LINK_COLOR); + user.font = [UIFont fontWithName:@"Helvetica-Bold" size:18]; + user.backgroundColor = [UIColor clearColor]; + self.username = user; + self.username.frame = CGRectMake(kTopBadgeTextXCoordinate, 10, width - kTopBadgeTextXCoordinate - 10, 22); + self.username.text = [profile objectForKey:@"username"]; + [self.contentView addSubview:username]; + + // FOLLOW BUTTON + UIButton *follow = [UIButton buttonWithType:UIButtonTypeCustom]; + follow.frame = CGRectMake(10, 96, 80, 24); + + follow.layer.borderColor = [UIColor grayColor].CGColor; + follow.layer.borderWidth = 0.5f; + follow.layer.cornerRadius = 10.0f; + + [follow setTitleColor:UIColorFromRGB(kFollowTextColor) forState:UIControlStateNormal]; + follow.backgroundColor = UIColorFromRGB(kFollowColor); + + // check follow button status + if ([[profile objectForKey:@"yourself"] intValue]) { + [follow setTitle:@"You" forState:UIControlStateNormal]; + follow.enabled = NO; + } else if ([[profile objectForKey:@"followed_by_you"] intValue]) { + [follow setTitle:@"Following" forState:UIControlStateNormal]; + follow.backgroundColor = UIColorFromRGB(kFollowingColor); + [follow setTitleColor:UIColorFromRGB(kFollowingTextColor) forState:UIControlStateNormal]; + } else { + [follow setTitle:@"Follow" forState:UIControlStateNormal]; + } + + follow.titleLabel.font = [UIFont systemFontOfSize:12]; + [follow addTarget:self + action:@selector(doFollowButton:) + forControlEvents:UIControlEventTouchUpInside]; + + self.followButton = follow; + [self.contentView addSubview:self.followButton]; + + // ACTIVITY INDICATOR + UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + activityView.frame = CGRectMake(40, 98, 20, 20.0); + self.activityIndicator = activityView; + + [self.contentView addSubview:self.activityIndicator]; + + yCoordinatePointer = self.username.frame.origin.y + self.username.frame.size.height; + + // BIO + if ([profile objectForKey:@"bio"] != [NSNull null]) { + UILabel *bio = [[UILabel alloc] + initWithFrame:CGRectMake(kTopBadgeTextXCoordinate, + yCoordinatePointer, + width - kTopBadgeTextXCoordinate - 10, + 60)]; + bio.text = [profile objectForKey:@"bio"]; + bio.textColor = UIColorFromRGB(0x333333); + bio.font = [UIFont fontWithName:@"Helvetica" size:12]; + bio.lineBreakMode = UILineBreakModeTailTruncation; + bio.numberOfLines = 5; + bio.backgroundColor = [UIColor clearColor]; + + // Calculate the expected size based on the font and linebreak mode of your label + CGSize maximumLabelSize = CGSizeMake(width - kTopBadgeTextXCoordinate - 10, 60); + CGSize expectedLabelSize = [bio.text + sizeWithFont:bio.font + constrainedToSize:maximumLabelSize + lineBreakMode:bio.lineBreakMode]; + CGRect newFrame = bio.frame; + newFrame.size.height = expectedLabelSize.height; + bio.frame = newFrame; + + self.userDescription = bio; + [self.contentView addSubview:self.userDescription]; + yCoordinatePointer = yCoordinatePointer + self.userDescription.frame.size.height + 6; + } + + // LOCATION + if ([profile objectForKey:@"location"] != [NSNull null] && + [[profile objectForKey:@"location"] length]) { + UILabel *location = [[UILabel alloc] + initWithFrame:CGRectMake(kTopBadgeTextXCoordinate + 16, + yCoordinatePointer, + width - kTopBadgeTextXCoordinate - 10 - 16, + 20)]; + location.text = [profile objectForKey:@"location"]; + location.textColor = UIColorFromRGB(0x666666); + location.backgroundColor = [UIColor clearColor]; + location.font = [UIFont fontWithName:@"Helvetica" size:12]; + self.userLocation = location; + [self.contentView addSubview:self.userLocation]; + + UIImage *locationIcon = [UIImage imageNamed:@"7-location-place.png"]; + UIImageView *locationIconView = [[UIImageView alloc] initWithImage:locationIcon]; + locationIconView.Frame = CGRectMake(kTopBadgeTextXCoordinate, + yCoordinatePointer + 2, + 16, + 16); + [self.contentView addSubview:locationIconView]; + } + + if (shouldShowStats) { + UIView *horizontalBar = [[UIView alloc] initWithFrame:CGRectMake(0, kTopBadgeHeight, width, 1)]; + horizontalBar.backgroundColor = [UIColor lightGrayColor]; + [self.contentView addSubview:horizontalBar]; + + UIView *leftVerticalBar = [[UIView alloc] initWithFrame:CGRectMake((width/3), kTopBadgeHeight, 1, 55)]; + leftVerticalBar.backgroundColor = [UIColor lightGrayColor]; + [self.contentView addSubview:leftVerticalBar]; + + UIView *rightVerticalBar = [[UIView alloc] initWithFrame:CGRectMake((width/3) * 2, kTopBadgeHeight, 1, 55)]; + rightVerticalBar.backgroundColor = [UIColor lightGrayColor]; + [self.contentView addSubview:rightVerticalBar]; + + // Shared + UILabel *shared = [[UILabel alloc] initWithFrame:CGRectMake(0, kTopBadgeHeight + 10, (width/3), 20)]; + NSString *sharedStr = [NSString stringWithFormat:@"%i", + [[profile objectForKey:@"shared_stories_count"] intValue]]; + shared.text = sharedStr; + shared.textAlignment = UITextAlignmentCenter; + shared.font = [UIFont boldSystemFontOfSize:20]; + shared.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:shared]; + + UILabel *sharedLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, kTopBadgeHeight + 30, (width/3), 20)]; + NSString *sharedLabelStr = [NSString stringWithFormat:@"Shared Stor%@", + [[profile objectForKey:@"shared_stories_count"] intValue] == 1 ? @"y" : @"ies"]; + sharedLabel.text = sharedLabelStr; + sharedLabel.textAlignment = UITextAlignmentCenter; + sharedLabel.font = [UIFont fontWithName:@"Helvetica" size:12]; + sharedLabel.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:sharedLabel]; + + + // Following + UILabel *following = [[UILabel alloc] initWithFrame:CGRectMake((width/3), kTopBadgeHeight + 10, (width/3), 20)]; + NSString *followingStr = [NSString stringWithFormat:@"%i", + [[profile objectForKey:@"following_count"] intValue]]; + following.text = followingStr; + following.textAlignment = UITextAlignmentCenter; + following.font = [UIFont boldSystemFontOfSize:20]; + following.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:following]; + + UILabel *followingLabel = [[UILabel alloc] initWithFrame:CGRectMake((width/3), kTopBadgeHeight + 30, (width/3), 20)]; + NSString *followingLabelStr = [NSString stringWithFormat:@"Following"]; + followingLabel.text = followingLabelStr; + followingLabel.textAlignment = UITextAlignmentCenter; + followingLabel.font = [UIFont fontWithName:@"Helvetica" size:12]; + followingLabel.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:followingLabel]; + + + // Followers + UILabel *followers = [[UILabel alloc] initWithFrame:CGRectMake((width/3) * 2, kTopBadgeHeight + 10, (width/3), 20)]; + NSString *followersStr = [NSString stringWithFormat:@"%i", + [[profile objectForKey:@"follower_count"] intValue]]; + followers.text = followersStr; + followers.textAlignment = UITextAlignmentCenter; + followers.font = [UIFont boldSystemFontOfSize:20]; + followers.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:followers]; + + UILabel *followersLabel = [[UILabel alloc] initWithFrame:CGRectMake((width/3) * 2, kTopBadgeHeight + 30, (width/3), 20)]; + NSString *followersLabelStr = [NSString stringWithFormat:@"Follower%@", + [[profile objectForKey:@"follower_count"] intValue] == 1 ? @"" : @"s"]; + followersLabel.text = followersLabelStr; + followersLabel.textAlignment = UITextAlignmentCenter; + followersLabel.font = [UIFont fontWithName:@"Helvetica" size:12]; + followersLabel.backgroundColor = [UIColor clearColor]; + [self.contentView addSubview:followersLabel]; + } + + +} + +- (void)initProfile { + for(UIView *subview in [self subviews]) { + [subview removeFromSuperview]; + + } +} + +- (void)doFollowButton:(id)sender { + NSString *urlString; + + [self.activityIndicator startAnimating]; + + if ([self.followButton.currentTitle isEqualToString:@"Follow"]) { + urlString = [NSString stringWithFormat:@"http://%@/social/follow", + NEWSBLUR_URL, + [self.activeProfile objectForKey:@"user_id"]]; + } else { + urlString = [NSString stringWithFormat:@"http://%@/social/unfollow", + NEWSBLUR_URL, + [self.activeProfile objectForKey:@"user_id"]]; + } + + NSURL *url = [NSURL URLWithString:urlString]; + + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + [request setDelegate:self]; + [request setPostValue:[self.activeProfile objectForKey:@"user_id"] forKey:@"user_id"]; + if ([self.followButton.currentTitle isEqualToString:@"Follow"]) { + [request setDidFinishSelector:@selector(finishFollowing:)]; + } else { + [request setDidFinishSelector:@selector(finishUnfollowing:)]; + } + [request setDidFailSelector:@selector(requestFailed:)]; + + [request startAsynchronous]; +} + +- (void)finishFollowing:(ASIHTTPRequest *)request { + + [self.activityIndicator stopAnimating]; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + NSLog(@"ERROR"); + return; + } + + [self.followButton setTitle:@"Following" forState:UIControlStateNormal]; + self.followButton.backgroundColor = UIColorFromRGB(kFollowColor); + [self.followButton setTitleColor:UIColorFromRGB(kFollowTextColor) forState:UIControlStateNormal]; + [appDelegate reloadFeedsView:NO]; + + NSMutableDictionary *newProfile = [self.activeProfile mutableCopy]; + NSNumber *count = [newProfile objectForKey:@"follower_count"]; + int value = [count intValue]; + count = [NSNumber numberWithInt:value + 1]; + + [newProfile setObject:count forKey:@"follower_count"]; + [newProfile setObject:[NSNumber numberWithInt:1] forKey:@"followed_by_you"]; + [self refreshWithProfile:newProfile showStats:self.shouldShowStats withWidth:0]; +} + + +- (void)finishUnfollowing:(ASIHTTPRequest *)request { + [self.activityIndicator stopAnimating]; + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + int code = [[results valueForKey:@"code"] intValue]; + if (code == -1) { + NSLog(@"ERROR"); + return; + } + + NSLog(@"results %@", results); + [self.followButton setTitle:@"Follow" forState:UIControlStateNormal]; + self.followButton.backgroundColor = UIColorFromRGB(kFollowingColor); + [self.followButton setTitleColor:UIColorFromRGB(kFollowingTextColor) forState:UIControlStateNormal]; + + [appDelegate reloadFeedsView:NO]; + + NSMutableDictionary *newProfile = [self.activeProfile mutableCopy]; + NSNumber *count = [newProfile objectForKey:@"follower_count"]; + int value = [count intValue]; + count = [NSNumber numberWithInt:value - 1]; + + [newProfile setObject:count forKey:@"follower_count"]; + [newProfile setObject:[NSNumber numberWithInt:0] forKey:@"followed_by_you"]; + [self refreshWithProfile:newProfile showStats:self.shouldShowStats withWidth:0]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + [self.activityIndicator stopAnimating]; + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +@end \ No newline at end of file diff --git a/media/ios/Classes/ShareViewController.h b/media/ios/Classes/ShareViewController.h new file mode 100644 index 000000000..d6114d383 --- /dev/null +++ b/media/ios/Classes/ShareViewController.h @@ -0,0 +1,38 @@ +// +// ShareViewController.h +// NewsBlur +// +// Created by Roy Yang on 6/21/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" + +@interface ShareViewController : UIViewController { + NewsBlurAppDelegate *appDelegate; + NSString *activeReplyId; +} + +@property (nonatomic) IBOutlet UITextView *commentField; +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) IBOutlet UIButton *facebookButton; +@property (nonatomic) IBOutlet UIButton *twitterButton; +@property (nonatomic) IBOutlet UIBarButtonItem *submitButton; +@property (nonatomic) NSString * activeReplyId; +@property (nonatomic) NSString* currentType; + +- (void)setSiteInfo:(NSString *)type setUserId:(NSString *)userId setUsername:(NSString *)username setReplyId:(NSString *)commentIndex; +- (void)clearComments; +- (IBAction)doCancelButton:(id)sender; +- (IBAction)doToggleButton:(id)sender; +- (IBAction)doShareThisStory:(id)sender; +- (IBAction)doReplyToComment:(id)sender; +- (void)finishShareThisStory:(ASIHTTPRequest *)request; +- (void)finishAddReply:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)replaceStory:(NSDictionary *)newStory withReplyId:(NSString *)replyId; +- (void)adjustCommentField; +- (NSString *)stringByStrippingHTML:(NSString *)s; + +@end diff --git a/media/ios/Classes/ShareViewController.m b/media/ios/Classes/ShareViewController.m new file mode 100644 index 000000000..f7ff717e0 --- /dev/null +++ b/media/ios/Classes/ShareViewController.m @@ -0,0 +1,392 @@ +// +// ShareViewController.m +// NewsBlur +// +// Created by Roy Yang on 6/21/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ShareViewController.h" +#import "NewsBlurAppDelegate.h" +#import "StoryDetailViewController.h" +#import +#import "Utilities.h" +#import "DataUtilities.h" +#import "ASIHTTPRequest.h" + +@implementation ShareViewController + +@synthesize facebookButton; +@synthesize twitterButton; +@synthesize submitButton; +@synthesize commentField; +@synthesize appDelegate; +@synthesize activeReplyId; +@synthesize currentType; + + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewDidLoad { + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + + // For textField1 + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onTextChange:) + name:UITextViewTextDidChangeNotification + object:self.commentField]; + + UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonSystemItemCancel target:self action:@selector(doCancelButton:)]; + self.navigationItem.leftBarButtonItem = cancel; + + UIBarButtonItem *submit = [[UIBarButtonItem alloc] initWithTitle:@"Post" style:UIBarButtonSystemItemDone target:self action:@selector(doShareThisStory:)]; + self.submitButton = submit; + self.navigationItem.rightBarButtonItem = submit; + + + // Do any additional setup after loading the view from its nib. + commentField.layer.borderWidth = 1.0f; + commentField.layer.cornerRadius = 4; + commentField.layer.borderColor = [[UIColor grayColor] CGColor]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if ([userPreferences integerForKey:@"shareToFacebook"]){ + facebookButton.selected = YES; + } + if ([userPreferences integerForKey:@"shareToTwitter"]){ + twitterButton.selected = YES; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + } else { + self.submitButton.tintColor = UIColorFromRGB(0x709d3c); + } + + [super viewDidLoad]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)viewDidUnload +{ + [self setCommentField:nil]; + [self setFacebookButton:nil]; + [self setTwitterButton:nil]; + [self setSubmitButton:nil]; + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [self adjustCommentField]; +} + +- (void)viewWillAppear:(BOOL)animated { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [self.commentField becomeFirstResponder]; + [self adjustCommentField]; + } +} + +- (void)adjustCommentField { + UIInterfaceOrientation orientation = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; + if (UIInterfaceOrientationIsPortrait(orientation)){ + self.commentField.frame = CGRectMake(20, 20, 280, 124); + self.twitterButton.frame = CGRectMake(228, 152, 32, 32); + self.facebookButton.frame = CGRectMake(268, 152, 32, 32); + } else { + self.commentField.frame = CGRectMake(60, 20, 400, 74); + self.twitterButton.frame = CGRectMake(15, 20, 32, 32); + self.facebookButton.frame = CGRectMake(15, 63, 32, 32); + } +} + +- (IBAction)doCancelButton:(id)sender { + [appDelegate hideShareView:NO]; +} + +- (IBAction)doToggleButton:(id)sender { + UIButton *button = (UIButton *)sender; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if (button.selected) { + button.selected = NO; + if ([[button currentTitle] isEqualToString: @"Facebook"]) { + [userPreferences setInteger:0 forKey:@"shareToFacebook"]; + } else if ([[button currentTitle] isEqualToString: @"Twitter"]) { + [userPreferences setInteger:0 forKey:@"shareToTwitter"]; + } + } else { + button.selected = YES; + if ([[button currentTitle] isEqualToString: @"Facebook"]) { + [userPreferences setInteger:1 forKey:@"shareToFacebook"]; + } else if ([[button currentTitle] isEqualToString: @"Twitter"]) { + [userPreferences setInteger:1 forKey:@"shareToTwitter"]; + } + } + [userPreferences synchronize]; +} + +- (void)setSiteInfo:(NSString *)type setUserId:(NSString *)userId setUsername:(NSString *)username setReplyId:(NSString *)replyId { + [self.submitButton setStyle:UIBarButtonItemStyleDone]; + if ([type isEqualToString: @"edit-reply"]) { + self.currentType = nil; + [submitButton setTitle:@"Save your reply"]; + facebookButton.hidden = YES; + twitterButton.hidden = YES; +// self.navigationItem.title = @"Edit Your Reply"; + [submitButton setAction:(@selector(doReplyToComment:))]; + self.activeReplyId = replyId; + + // get existing reply + NSArray *replies = [appDelegate.activeComment objectForKey:@"replies"]; + NSDictionary *reply = nil; + for (int i = 0; i < replies.count; i++) { + NSString *replyId = [NSString stringWithFormat:@"%@", [[replies objectAtIndex:i] valueForKey:@"reply_id"]]; + NSLog(@"[replies objectAtIndex:i] valueForKey:@reply_id] %@", [[replies objectAtIndex:i] valueForKey:@"reply_id"]); + NSLog(@":self.activeReplyId %@", self.activeReplyId); + + if ([replyId isEqualToString:self.activeReplyId]) { + reply = [replies objectAtIndex:i]; + } + } + if (reply) { + self.commentField.text = [self stringByStrippingHTML:[reply objectForKey:@"comments"]]; + } + } else if ([type isEqualToString: @"reply"]) { + + self.activeReplyId = nil; + [submitButton setTitle:[NSString stringWithFormat:@"Reply to %@", username]]; + facebookButton.hidden = YES; + twitterButton.hidden = YES; +// self.navigationItem.title = [NSString stringWithFormat:@"Reply to %@", username]; + [submitButton setAction:(@selector(doReplyToComment:))]; + + if (![self.currentType isEqualToString:@"share"] && + ![self.currentType isEqualToString:@"reply"]) { + self.commentField.text = @""; + self.currentType = type; + } + } else if ([type isEqualToString: @"edit-share"]) { + self.currentType = nil; + facebookButton.hidden = NO; + twitterButton.hidden = NO; + + // get old comment + self.commentField.text = [self stringByStrippingHTML:[appDelegate.activeComment objectForKey:@"comments"]]; + +// if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { +// self.navigationItem.title = @"Edit Your Comment"; +// [submitButton setTitle:@"Save your comments"]; +// } else { +// self.navigationItem.title = @"Edit Comment"; +// [submitButton setTitle:@"Save"]; +// } + [submitButton setTitle:@"Save your comments"]; + [submitButton setAction:(@selector(doShareThisStory:))]; + } else if ([type isEqualToString: @"share"]) { + facebookButton.hidden = NO; + twitterButton.hidden = NO; + [submitButton setTitle:@"Share this story"]; + [submitButton setAction:(@selector(doShareThisStory:))]; + if (![self.currentType isEqualToString:@"share"] && + ![self.currentType isEqualToString:@"reply"]) { + self.commentField.text = @""; + self.currentType = type; + } + } +} + +- (void)clearComments { + self.commentField.text = nil; + self.currentType = nil; +} + +# pragma mark +# pragma mark Share Story + +- (IBAction)doShareThisStory:(id)sender { + [appDelegate.storyDetailViewController showShareHUD:@"Sharing"]; + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/share_story", + NEWSBLUR_URL]; + + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; + NSString *storyIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"id"]]; + + [request setPostValue:feedIdStr forKey:@"feed_id"]; + [request setPostValue:storyIdStr forKey:@"story_id"]; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if ([userPreferences integerForKey:@"shareToFacebook"]){ + [request addPostValue:@"facebook" forKey:@"post_to_services"]; + } + if ([userPreferences integerForKey:@"shareToTwitter"]){ + [request addPostValue:@"twitter" forKey:@"post_to_services"]; + } + + if (appDelegate.isSocialRiverView) { + if ([appDelegate.activeStory objectForKey:@"friend_user_ids"] != nil) { + NSString *sourceUserIdStr = [NSString stringWithFormat:@"%@", [[appDelegate.activeStory objectForKey:@"friend_user_ids"] objectAtIndex:0]]; + [request setPostValue:sourceUserIdStr forKey:@"source_user_id"]; + } + } else { + if ([appDelegate.activeStory objectForKey:@"social_user_id"] != nil) { + NSString *sourceUserIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"social_user_id"]]; + [request setPostValue:sourceUserIdStr forKey:@"source_user_id"]; + } + } + + + NSString *comments = commentField.text; + if ([comments length]) { + [request setPostValue:comments forKey:@"comments"]; + } + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishShareThisStory:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; + [appDelegate hideShareView:YES]; +} + +- (void)finishShareThisStory:(ASIHTTPRequest *)request { + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + + NSArray *userProfiles = [results objectForKey:@"user_profiles"]; + appDelegate.activeFeedUserProfiles = [DataUtilities + updateUserProfiles:appDelegate.activeFeedUserProfiles + withNewUserProfiles:userProfiles]; + + [self replaceStory:[results objectForKey:@"story"] withReplyId:nil]; +} + +# pragma mark +# pragma mark Reply to Story + +- (IBAction)doReplyToComment:(id)sender { + [appDelegate.storyDetailViewController showShareHUD:@"Replying"]; + NSString *comments = commentField.text; + if ([comments length] == 0) { + return; + } + +// NSLog(@"REPLY TO COMMENT, %@", appDelegate.activeComment); + NSString *urlString = [NSString stringWithFormat:@"http://%@/social/save_comment_reply", + NEWSBLUR_URL]; + + NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]]; + NSString *storyIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"id"]]; + + NSURL *url = [NSURL URLWithString:urlString]; + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; + [request setPostValue:feedIdStr forKey:@"story_feed_id"]; + [request setPostValue:storyIdStr forKey:@"story_id"]; + [request setPostValue:[appDelegate.activeComment objectForKey:@"user_id"] forKey:@"comment_user_id"]; + [request setPostValue:commentField.text forKey:@"reply_comments"]; + + if (self.activeReplyId) { + [request setPostValue:activeReplyId forKey:@"reply_id"]; + } + + [request setDelegate:self]; + [request setDidFinishSelector:@selector(finishAddReply:)]; + [request setDidFailSelector:@selector(requestFailed:)]; + [request startAsynchronous]; + [appDelegate hideShareView:YES]; + [appDelegate.storyDetailViewController showShareHUD:@"Replying"]; +} + +- (void)finishAddReply:(ASIHTTPRequest *)request { + NSLog(@"Successfully added."); + NSString *responseString = [request responseString]; + NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *results = [NSJSONSerialization + JSONObjectWithData:responseData + options:kNilOptions + error:&error]; + // add the comment into the activeStory dictionary + NSDictionary *newStory = [DataUtilities updateComment:results for:appDelegate]; + [self replaceStory:newStory withReplyId:[results objectForKey:@"reply_id"]]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSError *error = [request error]; + NSLog(@"Error: %@", error); +} + +- (void)replaceStory:(NSDictionary *)newStory withReplyId:(NSString *)replyId { + NSMutableDictionary *newStoryParsed = [newStory mutableCopy]; + [newStoryParsed setValue:[NSNumber numberWithInt:1] forKey:@"read_status"]; + [newStoryParsed setValue:[appDelegate.activeStory objectForKey:@"short_parsed_date"] forKey:@"short_parsed_date"] ; + + // update the current story and the activeFeedStories + appDelegate.activeStory = newStoryParsed; + + NSMutableArray *newActiveFeedStories = [[NSMutableArray alloc] init]; + + for (int i = 0; i < appDelegate.activeFeedStories.count; i++) { + NSDictionary *feedStory = [appDelegate.activeFeedStories objectAtIndex:i]; + NSString *storyId = [NSString stringWithFormat:@"%@", [feedStory objectForKey:@"id"]]; + NSString *currentStoryId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"id"]]; + if ([storyId isEqualToString: currentStoryId]){ + [newActiveFeedStories addObject:newStoryParsed]; + } else { + [newActiveFeedStories addObject:[appDelegate.activeFeedStories objectAtIndex:i]]; + } + } + + appDelegate.activeFeedStories = [NSArray arrayWithArray:newActiveFeedStories]; + + self.commentField.text = nil; + [appDelegate.storyDetailViewController refreshComments:replyId]; +} + + +- (NSString *)stringByStrippingHTML:(NSString *)s { + NSRange r; + while ((r = [s rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound) + s = [s stringByReplacingCharactersInRange:r withString:@""]; + return s; +} + +-(void)onTextChange:(NSNotification*)notification { + NSString *text = self.commentField.text; + if ([self.submitButton.title isEqualToString:@"Share this story"] || + [self.submitButton.title isEqualToString:@"Share with comments"]) { + NSLog(@"text.length is %i", text.length); + if (text.length) { + self.submitButton.title = @"Share with comments"; + } else { + self.submitButton.title = @"Share this story"; + } + } + + + +} +@end diff --git a/media/ios/Classes/ShareViewController~ipad.xib b/media/ios/Classes/ShareViewController~ipad.xib new file mode 100644 index 000000000..a4df6c901 --- /dev/null +++ b/media/ios/Classes/ShareViewController~ipad.xib @@ -0,0 +1,1644 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUITextView + IBUIButton + IBUIView + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 319 + + + + 303 + {{86, 15}, {532, 75}} + + + _NS:9 + + 1 + MSAxIDEAA + + YES + YES + IBIPadFramework + NO + + + 2 + IBCocoaTouchFramework + + + 1 + 14 + + + Helvetica + 14 + 16 + + + + + 301 + {{29, 58}, {32, 32}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + Facebook + + 3 + MQA + + + 1 + MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA + + + 3 + MC41AA + + + NSImage + facebook_button_on.png + + + NSImage + facebook_button.png + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + + 301 + {{29, 15}, {32, 32}} + + + + _NS:9 + NO + IBIPadFramework + 0 + 0 + Twitter + + + 1 + MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA + + + + NSImage + twitter_button_on.png + + + NSImage + twitter_button.png + + + + + + {704, 100} + + + + + 3 + MQA + + 2 + + + NO + + IBUISimulatedFreeformSizeMetricsSentinel + Freeform + + IBIPadFramework + + + + + + + view + + + + 3 + + + + commentField + + + + 29 + + + + facebookButton + + + + 36 + + + + twitterButton + + + + 37 + + + + doToggleButton: + + + 7 + + 25 + + + + doToggleButton: + + + 7 + + 26 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + + + + 19 + + + + + 20 + + + + + 21 + + + + + + + ShareViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + + 50 + + + + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + id + id + id + id + id + id + + + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + id + id + + + + doLogout: + id + + + tapSegmentedButton: + id + + + + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + id + id + id + + + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + NewsBlurAppDelegate + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + id + id + id + id + + + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + id + id + id + + + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + NewsBlurAppDelegate + UIButton + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UIButton + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + id + id + id + + + + tapCategoryButton: + id + + + tapGoogleReaderButton + id + + + tapNextButton + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIButton + UIView + UILabel + UIBarButtonItem + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + id + id + + + + changeFontSize: + id + + + changeFontStyle: + id + + + + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + NewsBlurAppDelegate + UISearchBar + UITableView + + + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + id + id + id + id + id + + + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + id + id + id + id + + + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + UIButton + UIButton + UIButton + id + id + + + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + + + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + id + id + id + + + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + id + id + id + id + + + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + id + id + id + id + id + id + + + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + + 0 + IBIPadFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + {32, 32} + {32, 32} + {64, 64} + {32, 32} + + 1181 + + diff --git a/media/ios/Classes/SiteCell.h b/media/ios/Classes/SiteCell.h new file mode 100644 index 000000000..eefbe98ff --- /dev/null +++ b/media/ios/Classes/SiteCell.h @@ -0,0 +1,34 @@ +// +// SiteCell.h +// NewsBlur +// +// Created by Roy Yang on 8/14/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// +#import +#import "NewsBlurAppDelegate.h" +#import "ABTableViewCell.h" + +@interface SiteCell : ABTableViewCell { + NewsBlurAppDelegate *appDelegate; + // River view + NSString *siteTitle; + UIImage *siteFavicon; + UIColor *feedColorBar; + UIColor *feedColorBarTopBorder; + + BOOL hasAlpha; + BOOL isRead; +} + +@property (nonatomic) NSString *siteTitle; +@property (nonatomic) UIImage *siteFavicon; +@property (nonatomic) UIColor *feedColorBar; +@property (nonatomic) UIColor *feedColorBarTopBorder; + +@property (readwrite) BOOL hasAlpha; +@property (readwrite) BOOL isRead; + +- (UIImage *)imageByApplyingAlpha:(UIImage *)image withAlpha:(CGFloat) alpha; + +@end \ No newline at end of file diff --git a/media/ios/Classes/SiteCell.m b/media/ios/Classes/SiteCell.m new file mode 100644 index 000000000..3b50f0742 --- /dev/null +++ b/media/ios/Classes/SiteCell.m @@ -0,0 +1,116 @@ +// +// SiteCell.m +// NewsBlur +// +// Created by Roy Yang on 8/14/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "SiteCell.h" +#import "ABTableViewCell.h" +#import "UIView+TKCategory.h" +#import "Utilities.h" + +static UIFont *textFont = nil; +static UIFont *indicatorFont = nil; + + +@implementation SiteCell + +@synthesize siteTitle; +@synthesize siteFavicon; +@synthesize feedColorBar; +@synthesize feedColorBarTopBorder; +@synthesize hasAlpha; +@synthesize isRead; + +#define leftMargin 18 +#define rightMargin 18 + ++ (void) initialize { + if (self == [SiteCell class]) { + textFont = [UIFont boldSystemFontOfSize:18]; + indicatorFont = [UIFont boldSystemFontOfSize:12]; + } +} + +- (void)drawContentView:(CGRect)r highlighted:(BOOL)highlighted { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGRect rect = CGRectInset(r, 12, 12); + rect.size.width -= 18; // Scrollbar padding + + // set the background color + UIColor *backgroundColor = UIColorFromRGB(0xf4f4f4); + [backgroundColor set]; + + CGContextFillRect(context, r); + + if (!self.isRead) { + CGContextSetAlpha(context, 1); + } else { + CGContextSetAlpha(context, 0.5); + } + // set site title + UIColor *textColor; + UIFont *font; + + font = [UIFont fontWithName:@"Helvetica-Bold" size:11]; + textColor = UIColorFromRGB(0x606060); + [textColor set]; + + [self.siteTitle + drawInRect:CGRectMake(leftMargin + 20, 6, rect.size.width - 20, 21) + withFont:font + lineBreakMode:UILineBreakModeTailTruncation + alignment:UITextAlignmentLeft]; + + // feed bar + CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor])); //feedColorBarTopBorder + CGContextSetLineWidth(context, 6.0f); + CGContextBeginPath(context); + CGContextMoveToPoint(context, 3.0f, 1.0f); + CGContextAddLineToPoint(context, 3.0f, self.frame.size.height); + CGContextStrokePath(context); + + CGContextSetStrokeColor(context, CGColorGetComponents([self.feedColorBar CGColor])); //feedColorBarTopBorder + CGContextSetLineWidth(context, 6.0f); + CGContextBeginPath(context); + float width = self.bounds.size.width - 20.0f; + CGContextMoveToPoint(context, width, 1.0f); + CGContextAddLineToPoint(context, width, self.frame.size.height); + CGContextStrokePath(context); + + // site favicon + if (self.isRead) { + self.siteFavicon = [self imageByApplyingAlpha:self.siteFavicon withAlpha:0.25]; + } + + [self.siteFavicon drawInRect:CGRectMake(leftMargin, 5.0, 16.0, 16.0)]; +} + +- (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 diff --git a/media/ios/Classes/SmallActivityCell.h b/media/ios/Classes/SmallActivityCell.h new file mode 100644 index 000000000..01975176b --- /dev/null +++ b/media/ios/Classes/SmallActivityCell.h @@ -0,0 +1,13 @@ +// +// SmallActivityCell.h +// NewsBlur +// +// Created by Roy Yang on 7/21/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "ActivityCell.h" + +@interface SmallActivityCell : ActivityCell + +@end diff --git a/media/ios/Classes/SmallActivityCell.m b/media/ios/Classes/SmallActivityCell.m new file mode 100644 index 000000000..bb66f593b --- /dev/null +++ b/media/ios/Classes/SmallActivityCell.m @@ -0,0 +1,67 @@ +// +// SmallActivityCell.m +// NewsBlur +// +// Created by Roy Yang on 7/21/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "SmallActivityCell.h" +#import "NSAttributedString+Attributes.h" +#import "UIImageView+AFNetworking.h" +#import + +@implementation SmallActivityCell + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + activityLabel = nil; + faviconView = nil; + + // create favicon and label in view + UIImageView *favicon = [[UIImageView alloc] initWithFrame:CGRectZero]; + self.faviconView = favicon; + [self.contentView addSubview:favicon]; + + OHAttributedLabel *activity = [[OHAttributedLabel alloc] initWithFrame:CGRectZero]; + activity.backgroundColor = [UIColor whiteColor]; + activity.automaticallyAddLinksForType = NO; + self.activityLabel = activity; + [self.contentView addSubview:activity]; + + topMargin = 10; + bottomMargin = 10; + leftMargin = 10; + rightMargin = 10; + avatarSize = 32; + } + + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + // determine outer bounds + CGRect contentRect = self.contentView.bounds; + + // position avatar to bounds + self.faviconView.frame = CGRectMake(leftMargin, topMargin, avatarSize, avatarSize); + + // 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; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.activityLabel.backgroundColor = UIColorFromRGB(0xd7dadf); + } else { + self.activityLabel.backgroundColor = UIColorFromRGB(0xf6f6f6); + } + self.activityLabel.backgroundColor = [UIColor clearColor]; +} + +@end diff --git a/media/ios/Classes/StoryDetailViewController.h b/media/ios/Classes/StoryDetailViewController.h new file mode 100644 index 000000000..33b09790e --- /dev/null +++ b/media/ios/Classes/StoryDetailViewController.h @@ -0,0 +1,94 @@ +// +// StoryDetailViewController.h +// NewsBlur +// +// Created by Samuel Clay on 6/24/10. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import +#import "WEPopoverController.h" + +@class NewsBlurAppDelegate; +@class ASIHTTPRequest; + +@interface StoryDetailViewController : UIViewController + { + NewsBlurAppDelegate *appDelegate; + + NSString *activeStoryId; + UIProgressView *progressView; + UIView *innerView; + UIWebView *webView; + UIToolbar *toolbar; + UIBarButtonItem *buttonPrevious; + UIBarButtonItem *buttonNext; + UIBarButtonItem *activity; + UIActivityIndicatorView *loadingIndicator; + WEPopoverController *popoverController; + UIToolbar *bottomPlaceholderToolbar; + UIBarButtonItem *buttonBack; + Class popoverClass; + +} + +@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator; +@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate; +@property (nonatomic) NSString *activeStoryId; +@property (nonatomic) IBOutlet UIProgressView *progressView; +@property (strong, nonatomic) IBOutlet UIView *progressViewContainer; +@property (nonatomic) IBOutlet UIView *innerView; +@property (nonatomic) IBOutlet UIWebView *webView; +@property (nonatomic) IBOutlet UIToolbar *toolbar; +@property (nonatomic) IBOutlet UIBarButtonItem *buttonPrevious; +@property (nonatomic) IBOutlet UIBarButtonItem *buttonNext; +@property (nonatomic) UIBarButtonItem *buttonBack; +@property (nonatomic) IBOutlet UIBarButtonItem *activity; +@property (nonatomic) IBOutlet UIBarButtonItem *buttonAction; +@property (nonatomic) IBOutlet UIView *feedTitleGradient; +@property (nonatomic) IBOutlet UIBarButtonItem *buttonNextStory; +@property (nonatomic, strong) WEPopoverController *popoverController; +@property (nonatomic) IBOutlet UIToolbar *bottomPlaceholderToolbar; +@property (nonatomic) IBOutlet UIBarButtonItem *fontSettingsButton; +@property (nonatomic) IBOutlet UIBarButtonItem *originalStoryButton; +@property (nonatomic, strong) IBOutlet UIBarButtonItem *subscribeButton; +@property (nonatomic) IBOutlet UILabel *noStorySelectedLabel; + + +- (void)setNextPreviousButtons; +- (void)markStoryAsRead; +- (void)toggleLikeComment:(BOOL)likeComment; +- (void)showStory; +- (void)scrolltoComment; +- (IBAction)showOriginalSubview:(id)sender; +- (IBAction)doNextUnreadStory; +- (IBAction)doNextStory; +- (IBAction)doPreviousStory; +- (IBAction)tapProgressBar:(id)sender; +- (void)changeWebViewWidth:(int)width; +- (void)showUserProfile:(NSString *)userId xCoordinate:(int)x yCoordinate:(int)y width:(int)width height:(int)height; +- (void)initStory; +- (void)clearStory; + +- (void)showShareHUD:(NSString *)msg; +- (void)refreshComments:(NSString *)replyId; +- (void)finishMarkAsRead:(ASIHTTPRequest *)request; +- (void)finishLikeComment:(ASIHTTPRequest *)request; +- (void)subscribeToBlurblog; +- (void)finishSubscribeToBlurblog:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)setActiveStory; +- (IBAction)toggleFontSize:(id)sender; +- (void)setFontStyle:(NSString *)fontStyle; +- (void)changeFontSize:(NSString *)fontSize; +- (NSString *)getShareBar; +- (NSString *)getComments; +- (NSString *)getComment:(NSDictionary *)commentDict; +- (NSString *)getReplies:(NSArray *)replies forUserId:(NSString *)commentUserId; +- (NSString *)getAvatars:(NSString *)key; +- (NSDictionary *)getUser:(int)user_id; +- (void)transitionFromFeedDetail; + + + +@end diff --git a/media/ios/Classes/StoryDetailViewController.m b/media/ios/Classes/StoryDetailViewController.m new file mode 100644 index 000000000..eeadcc59e --- /dev/null +++ b/media/ios/Classes/StoryDetailViewController.m @@ -0,0 +1,1640 @@ +// +// StoryDetailViewController.m +// NewsBlur +// +// Created by Samuel Clay on 6/24/10. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import "StoryDetailViewController.h" +#import "NewsBlurAppDelegate.h" +#import "NewsBlurViewController.h" +#import "FeedDetailViewController.h" +#import "FontSettingsViewController.h" +#import "UserProfileViewController.h" +#import "ShareViewController.h" +#import "ASIHTTPRequest.h" +#import "ASIFormDataRequest.h" +#import "Base64.h" +#import "Utilities.h" +#import "NSString+HTML.h" +#import "NBContainerViewController.h" +#import "DataUtilities.h" +#import "JSON.h" + +@interface StoryDetailViewController () + +@property (readwrite) CGFloat inTouchMove; +@property (nonatomic) MBProgressHUD *storyHUD; + +@end + +@implementation StoryDetailViewController + +@synthesize appDelegate; +@synthesize activeStoryId; +@synthesize progressView; +@synthesize progressViewContainer; +@synthesize innerView; +@synthesize webView; +@synthesize toolbar; +@synthesize buttonPrevious; +@synthesize buttonNext; +@synthesize buttonAction; +@synthesize activity; +@synthesize loadingIndicator; +@synthesize feedTitleGradient; +@synthesize buttonNextStory; +@synthesize popoverController; +@synthesize fontSettingsButton; +@synthesize originalStoryButton; +@synthesize subscribeButton; +@synthesize noStorySelectedLabel; +@synthesize buttonBack; +@synthesize bottomPlaceholderToolbar; + +// private +@synthesize inTouchMove; +@synthesize storyHUD; + + + +#pragma mark - +#pragma mark View boilerplate + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + + if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + } + return self; +} + + +- (void)viewDidLoad { + [super viewDidLoad]; + popoverClass = [WEPopoverController class]; + + // adding HUD for progress bar + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapProgressBar:)]; + + [self.progressViewContainer addGestureRecognizer:tap]; + self.progressViewContainer.hidden = YES; + + + // settings button to right +// UIImage *settingsImage = [UIImage imageNamed:@"settings.png"]; +// UIButton *settings = [UIButton buttonWithType:UIButtonTypeCustom]; +// settings.bounds = CGRectMake(0, 0, 32, 32); +// [settings addTarget:self action:@selector(toggleFontSize:) forControlEvents:UIControlEventTouchUpInside]; +// [settings setImage:settingsImage forState:UIControlStateNormal]; +// +// UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] +// initWithCustomView:settings]; + + UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"settings.png"] style:UIBarButtonItemStylePlain target:self action:@selector(toggleFontSize:)]; + + self.fontSettingsButton = settingsButton; + + // original button for iPhone + + UIBarButtonItem *originalButton = [[UIBarButtonItem alloc] + initWithTitle:@"Original" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(showOriginalSubview:) + ]; + + self.originalStoryButton = originalButton; + + UIBarButtonItem *subscribeBtn = [[UIBarButtonItem alloc] + initWithTitle:@"Follow User" + style:UIBarButtonSystemItemAction + target:self + action:@selector(subscribeToBlurblog) + ]; + + self.subscribeButton = subscribeBtn; + + // back button + UIBarButtonItem *backButton = [[UIBarButtonItem alloc] + initWithTitle:@"All Sites" style:UIBarButtonItemStyleBordered target:self action:@selector(transitionFromFeedDetail)]; + self.buttonBack = backButton; + + // loading indicator + self.loadingIndicator = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + + self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + self.webView.scalesPageToFit = NO; + self.webView.multipleTouchEnabled = NO; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + backBtn.frame = CGRectMake(0, 0, 51, 31); + [backBtn setImage:[UIImage imageNamed:@"nav_btn_back.png"] forState:UIControlStateNormal]; + [backBtn addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside]; + UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithCustomView:backBtn]; + self.navigationItem.backBarButtonItem = back; + + self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects: originalButton, settingsButton, nil]; + } else { + self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + self.bottomPlaceholderToolbar.tintColor = [UIColor colorWithRed:0.16f green:0.36f blue:0.46 alpha:0.9]; + } +} + +- (void)transitionFromFeedDetail { + [self performSelector:@selector(clearStory) withObject:self afterDelay:0.5]; + [appDelegate.masterContainerViewController transitionFromFeedDetail]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return YES; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [appDelegate adjustStoryDetailWebView]; + [self setActiveStory]; +} + +- (void)viewDidAppear:(BOOL)animated { + // set the subscribeButton flag + if (appDelegate.isTryFeedView) { + self.subscribeButton.title = [NSString stringWithFormat:@"Follow %@", [appDelegate.activeFeed objectForKey:@"username"]]; + self.navigationItem.leftBarButtonItem = self.subscribeButton; + // self.subscribeButton.tintColor = UIColorFromRGB(0x0a6720); + } + appDelegate.isTryFeedView = NO; + +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && UIInterfaceOrientationIsPortrait(orientation)) { + UITouch *theTouch = [touches anyObject]; + if ([theTouch.view isKindOfClass: UIToolbar.class] || [theTouch.view isKindOfClass: UIView.class]) { + self.inTouchMove = YES; + CGPoint touchLocation = [theTouch locationInView:self.view]; + CGFloat y = touchLocation.y; + [appDelegate.masterContainerViewController dragStoryToolbar:y]; + } + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && UIInterfaceOrientationIsPortrait(orientation)) { + UITouch *theTouch = [touches anyObject]; + + if (([theTouch.view isKindOfClass: UIToolbar.class] || [theTouch.view isKindOfClass: UIView.class]) && self.inTouchMove) { + self.inTouchMove = NO; + [appDelegate.masterContainerViewController adjustFeedDetailScreenForStoryTitles]; + } + } +} + +- (void)viewDidUnload { + [self setButtonNextStory:nil]; + [self setInnerView:nil]; + [self setBottomPlaceholderToolbar:nil]; + [self setProgressViewContainer:nil]; + [self setNoStorySelectedLabel:nil]; + [super viewDidUnload]; +} + +- (void)initStory { + id storyId = [appDelegate.activeStory objectForKey:@"id"]; + [appDelegate pushReadStory:storyId]; + [self showStory]; + self.webView.scalesPageToFit = YES; + [self.loadingIndicator stopAnimating]; +} + +- (void)clearStory { + [self.webView loadHTMLString:@"" baseURL:nil]; + self.noStorySelectedLabel.hidden = NO; +} + +- (void)viewDidDisappear:(BOOL)animated { +// Class viewClass = [appDelegate.navigationController.visibleViewController class]; +// if (viewClass == [appDelegate.feedDetailViewController class] || +// viewClass == [appDelegate.feedsViewController class]) { +//// self.activeStoryId = nil; +// [webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@""]]; +// } +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + [appDelegate adjustStoryDetailWebView]; +} + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { +// [appDelegate.shareViewController.commentField resignFirstResponder]; +} + + +#pragma mark - +#pragma mark Story layout + +- (NSString *)getAvatars:(NSString *)key { + NSString *avatarString = @""; + NSArray *share_user_ids = [appDelegate.activeStory objectForKey:key]; + + for (int i = 0; i < share_user_ids.count; i++) { + NSDictionary *user = [self getUser:[[share_user_ids objectAtIndex:i] intValue]]; + NSString *avatarClass = @"NB-user-avatar"; + if ([key isEqualToString:@"commented_by_public"] || + [key isEqualToString:@"shared_by_public"]) { + avatarClass = @"NB-public-user NB-user-avatar"; + } + NSString *avatar = [NSString stringWithFormat:@ + "
" + "" + "
", + avatarClass, + [user objectForKey:@"user_id"], + [user objectForKey:@"user_id"], + [user objectForKey:@"photo_url"]]; + avatarString = [avatarString stringByAppendingString:avatar]; + } + + return avatarString; +} + +- (NSString *)getComments { + NSString *comments = @"
"; + + if ([appDelegate.activeStory objectForKey:@"share_count"] != [NSNull null] && + [[appDelegate.activeStory objectForKey:@"share_count"] intValue] > 0) { + + NSDictionary *story = appDelegate.activeStory; + NSArray *friendsCommentsArray = [story objectForKey:@"friend_comments"]; + NSArray *publicCommentsArray = [story objectForKey:@"public_comments"]; + + // add friends comments + for (int i = 0; i < friendsCommentsArray.count; i++) { + NSString *comment = [self getComment:[friendsCommentsArray objectAtIndex:i]]; + comments = [comments stringByAppendingString:comment]; + } + + if ([[story objectForKey:@"comment_count_public"] intValue] > 0 ) { + NSString *publicCommentHeader = [NSString stringWithFormat:@ + "
" + "
%i public comment%@
" + "
", + [[story objectForKey:@"comment_count_public"] intValue], + [[story objectForKey:@"comment_count_public"] intValue] == 1 ? @"" : @"s"]; + + comments = [comments stringByAppendingString:publicCommentHeader]; + + // add friends comments + for (int i = 0; i < publicCommentsArray.count; i++) { + NSString *comment = [self getComment:[publicCommentsArray objectAtIndex:i]]; + comments = [comments stringByAppendingString:comment]; + } + } + + + comments = [comments stringByAppendingString:[NSString stringWithFormat:@"
"]]; + } + + return comments; +} + +- (NSString *)getShareBar { + NSString *comments = @"
"; + NSString *commentLabel = @""; + NSString *shareLabel = @""; +// NSString *replyStr = @""; + +// if ([[appDelegate.activeStory objectForKey:@"reply_count"] intValue] == 1) { +// replyStr = [NSString stringWithFormat:@" and 1 reply"]; +// } else if ([[appDelegate.activeStory objectForKey:@"reply_count"] intValue] == 1) { +// replyStr = [NSString stringWithFormat:@" and %@ replies", [appDelegate.activeStory objectForKey:@"reply_count"]]; +// } + NSLog(@"[appDelegate.activeStory objectForKey:@'comment_count'] %@", [[appDelegate.activeStory objectForKey:@"comment_count"] class]); + if (![[appDelegate.activeStory objectForKey:@"comment_count"] isKindOfClass:[NSNull class]] && + [[appDelegate.activeStory objectForKey:@"comment_count"] intValue]) { + commentLabel = [commentLabel stringByAppendingString:[NSString stringWithFormat:@ + "
" + "%@" // comment count + //"%@" // reply count + "
" + "
" + "%@" // friend avatars + "%@" // public avatars + "
", + [[appDelegate.activeStory objectForKey:@"comment_count"] intValue] == 1 + ? [NSString stringWithFormat:@"1 comment"] : + [NSString stringWithFormat:@"%@ comments", [appDelegate.activeStory objectForKey:@"comment_count"]], + + //replyStr, + [self getAvatars:@"commented_by_friends"], + [self getAvatars:@"commented_by_public"]]]; + NSLog(@"commentLabel is %@", commentLabel); + } + + if (![[appDelegate.activeStory objectForKey:@"share_count"] isKindOfClass:[NSNull class]] && + [[appDelegate.activeStory objectForKey:@"share_count"] intValue]) { + shareLabel = [shareLabel stringByAppendingString:[NSString stringWithFormat:@ + + "
" + "
" + "%@" // friend avatars + "%@" // public avatars + "
" + "
" + "%@" // comment count + "
" + "
", + [self getAvatars:@"shared_by_public"], + [self getAvatars:@"shared_by_friends"], + [[appDelegate.activeStory objectForKey:@"share_count"] intValue] == 1 + ? [NSString stringWithFormat:@"1 share"] : + [NSString stringWithFormat:@"%@ shares", [appDelegate.activeStory objectForKey:@"share_count"]]]]; + NSLog(@"commentLabel is %@", commentLabel); + } + + if ([appDelegate.activeStory objectForKey:@"share_count"] != [NSNull null] && + [[appDelegate.activeStory objectForKey:@"share_count"] intValue] > 0) { + + comments = [comments stringByAppendingString:[NSString stringWithFormat:@ + "
" + "
" + "
" + "%@" + "%@" + "
", + commentLabel, + shareLabel + ]]; + + + + + comments = [comments stringByAppendingString:[NSString stringWithFormat:@""]]; + } + comments = [comments stringByAppendingString:[NSString stringWithFormat:@""]]; + return comments; +} + +- (NSString *)getComment:(NSDictionary *)commentDict { + + NSDictionary *user = [self getUser:[[commentDict objectForKey:@"user_id"] intValue]]; + NSString *userAvatarClass = @"NB-user-avatar"; + NSString *userReshareString = @""; + NSString *userEditButton = @""; + NSString *userLikeButton = @""; + NSString *commentUserId = [NSString stringWithFormat:@"%@", [commentDict objectForKey:@"user_id"]]; + NSString *currentUserId = [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"user_id"]]; + NSArray *likingUsers = [commentDict objectForKey:@"liking_users"]; + + + + if ([commentUserId isEqualToString:currentUserId]) { + userEditButton = [NSString stringWithFormat:@ + "
" + "
" + "Edit" + "
" + "
", + commentUserId]; + } else { + BOOL isInLikingUsers = NO; + for (int i = 0; i < likingUsers.count; i++) { + if ([[[likingUsers objectAtIndex:i] stringValue] isEqualToString:currentUserId]) { + isInLikingUsers = YES; + break; + } + } + + if (isInLikingUsers) { + userLikeButton = [NSString stringWithFormat:@ + "
" + "
" + "Favorited" + "
" + "
", + commentUserId]; + } else { + userLikeButton = [NSString stringWithFormat:@ + "
" + "
" + "Favorite" + "
" + "
", + commentUserId]; + } + + } + + if ([commentDict objectForKey:@"source_user_id"] != [NSNull null]) { + userAvatarClass = @"NB-user-avatar NB-story-comment-reshare"; + + NSDictionary *sourceUser = [self getUser:[[commentDict objectForKey:@"source_user_id"] intValue]]; + userReshareString = [NSString stringWithFormat:@ + "
" + "
" + "
" + "
" + "
", + [sourceUser objectForKey:@"photo_url"]]; + } + + NSString *commentContent = [self textToHtml:[commentDict objectForKey:@"comments"]]; + + NSString *comment; + NSString *locationHtml = @""; + NSString *location = [NSString stringWithFormat:@"%@", [user objectForKey:@"location"]]; + + if (location.length) { + locationHtml = [NSString stringWithFormat:@"
%@
", location]; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + comment = [NSString stringWithFormat:@ + "
" + "
" + "
" + " %@" + "
%@
" + " %@" // location + "
%@ ago
" + "
" + " %@" //User Like Button>" + " %@" //User Edit Button>" + "
" + "
" + " Reply" + "
" + "
" + "
" + "
" + "
%@
" + "%@" + "
", + [commentDict objectForKey:@"user_id"], + userAvatarClass, + [commentDict objectForKey:@"user_id"], + [user objectForKey:@"photo_url"], + userReshareString, + [user objectForKey:@"username"], + locationHtml, + [commentDict objectForKey:@"shared_date"], + userEditButton, + userLikeButton, + [commentDict objectForKey:@"user_id"], + [user objectForKey:@"username"], + commentContent, + [self getReplies:[commentDict objectForKey:@"replies"] forUserId:[commentDict objectForKey:@"user_id"]]]; + } else { + comment = [NSString stringWithFormat:@ + "
" + "
" + "
" + " %@" + "
%@
" + " %@" + "
%@ ago
" + "
" + "
%@
" + + "
" + " %@" //User Like Button>" + " %@" //User Edit Button>" + "
" + "
" + " Reply" + "
" + "
" + "
" + "%@" + "
", + [commentDict objectForKey:@"user_id"], + userAvatarClass, + [commentDict objectForKey:@"user_id"], + [user objectForKey:@"photo_url"], + userReshareString, + [user objectForKey:@"username"], + locationHtml, + [commentDict objectForKey:@"shared_date"], + commentContent, + userEditButton, + userLikeButton, + [commentDict objectForKey:@"user_id"], + [user objectForKey:@"username"], + [self getReplies:[commentDict objectForKey:@"replies"] forUserId:[commentDict objectForKey:@"user_id"]]]; + + } + + return comment; +} + +- (NSString *)getReplies:(NSArray *)replies forUserId:(NSString *)commentUserId { + NSString *repliesString = @""; + if (replies.count > 0) { + repliesString = [repliesString stringByAppendingString:@"
"]; + for (int i = 0; i < replies.count; i++) { + NSDictionary *replyDict = [replies objectAtIndex:i]; + NSDictionary *user = [self getUser:[[replyDict objectForKey:@"user_id"] intValue]]; + + NSString *userEditButton = @""; + NSString *replyUserId = [NSString stringWithFormat:@"%@", [replyDict objectForKey:@"user_id"]]; + NSString *replyId = [replyDict objectForKey:@"reply_id"]; + NSString *currentUserId = [NSString stringWithFormat:@"%@", [appDelegate.dictUserProfile objectForKey:@"user_id"]]; + + if ([replyUserId isEqualToString:currentUserId]) { + userEditButton = [NSString stringWithFormat:@ + "
" + "" + "
" + "Edit" + "
" + "
" + "
", + commentUserId, + replyUserId, + replyId + ]; + } + + NSString *replyContent = [self textToHtml:[replyDict objectForKey:@"comments"]]; + + NSString *locationHtml = @""; + NSString *location = [NSString stringWithFormat:@"%@", [user objectForKey:@"location"]]; + + if (location.length) { + locationHtml = [NSString stringWithFormat:@"
%@
", location]; + } + + NSString *reply; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + reply = [NSString stringWithFormat:@ + "
" + " " + " " + " " + "
%@
" + " %@" + "
%@ ago
" + "
" + " %@" //User Edit Button>" + "
" + "
%@
" + "
", + [replyDict objectForKey:@"reply_id"], + [user objectForKey:@"user_id"], + [user objectForKey:@"photo_url"], + [user objectForKey:@"username"], + locationHtml, + [replyDict objectForKey:@"publish_date"], + userEditButton, + replyContent]; + } else { + reply = [NSString stringWithFormat:@ + "
" + " " + " " + " " + "
%@
" + " %@" + "
%@ ago
" + "
%@
" + "
" + " %@" //User Edit Button>" + "
" + "
", + [replyDict objectForKey:@"reply_id"], + [user objectForKey:@"user_id"], + [user objectForKey:@"photo_url"], + [user objectForKey:@"username"], + locationHtml, + [replyDict objectForKey:@"publish_date"], + replyContent, + userEditButton]; + } + repliesString = [repliesString stringByAppendingString:reply]; + } + repliesString = [repliesString stringByAppendingString:@"
"]; + } + return repliesString; +} + +- (NSDictionary *)getUser:(int)user_id { + for (int i = 0; i < appDelegate.activeFeedUserProfiles.count; i++) { + if ([[[appDelegate.activeFeedUserProfiles objectAtIndex:i] objectForKey:@"user_id"] intValue] == user_id) { + return [appDelegate.activeFeedUserProfiles objectAtIndex:i]; + } + } + return nil; +} + +- (void)showStory { + appDelegate.inStoryDetail = YES; + NSLog(@"in showStory"); + // when we show story, we mark it as read + [self markStoryAsRead]; + self.noStorySelectedLabel.hidden = YES; + + + appDelegate.shareViewController.commentField.text = nil; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [appDelegate.masterContainerViewController transitionFromShareView]; + } + + self.webView.hidden = NO; + self.bottomPlaceholderToolbar.hidden = YES; + self.progressViewContainer.hidden = NO; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects: originalStoryButton, fontSettingsButton, nil]; + } + + [appDelegate hideShareView:YES]; + + [appDelegate resetShareComments]; + NSString *shareBarString = [self getShareBar]; + NSString *commentString = [self getComments]; + NSString *headerString; + NSString *sharingHtmlString; + NSString *footerString; + NSString *fontStyleClass = @""; + NSString *fontSizeClass = @""; + + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + if ([userPreferences stringForKey:@"fontStyle"]){ + fontStyleClass = [fontStyleClass stringByAppendingString:[userPreferences stringForKey:@"fontStyle"]]; + } else { + fontStyleClass = [fontStyleClass stringByAppendingString:@"NB-san-serif"]; + } + if ([userPreferences stringForKey:@"fontSizing"]){ + fontSizeClass = [fontSizeClass stringByAppendingString:[userPreferences stringForKey:@"fontSizing"]]; + } else { + fontSizeClass = [fontSizeClass stringByAppendingString:@"NB-medium"]; + } + + int contentWidth = self.view.frame.size.width; + NSString *contentWidthClass; + + if (contentWidth > 700) { + contentWidthClass = @"NB-ipad-wide"; + } else if (contentWidth > 480) { + contentWidthClass = @"NB-ipad-narrow"; + } else { + contentWidthClass = @"NB-iphone"; + } + + + // set up layout values based on iPad/iPhone + headerString = [NSString stringWithFormat:@ + "" + "" + "", + + + contentWidth]; + footerString = [NSString stringWithFormat:@ + "" + ""]; + + sharingHtmlString = [NSString stringWithFormat:@ + "
" + "
" + "" + "
"]; + NSString *story_author = @""; + if ([appDelegate.activeStory objectForKey:@"story_authors"]) { + NSString *author = [NSString stringWithFormat:@"%@", + [appDelegate.activeStory objectForKey:@"story_authors"]]; + if (author && ![author isEqualToString:@""]) { + story_author = [NSString stringWithFormat:@"",author]; + } + } + NSString *story_tags = @""; + if ([appDelegate.activeStory objectForKey:@"story_tags"]) { + NSArray *tag_array = [appDelegate.activeStory objectForKey:@"story_tags"]; + if ([tag_array count] > 0) { + story_tags = [NSString + stringWithFormat:@"", + [tag_array componentsJoinedByString:@"