Merge remote-tracking branch 'upstream/master' into profile

This commit is contained in:
Mark Anderson 2015-06-05 22:29:47 +01:00
commit 414a921f72
78 changed files with 16678 additions and 15530 deletions

View file

@ -322,6 +322,7 @@ class Profile(models.Model):
'API_USERNAME': settings.PAYPAL_API_USERNAME,
'API_PASSWORD': settings.PAYPAL_API_PASSWORD,
'API_SIGNATURE': settings.PAYPAL_API_SIGNATURE,
'API_CA_CERTS': False,
}
paypal = PayPalInterface(**paypal_opts)
transactions = PayPalIPN.objects.filter(custom=self.user.username,
@ -362,6 +363,7 @@ class Profile(models.Model):
'API_USERNAME': settings.PAYPAL_API_USERNAME,
'API_PASSWORD': settings.PAYPAL_API_PASSWORD,
'API_SIGNATURE': settings.PAYPAL_API_SIGNATURE,
'API_CA_CERTS': False,
}
paypal = PayPalInterface(**paypal_opts)
transaction = transactions[0]

View file

@ -19,6 +19,7 @@ urlpatterns = patterns('',
url(r'^payment_history/?', views.payment_history, name='profile-payment-history'),
url(r'^cancel_premium/?', views.cancel_premium, name='profile-cancel-premium'),
url(r'^refund_premium/?', views.refund_premium, name='profile-refund-premium'),
url(r'^never_expire_premium/?', views.never_expire_premium, name='profile-never-expire-premium'),
url(r'^upgrade_premium/?', views.upgrade_premium, name='profile-upgrade-premium'),
url(r'^update_payment_history/?', views.update_payment_history, name='profile-update-payment-history'),
url(r'^delete_account/?', views.delete_account, name='profile-delete-account'),

View file

@ -23,6 +23,7 @@ from apps.profile.forms import RedeemCodeForm
from apps.reader.forms import SignupForm, LoginForm
from apps.rss_feeds.models import MStarredStory, MStarredStoryCounts
from apps.social.models import MSocialServices, MActivity, MSocialProfile
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
from utils import json_functions as json
from utils.user_functions import ajax_login_required
from utils.view_functions import render_to
@ -407,6 +408,7 @@ def payment_history(request):
history = PaymentHistory.objects.filter(user=user)
statistics = {
"created_date": user.date_joined,
"last_seen_date": user.profile.last_seen_on,
"timezone": unicode(user.profile.timezone),
"stripe_id": user.profile.stripe_id,
@ -415,6 +417,12 @@ def payment_history(request):
"email": user.email,
"read_story_count": RUserStory.read_story_count(user.pk),
"feed_opens": UserSubscription.objects.filter(user=user).aggregate(sum=Sum('feed_opens'))['sum'],
"training": {
'title': MClassifierTitle.objects.filter(user_id=user.pk).count(),
'tag': MClassifierTag.objects.filter(user_id=user.pk).count(),
'author': MClassifierAuthor.objects.filter(user_id=user.pk).count(),
'feed': MClassifierFeed.objects.filter(user_id=user.pk).count(),
}
}
return {
@ -459,6 +467,19 @@ def upgrade_premium(request):
return {'code': 1 if upgraded else -1}
@staff_member_required
@ajax_login_required
@json.json_view
def never_expire_premium(request):
user_id = request.REQUEST.get('user_id')
user = User.objects.get(pk=user_id)
if user.profile.is_premium:
user.profile.premium_expire = None
user.profile.save()
return {'code': 1}
return {'code': -1}
@staff_member_required
@ajax_login_required
@json.json_view

View file

@ -423,9 +423,9 @@ class UserSubscription(models.Model):
@classmethod
def identify_deleted_feed_users(cls, old_feed_id):
users = UserSubscriptionFolders.objects.filter(folders__contains="5636682").only('user')
users = UserSubscriptionFolders.objects.filter(folders__contains=old_feed_id).only('user')
user_ids = [usf.user_id for usf in users]
f = open('users.txt', 'w')
f = open('utils/backups/users.txt', 'w')
f.write('\n'.join([str(u) for u in user_ids]))
return user_ids

View file

@ -194,7 +194,6 @@ class Feed(models.Model):
feed['tagline'] = self.data.feed_tagline
feed['feed_tags'] = json.decode(self.data.popular_tags) if self.data.popular_tags else []
feed['feed_authors'] = json.decode(self.data.popular_authors) if self.data.popular_authors else []
return feed
@ -622,6 +621,16 @@ class Feed(models.Model):
self.save()
return errors, non_errors
def count_redirects_in_history(self, fetch_type='feed', fetch_history=None):
logging.debug(' ---> [%-30s] Counting redirects in history...' % (unicode(self)[:30]))
if not fetch_history:
fetch_history = MFetchHistory.feed(self.pk)
fh = fetch_history[fetch_type+'_fetch_history']
redirects = [h for h in fh if h['status_code'] and int(h['status_code']) in (301, 302)]
non_redirects = [h for h in fh if h['status_code'] and int(h['status_code']) not in (301, 302)]
return redirects, non_redirects
def count_subscribers(self, verbose=False):
SUBSCRIBER_EXPIRE = datetime.datetime.now() - datetime.timedelta(days=settings.SUBSCRIBER_EXPIRE)

View file

@ -1840,6 +1840,7 @@ class MSharedStory(mongo.Document):
profile_user_ids = set()
for story in stories:
story['friend_comments'] = []
story['friend_shares'] = []
story['public_comments'] = []
story['reply_count'] = 0
if check_all or story['comment_count']:
@ -1895,6 +1896,23 @@ class MSharedStory(mongo.Document):
story['share_user_ids'] = story['friend_user_ids'] + story['public_user_ids']
if story.get('source_user_id'):
profile_user_ids.add(story['source_user_id'])
shared_stories = []
if story['shared_by_friends']:
params = {
'story_hash': story['story_hash'],
'user_id__in': story['shared_by_friends'],
}
shared_stories = cls.objects.filter(**params)
for shared_story in shared_stories:
comments = shared_story.comments_with_author()
story['reply_count'] += len(comments['replies'])
story['friend_shares'].append(comments)
profile_user_ids = profile_user_ids.union([reply['user_id']
for reply in comments['replies']])
if comments.get('source_user_id'):
profile_user_ids.add(comments['source_user_id'])
if comments.get('liking_users'):
profile_user_ids = profile_user_ids.union(comments['liking_users'])
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
profiles = [profile.canonical(compact=True) for profile in profiles]
@ -1920,7 +1938,7 @@ class MSharedStory(mongo.Document):
for u, user_id in enumerate(story['shared_by_public']):
if user_id not in profiles: continue
stories[s]['shared_by_public'][u] = profiles[user_id]
for comment_set in ['friend_comments', 'public_comments']:
for comment_set in ['friend_comments', 'public_comments', 'friend_shares']:
for c, comment in enumerate(story[comment_set]):
if comment['user_id'] not in profiles: continue
stories[s][comment_set][c]['user'] = profiles[comment['user_id']]
@ -2743,6 +2761,10 @@ class MInteraction(mongo.Document):
self.category, self.content and self.content[:20])
def canonical(self):
story_hash = None
if self.story_feed_id:
story_hash = MStory.ensure_story_hash(self.content_id, story_feed_id=self.story_feed_id)
return {
'date': self.date,
'category': self.category,
@ -2752,6 +2774,7 @@ class MInteraction(mongo.Document):
'feed_id': self.feed_id,
'story_feed_id': self.story_feed_id,
'content_id': self.content_id,
'story_hash': story_hash,
}
@classmethod
@ -2867,11 +2890,12 @@ class MInteraction(mongo.Document):
cls.publish_update_to_subscribers(user_id)
@classmethod
def new_comment_like(cls, liking_user_id, comment_user_id, story_id, story_title, comments):
def new_comment_like(cls, liking_user_id, comment_user_id, story_id, story_feed_id, story_title, comments):
cls.objects.get_or_create(user_id=comment_user_id,
with_user_id=liking_user_id,
category="comment_like",
feed_id="social:%s" % comment_user_id,
story_feed_id=story_feed_id,
content_id=story_id,
defaults={
"title": story_title,
@ -2974,6 +2998,10 @@ class MActivity(mongo.Document):
return "<%s> %s - %s" % (user.username, self.category, self.content and self.content[:20])
def canonical(self):
story_hash = None
if self.story_feed_id:
story_hash = MStory.ensure_story_hash(self.content_id, story_feed_id=self.story_feed_id)
return {
'date': self.date,
'category': self.category,
@ -2984,6 +3012,7 @@ class MActivity(mongo.Document):
'feed_id': self.feed_id or self.story_feed_id,
'story_feed_id': self.story_feed_id or self.feed_id,
'content_id': self.content_id,
'story_hash': story_hash,
}
@classmethod
@ -3109,11 +3138,12 @@ class MActivity(mongo.Document):
original.delete()
@classmethod
def new_comment_like(cls, liking_user_id, comment_user_id, story_id, story_title, comments):
def new_comment_like(cls, liking_user_id, comment_user_id, story_id, story_feed_id, story_title, comments):
cls.objects.get_or_create(user_id=liking_user_id,
with_user_id=comment_user_id,
category="comment_like",
feed_id="social:%s" % comment_user_id,
story_feed_id=story_feed_id,
content_id=story_id,
defaults={
"title": story_title,

View file

@ -1185,11 +1185,13 @@ def like_comment(request):
MActivity.new_comment_like(liking_user_id=request.user.pk,
comment_user_id=comment['user_id'],
story_id=story_id,
story_feed_id=feed_id,
story_title=shared_story.story_title,
comments=shared_story.comments)
MInteraction.new_comment_like(liking_user_id=request.user.pk,
comment_user_id=comment['user_id'],
story_id=story_id,
story_feed_id=feed_id,
story_title=shared_story.story_title,
comments=shared_story.comments)

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="93"
android:versionName="4.3.0b2" >
android:versionCode="94"
android:versionName="4.3.1b1" >
<uses-sdk
android:minSdkVersion="11"

View file

@ -1,26 +0,0 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# We're open-source; dont' obfuscate.
-dontobfuscate
-dontwarn com.squareup.okhttp.**
-dontwarn okio.**

View file

@ -7,8 +7,6 @@
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-21

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -133,15 +133,15 @@
<LinearLayout
android:id="@+id/comment_replies_container"
android:orientation="horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
<View
android:id="@+id/comment_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="5dp"
style="?storyCommentDivider" />
</LinearLayout>
</LinearLayout>

View file

@ -51,6 +51,7 @@
<LinearLayout
android:id="@+id/reading_friend_comment_header"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
@ -73,8 +74,7 @@
android:paddingBottom="5dp"
android:textStyle="bold"
android:textSize="10sp"
style="?commentsHeader"
/>
style="?commentsHeader"/>
<View
android:id="@+id/reading_friend_header_bottom_border"
@ -83,7 +83,7 @@
android:layout_below="@id/reading_friend_comment_total"
android:background="@color/lightgray"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/reading_friend_comment_container"
@ -92,15 +92,58 @@
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"/>
<LinearLayout
android:id="@+id/reading_friend_emptyshare_header"
android:layout_below="@id/reading_friend_comment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical">
<View
android:id="@+id/reading_friend_share_header_top_border"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:background="@color/lightgray"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:id="@+id/reading_friend_emptyshare_total"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textStyle="bold"
android:textSize="10sp"
style="?commentsHeader"/>
<View
android:id="@+id/reading_friend_share_header_bottom_border"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/reading_friend_comment_total"
android:background="@color/lightgray"/>
</LinearLayout>
<LinearLayout
android:id="@+id/reading_friend_emptyshare_container"
android:layout_below="@id/reading_friend_emptyshare_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"/>
<LinearLayout
android:id="@+id/reading_public_comment_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/comment_divider"
android:layout_below="@id/reading_friend_emptyshare_container"
android:visibility="gone"
android:orientation="vertical">
@ -139,9 +182,8 @@
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_below="@id/reading_public_comment_header"
android:orientation="vertical">
android:orientation="vertical"/>
</LinearLayout>
</RelativeLayout>
</merge>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_shared"
android:title="@string/menu_share"/>
<item android:id="@+id/menu_send_story"
android:title="@string/menu_send_story"/>
<item android:id="@+id/menu_mark_story_as_unread"
android:title="@string/menu_mark_unread" />

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_shared"
android:title="@string/menu_share"/>
<item android:id="@+id/menu_send_story"
android:title="@string/menu_send_story"/>
<item android:id="@+id/menu_mark_story_as_unread"
android:title="@string/menu_mark_unread" />

View file

@ -18,10 +18,9 @@
android:showAsAction="never"
android:title="@string/menu_original"/>
<item
android:id="@+id/menu_shared"
android:icon="@drawable/share"
android:id="@+id/menu_send_story"
android:showAsAction="never"
android:title="@string/menu_share"/>
android:title="@string/menu_send_story"/>
<item
android:id="@+id/menu_textsize"

View file

@ -22,7 +22,6 @@
<string name="loading">Loading…</string>
<string name="orig_text_loading">Fetching text…</string>
<string name="edit">Shared</string>
<string name="follow_error">There was a problem following the user. Check your internet connection and try again.</string>
<string name="unfollow_error">There was a problem unfollowing the user. Check your internet connection and try again.</string>
@ -46,21 +45,18 @@
<string name="read_stories_title">Read Stories</string>
<string name="saved_stories_title">Saved Stories</string>
<string name="now">now</string>
<string name="error_saving_classifier">There was an error. Check your internet connection.</string>
<string name="replied">Reply posted</string>
<string name="error_replying">There was an error replying</string>
<string name="share">\"%1$s\" - %2$s</string>
<string name="share_comment_hint">Comment (Optional)</string>
<string name="shared">Story shared</string>
<string name="error_sharing">There was an problem sharing this story.</string>
<string name="share_newsblur">Share \"%s\" to your Blurblog?</string>
<string name="share_this">SHARE THIS STORY</string>
<string name="already_shared">SHARED</string>
<string name="share_this_story">Share this story</string>
<string name="update_shared">Update comment</string>
<string name="save_this">SAVE THIS STORY</string>
<string name="unsave_this">UNSAVE THIS STORY</string>
<string name="share_this">SHARE THIS STORY</string>
<string name="overlay_next">NEXT</string>
<string name="overlay_done">DONE</string>
@ -99,7 +95,7 @@
<string name="menu_profile">Profile</string>
<string name="menu_refresh">Refresh</string>
<string name="menu_original">View original</string>
<string name="menu_share">Send to…</string>
<string name="menu_send_story">Send to…</string>
<string name="menu_mark_feed_as_read">Mark feed as read</string>
<string name="menu_delete_feed">Delete feed</string>
<string name="menu_mark_folder_as_read">Mark folder as read</string>
@ -148,11 +144,9 @@
<string name="add_facebook">Add Facebook friends</string>
<string name="need_to_login"><u>I need to log in!</u></string>
<string name="need_to_register"><u>I need to register</u></string>
<string name="share_this_story">Share this story</string>
<string name="comment_favourited">Comment favorited</string>
<string name="error_liking_comment">Error favoriting comment</string>
<string name="public_comment_count">%d PUBLIC COMMENTS</string>
<string name="friends_comments_count">%d COMMENTS</string>
<string name="friends_shares_count">%d SHARES</string>
<string name="unknown_user">Unknown User</string>
<string name="delete_feed_message">Delete feed \&quot;%s\&quot;?</string>

View file

@ -17,6 +17,9 @@ import android.view.Window;
import android.widget.AbsListView;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.FindView;
import com.newsblur.R;
import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
import com.newsblur.fragment.FolderListFragment;
@ -35,10 +38,10 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
private FolderListFragment folderFeedList;
private FragmentManager fragmentManager;
private TextView overlayStatusText;
private boolean isLightTheme;
private SwipeRefreshLayout swipeLayout;
private boolean wasSwipeEnabled = false;
@FindView(R.id.main_sync_status) TextView overlayStatusText;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -52,6 +55,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_container);
@ -63,8 +68,6 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
folderFeedList.setRetainInstance(true);
((FeedIntelligenceSelectorFragment) fragmentManager.findFragmentByTag("feedIntelligenceSelector")).setState(folderFeedList.currentState);
this.overlayStatusText = (TextView) findViewById(R.id.main_sync_status);
// make sure the interval sync is scheduled, since we are the root Activity
BootReceiver.scheduleSyncService(this);
}

View file

@ -27,6 +27,9 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
import butterknife.ButterKnife;
import butterknife.FindView;
import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.fragment.ReadingItemFragment;
@ -73,11 +76,17 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
protected final Object STORIES_MUTEX = new Object();
protected Cursor stories;
private View contentView; // we use this a ton, so cache it
protected ViewPager pager;
protected Button overlayLeft, overlayRight;
protected ProgressBar overlayProgress, overlayProgressRight, overlayProgressLeft;
protected Button overlayText, overlaySend;
@FindView(android.R.id.content) View contentView; // we use this a ton, so cache it
@FindView(R.id.reading_overlay_left) Button overlayLeft;
@FindView(R.id.reading_overlay_right) Button overlayRight;
@FindView(R.id.reading_overlay_progress) ProgressBar overlayProgress;
@FindView(R.id.reading_overlay_progress_right) ProgressBar overlayProgressRight;
@FindView(R.id.reading_overlay_progress_left) ProgressBar overlayProgressLeft;
@FindView(R.id.reading_overlay_text) Button overlayText;
@FindView(R.id.reading_overlay_send) Button overlaySend;
ViewPager pager;
protected FragmentManager fragmentManager;
protected ReadingAdapter readingAdapter;
private boolean stopLoading;
@ -104,14 +113,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
super.onCreate(savedInstanceBundle);
setContentView(R.layout.activity_reading);
this.contentView = findViewById(android.R.id.content);
this.overlayLeft = (Button) findViewById(R.id.reading_overlay_left);
this.overlayRight = (Button) findViewById(R.id.reading_overlay_right);
this.overlayProgress = (ProgressBar) findViewById(R.id.reading_overlay_progress);
this.overlayProgressRight = (ProgressBar) findViewById(R.id.reading_overlay_progress_right);
this.overlayProgressLeft = (ProgressBar) findViewById(R.id.reading_overlay_progress_left);
this.overlayText = (Button) findViewById(R.id.reading_overlay_text);
this.overlaySend = (Button) findViewById(R.id.reading_overlay_send);
ButterKnife.bind(this);
fragmentManager = getFragmentManager();
@ -221,7 +223,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
}
private void setupPager() {
pager = (ViewPager) findViewById(R.id.reading_pager);
pager = (ViewPager) findViewById(R.id.reading_pager);
pager.setPageMargin(UIUtils.convertDPsToPixels(getApplicationContext(), 1));
if (PrefsUtils.isLightThemeSelected(this)) {
pager.setPageMarginDrawable(R.drawable.divider_light);
@ -285,11 +287,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
startActivity(i);
return true;
} else if (item.getItemId() == R.id.menu_reading_sharenewsblur) {
DialogFragment newFragment = ShareDialogFragment.newInstance(getReadingFragment(), story, getReadingFragment().previouslySavedShareText, readingAdapter.getSourceUserId());
DialogFragment newFragment = ShareDialogFragment.newInstance(story, readingAdapter.getSourceUserId());
newFragment.show(getFragmentManager(), "dialog");
return true;
} else if (item.getItemId() == R.id.menu_shared) {
FeedUtils.shareStory(story, this);
} else if (item.getItemId() == R.id.menu_send_story) {
FeedUtils.sendStory(story, this);
return true;
} else if (item.getItemId() == R.id.menu_textsize) {
TextSizeDialogFragment textSize = TextSizeDialogFragment.newInstance(PrefsUtils.getTextSize(this));
@ -592,9 +594,18 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
unreadSearchStarted = true;
}
int candidate = 0;
boolean unreadFound = false;
// start searching just after the current story
int candidate = pager.getCurrentItem() + 1;
unreadSearch:while (!unreadFound) {
// if we've reached the end of the list, loop back to the beginning
if (candidate >= readingAdapter.getCount()) {
candidate = 0;
}
// if we have looped all the way around to the story we are on, there aren't any left
if (candidate == pager.getCurrentItem()) {
break unreadSearch;
}
Story story = readingAdapter.getStory(candidate);
if (this.stopLoading) {
// this activity was ended before we finished. just stop.
@ -603,13 +614,14 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
}
// iterate through the stories in our cursor until we find an unread one
if (story != null) {
if ((candidate == pager.getCurrentItem()) || (story.read) ) {
if (story.read) {
candidate++;
continue unreadSearch;
} else {
unreadFound = true;
}
}
// if we didn't continue or break, the cursor probably changed out from under us, so stop.
break unreadSearch;
}
@ -632,7 +644,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
} else {
// trigger a check to see if there are any more to search before proceeding. By leaving the
// unreadSearchActive flag high, this method will be called again when a new cursor is loaded
this.checkStoryCount(candidate+1);
this.checkStoryCount(readingAdapter.getCount()+1);
}
}
}
@ -682,7 +694,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
public void overlaySend(View v) {
if ((readingAdapter == null) || (pager == null)) return;
Story story = readingAdapter.getStory(pager.getCurrentItem());
FeedUtils.shareStory(story, this);
FeedUtils.sendStory(story, this);
}
public void overlayText(View v) {

View file

@ -31,7 +31,9 @@ import com.newsblur.util.ReadFilter;
import com.newsblur.util.StateFilter;
import com.newsblur.util.StoryOrder;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -137,9 +139,14 @@ public class BlurDatabaseHelper {
}
public void cleanupFeedsFolders() {
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FEED_TABLE, null, null);}
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.FOLDER_TABLE, null, null);}
synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_TABLE, null, null);}
synchronized (RW_MUTEX) {
dbRW.delete(DatabaseConstants.FEED_TABLE, null, null);
dbRW.delete(DatabaseConstants.FOLDER_TABLE, null, null);
dbRW.delete(DatabaseConstants.SOCIALFEED_TABLE, null, null);
dbRW.delete(DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE, null, null);
dbRW.delete(DatabaseConstants.COMMENT_TABLE, null, null);
dbRW.delete(DatabaseConstants.REPLY_TABLE, null, null);
}
}
public void vacuum() {
@ -320,31 +327,70 @@ public class BlurDatabaseHelper {
// handle comments
List<ContentValues> commentValues = new ArrayList<ContentValues>();
List<ContentValues> replyValues = new ArrayList<ContentValues>();
// track which comments were seen, so replies can be cleared before re-insertion. there isn't
// enough data to de-dupe them for an insert/update operation
List<String> freshCommentIds = new ArrayList<String>();
for (Story story : apiResponse.stories) {
for (Comment comment : story.publicComments) {
comment.storyId = story.id;
comment.id = TextUtils.concat(story.id, story.feedId, comment.userId).toString();
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
}
for (Comment comment : story.friendsComments) {
comment.storyId = story.id;
comment.id = TextUtils.concat(story.id, story.feedId, comment.userId).toString();
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
comment.byFriend = true;
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
}
for (Comment comment : story.friendsShares) {
comment.isPseudo = true;
comment.storyId = story.id;
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
comment.byFriend = true;
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
}
}
deleteRepliesForComments(freshCommentIds);
bulkInsertValues(DatabaseConstants.COMMENT_TABLE, commentValues);
bulkInsertValues(DatabaseConstants.REPLY_TABLE, replyValues);
}
private void deleteRepliesForComments(Collection<String> commentIds) {
// NB: attempting to do this with a "WHERE col IN (vector)" for speed can cause errors on some versions of sqlite
synchronized (RW_MUTEX) {
dbRW.beginTransaction();
try {
for (String commentId : commentIds) {
dbRW.delete(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_COMMENTID + " = ?", new String[]{commentId});
}
dbRW.setTransactionSuccessful();
} finally {
dbRW.endTransaction();
}
}
}
public Folder getFolder(String folderName) {
String[] selArgs = new String[] {folderName};
String selection = DatabaseConstants.FOLDER_NAME + " = ?";
@ -623,6 +669,33 @@ public class BlurDatabaseHelper {
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
}
public void setStoryShared(String hash) {
// get a fresh copy of the story from the DB so we can append to the shared ID set
Cursor c = dbRO.query(DatabaseConstants.STORY_TABLE,
new String[]{DatabaseConstants.STORY_SHARED_USER_IDS},
DatabaseConstants.STORY_HASH + " = ?",
new String[]{hash},
null, null, null);
if ((c == null)||(c.getCount() < 1)) {
Log.w(this.getClass().getName(), "story removed before finishing mark-shared");
closeQuietly(c);
return;
}
c.moveToFirst();
String[] sharedUserIds = TextUtils.split(c.getString(c.getColumnIndex(DatabaseConstants.STORY_SHARED_USER_IDS)), ",");
closeQuietly(c);
// the new id to append to the shared list (the current user)
String currentUser = PrefsUtils.getUserDetails(context).id;
// append to set and update DB
Set<String> newIds = new HashSet<String>(Arrays.asList(sharedUserIds));
newIds.add(currentUser);
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_SHARED_USER_IDS, TextUtils.join(",", newIds));
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
}
public String getStoryText(String hash) {
String q = "SELECT " + DatabaseConstants.STORY_TEXT_STORY_TEXT +
" FROM " + DatabaseConstants.STORY_TEXT_TABLE +
@ -852,7 +925,7 @@ public class BlurDatabaseHelper {
public List<Comment> getComments(String storyId) {
String[] selArgs = new String[] {storyId};
String selection = DatabaseConstants.COMMENT_STORYID + " = ?";
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, DatabaseConstants.COMMENT_COLUMNS, selection, selArgs, null, null, null);
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, null, selection, selArgs, null, null, null);
List<Comment> comments = new ArrayList<Comment>(c.getCount());
while (c.moveToNext()) {
comments.add(Comment.fromCursor(c));
@ -864,7 +937,7 @@ public class BlurDatabaseHelper {
public Comment getComment(String storyId, String userId) {
String selection = DatabaseConstants.COMMENT_STORYID + " = ? AND " + DatabaseConstants.COMMENT_USERID + " = ?";
String[] selArgs = new String[] {storyId, userId};
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, DatabaseConstants.COMMENT_COLUMNS, selection, selArgs, null, null, null);
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE, null, selection, selArgs, null, null, null);
if (c.getCount() < 1) return null;
c.moveToFirst();
Comment comment = Comment.fromCursor(c);
@ -872,6 +945,54 @@ public class BlurDatabaseHelper {
return comment;
}
public void insertUpdateComment(String storyId, String feedId, String commentText) {
// we can only insert comments as the currently logged-in user
String userId = PrefsUtils.getUserDetails(context).id;
Comment comment = new Comment();
comment.id = Comment.constructId(storyId, feedId, userId);
comment.storyId = storyId;
comment.userId = userId;
comment.commentText = commentText;
comment.byFriend = true;
if (TextUtils.isEmpty(commentText)) {
comment.isPseudo = true;
}
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.COMMENT_TABLE, null, comment.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
}
public void setCommentLiked(String storyId, String userId, String feedId, boolean liked) {
String commentKey = Comment.constructId(storyId, feedId, userId);
// get a fresh copy of the story from the DB so we can append to the shared ID set
Cursor c = dbRO.query(DatabaseConstants.COMMENT_TABLE,
new String[]{DatabaseConstants.COMMENT_LIKING_USERS},
DatabaseConstants.COMMENT_ID + " = ?",
new String[]{commentKey},
null, null, null);
if ((c == null)||(c.getCount() < 1)) {
Log.w(this.getClass().getName(), "story removed before finishing mark-shared");
closeQuietly(c);
return;
}
c.moveToFirst();
String[] likingUserIds = TextUtils.split(c.getString(c.getColumnIndex(DatabaseConstants.COMMENT_LIKING_USERS)), ",");
closeQuietly(c);
// the new id to append/remove from the liking list (the current user)
String currentUser = PrefsUtils.getUserDetails(context).id;
// append to set and update DB
Set<String> newIds = new HashSet<String>(Arrays.asList(likingUserIds));
if (liked) {
newIds.add(currentUser);
} else {
newIds.remove(currentUser);
}
ContentValues values = new ContentValues();
values.put(DatabaseConstants.COMMENT_LIKING_USERS, TextUtils.join(",", newIds));
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.COMMENT_TABLE, values, DatabaseConstants.COMMENT_ID + " = ?", new String[]{commentKey});}
}
public UserProfile getUserProfile(String userId) {
String[] selArgs = new String[] {userId};
String selection = DatabaseConstants.USER_USERID + " = ?";
@ -884,7 +1005,7 @@ public class BlurDatabaseHelper {
public List<Reply> getCommentReplies(String commentId) {
String[] selArgs = new String[] {commentId};
String selection = DatabaseConstants.REPLY_COMMENTID+ " = ?";
Cursor c = dbRO.query(DatabaseConstants.REPLY_TABLE, DatabaseConstants.REPLY_COLUMNS, selection, selArgs, null, null, DatabaseConstants.REPLY_DATE + " DESC");
Cursor c = dbRO.query(DatabaseConstants.REPLY_TABLE, null, selection, selArgs, null, null, DatabaseConstants.REPLY_DATE + " ASC");
List<Reply> replies = new ArrayList<Reply>(c.getCount());
while (c.moveToNext()) {
replies.add(Reply.fromCursor(c));
@ -893,6 +1014,16 @@ public class BlurDatabaseHelper {
return replies;
}
public void replyToComment(String storyId, String feedId, String commentUserId, String replyText) {
Reply reply = new Reply();
reply.commentId = Comment.constructId(storyId, feedId, commentUserId);
reply.text = replyText;
reply.userId = PrefsUtils.getUserDetails(context).id;
reply.date = new Date();
reply.id = reply.constructId();
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.REPLY_TABLE, null, reply.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
}
public static void closeQuietly(Cursor c) {
if (c == null) return;
try {c.close();} catch (Exception e) {;}

View file

@ -113,6 +113,7 @@ public class DatabaseConstants {
public static final String COMMENT_SHAREDDATE = "comment_shareddate";
public static final String COMMENT_BYFRIEND = "comment_byfriend";
public static final String COMMENT_USERID = "comment_userid";
public static final String COMMENT_ISPSEUDO = "comment_ispseudo";
public static final String REPLY_TABLE = "comment_replies";
public static final String REPLY_ID = BaseColumns._ID;
@ -131,11 +132,17 @@ public class DatabaseConstants {
public static final String ACTION_UNSAVE = "unsave";
public static final String ACTION_SHARE = "share";
public static final String ACTION_UNSHARE = "unshare";
public static final String ACTION_COMMENT = "comment";
public static final String ACTION_LIKE_COMMENT = "like_comment";
public static final String ACTION_UNLIKE_COMMENT = "unlike_comment";
public static final String ACTION_REPLY = "reply";
public static final String ACTION_COMMENT_TEXT = "comment_text";
public static final String ACTION_STORY_HASH = "story_hash";
public static final String ACTION_FEED_ID = "feed_id";
public static final String ACTION_INCLUDE_OLDER = "include_older";
public static final String ACTION_INCLUDE_NEWER = "include_newer";
public static final String ACTION_STORY_ID = "story_id";
public static final String ACTION_SOURCE_USER_ID = "source_user_id";
public static final String ACTION_COMMENT_ID = "comment_id";
static final String FOLDER_SQL = "CREATE TABLE " + FOLDER_TABLE + " (" +
FOLDER_NAME + TEXT + " PRIMARY KEY, " +
@ -189,7 +196,8 @@ public class DatabaseConstants {
COMMENT_BYFRIEND + TEXT + ", " +
COMMENT_STORYID + TEXT + ", " +
COMMENT_TEXT + TEXT + ", " +
COMMENT_USERID + TEXT +
COMMENT_USERID + TEXT + ", " +
COMMENT_ISPSEUDO + TEXT +
")";
static final String REPLY_SQL = "CREATE TABLE " + REPLY_TABLE + " (" +
@ -266,11 +274,17 @@ public class DatabaseConstants {
ACTION_UNSAVE + INTEGER + " DEFAULT 0, " +
ACTION_SHARE + INTEGER + " DEFAULT 0, " +
ACTION_UNSHARE + INTEGER + " DEFAULT 0, " +
ACTION_COMMENT + TEXT + ", " +
ACTION_LIKE_COMMENT + INTEGER + " DEFAULT 0, " +
ACTION_UNLIKE_COMMENT + INTEGER + " DEFAULT 0, " +
ACTION_REPLY + INTEGER + " DEFAULT 0, " +
ACTION_COMMENT_TEXT + TEXT + ", " +
ACTION_STORY_HASH + TEXT + ", " +
ACTION_FEED_ID + TEXT + ", " +
ACTION_INCLUDE_OLDER + INTEGER + ", " +
ACTION_INCLUDE_NEWER + INTEGER +
ACTION_INCLUDE_NEWER + INTEGER + ", " +
ACTION_STORY_ID + TEXT + ", " +
ACTION_SOURCE_USER_ID + TEXT + ", " +
ACTION_COMMENT_ID + TEXT +
")";
public static final String[] FEED_COLUMNS = {
@ -282,14 +296,6 @@ public class DatabaseConstants {
SOCIAL_FEED_ID, SOCIAL_FEED_USERNAME, SOCIAL_FEED_TITLE, SOCIAL_FEED_ICON, SOCIAL_FEED_POSITIVE_COUNT, SOCIAL_FEED_NEUTRAL_COUNT, SOCIAL_FEED_NEGATIVE_COUNT
};
public static final String[] COMMENT_COLUMNS = {
COMMENT_ID, COMMENT_STORYID, COMMENT_TEXT, COMMENT_BYFRIEND, COMMENT_USERID, COMMENT_DATE, COMMENT_LIKING_USERS, COMMENT_SHAREDDATE, COMMENT_SOURCE_USERID
};
public static final String[] REPLY_COLUMNS = {
REPLY_COMMENTID, REPLY_DATE, REPLY_ID, REPLY_SHORTDATE, REPLY_TEXT, REPLY_USERID
};
public static final String SUM_STORY_TOTAL = "storyTotal";
private static String STORY_SUM_TOTAL = " CASE " +
"WHEN MAX(" + STORY_INTELLIGENCE_AUTHORS + "," + STORY_INTELLIGENCE_TAGS + "," + STORY_INTELLIGENCE_TITLE + ") > 0 " +

View file

@ -12,6 +12,7 @@ import com.newsblur.database.DatabaseConstants;
public class Comment implements Serializable {
private static final long serialVersionUID = -2018705258520565390L;
// we almost always override API version with sensible PK constructed by concating story, feed, and user IDs
public String id;
@SerializedName("comments")
@ -36,8 +37,12 @@ public class Comment implements Serializable {
public String storyId;
// not vended by API, but we set it depending on which comment block of the response in which it appeared
public boolean byFriend = false;
// means this "comment" is actually a text-less share, which is identical to a comment, but included in a different list in the story member
public boolean isPseudo = false;
public ContentValues getValues() {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.COMMENT_DATE, date);
@ -49,11 +54,11 @@ public class Comment implements Serializable {
values.put(DatabaseConstants.COMMENT_SOURCE_USERID, sourceUserId);
values.put(DatabaseConstants.COMMENT_USERID, userId);
values.put(DatabaseConstants.COMMENT_ID, id);
values.put(DatabaseConstants.COMMENT_ISPSEUDO, isPseudo ? "true" : "false");
return values;
}
public static Comment fromCursor(final Cursor cursor) {
Comment comment = new Comment();
comment.date = cursor.getString(cursor.getColumnIndex(DatabaseConstants.COMMENT_DATE));
comment.sharedDate = cursor.getString(cursor.getColumnIndex(DatabaseConstants.COMMENT_SHAREDDATE));
@ -65,8 +70,12 @@ public class Comment implements Serializable {
comment.likingUsers = TextUtils.split(likingUsers, ",");
comment.sourceUserId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.COMMENT_SOURCE_USERID));
comment.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.COMMENT_ID));
comment.isPseudo = Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(DatabaseConstants.COMMENT_ISPSEUDO)));
return comment;
}
}
public static String constructId(String storyId, String feedId, String userId) {
return TextUtils.concat(feedId, storyId, userId).toString();
}
}

View file

@ -4,6 +4,7 @@ import java.util.Date;
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
@ -24,6 +25,7 @@ public class Reply {
@SerializedName("date")
public Date date;
// NB: this is the commentId that we generate, not the API one
public String commentId;
public ContentValues getValues() {
@ -47,4 +49,9 @@ public class Reply {
reply.userId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.REPLY_USERID));
return reply;
}
}
// construct a string we can internally use as a PK
public String constructId() {
return TextUtils.concat(commentId, "_", Long.toString(date.getTime())).toString();
}
}

View file

@ -79,6 +79,10 @@ public class Story implements Serializable {
@SerializedName("friend_comments")
public Comment[] friendsComments;
// these are pseudo-comments that allow replying to empty shares
@SerializedName("friend_shares")
public Comment[] friendsShares;
@SerializedName("intelligence")
public Intelligence intelligence = new Intelligence();

View file

@ -12,6 +12,10 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.domain.Classifier;
import com.newsblur.util.FeedUtils;
@ -25,12 +29,16 @@ public class ClassifierDialogFragment extends DialogFragment {
private static final String CALLBACK = "callback";
private static final String CLASSIFIER = "classifier";
@FindView(R.id.tag_negative) ImageView classifyNegative;
@FindView(R.id.tag_positive) ImageView classifyPositive;
@FindView(R.id.dialog_message) TextView message;
private String key, feedId;
private Classifier classifier;
private int classifierType;
private TagUpdateCallback tagCallback;
private Map<String,Integer> typeHashMap;
public static ClassifierDialogFragment newInstance(TagUpdateCallback callbackInterface, final String feedId, final Classifier classifier, final String key, final int classifierType) {
ClassifierDialogFragment frag = new ClassifierDialogFragment();
@ -64,49 +72,10 @@ public class ClassifierDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
View v = inflater.inflate(R.layout.fragment_classify_dialog, container, false);
final TextView message = (TextView) v.findViewById(R.id.dialog_message);
message.setText(key);
ButterKnife.bind(this, v);
final ImageView classifyPositive = (ImageView) v.findViewById(R.id.tag_positive);
final ImageView classifyNegative = (ImageView) v.findViewById(R.id.tag_negative);
typeHashMap = classifier.getMapForType(classifierType);
final Map<String,Integer> typeHashMap = classifier.getMapForType(classifierType);
setupTypeUI(classifyPositive, classifyNegative, typeHashMap);
classifyNegative.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int classifierAction = (typeHashMap.containsKey(key) && typeHashMap.get(key) == Classifier.DISLIKE) ? Classifier.CLEAR_DISLIKE : Classifier.DISLIKE;
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, classifierAction, getActivity());
tagCallback.updateTagView(key, classifierType,classifierAction);
ClassifierDialogFragment.this.dismiss();
}
});
message.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, Classifier.CLEAR_LIKE, getActivity());
tagCallback.updateTagView(key, classifierType, Classifier.CLEAR_LIKE);
ClassifierDialogFragment.this.dismiss();
}
});
classifyPositive.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int classifierAction = (typeHashMap.containsKey(key) && typeHashMap.get(key) == Classifier.LIKE) ? Classifier.CLEAR_LIKE : Classifier.LIKE;
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, classifierAction, getActivity());
tagCallback.updateTagView(key, classifierType,classifierAction);
ClassifierDialogFragment.this.dismiss();
}
});
return v;
}
private void setupTypeUI(final ImageView classifyPositive, final ImageView classifyNegative, final Map<String, Integer> typeHashMap) {
if (typeHashMap != null && typeHashMap.containsKey(key)) {
switch (typeHashMap.get(key)) {
case Classifier.LIKE:
@ -116,8 +85,33 @@ public class ClassifierDialogFragment extends DialogFragment {
classifyNegative.setImageResource(R.drawable.tag_negative_already);
break;
}
}
}
message.setText(key);
return v;
}
@OnClick(R.id.tag_negative) void onClickNegative() {
int classifierAction = (typeHashMap.containsKey(key) && typeHashMap.get(key) == Classifier.DISLIKE) ? Classifier.CLEAR_DISLIKE : Classifier.DISLIKE;
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, classifierAction, getActivity());
tagCallback.updateTagView(key, classifierType,classifierAction);
ClassifierDialogFragment.this.dismiss();
}
@OnClick(R.id.tag_positive) void onClickPositive() {
int classifierAction = (typeHashMap.containsKey(key) && typeHashMap.get(key) == Classifier.LIKE) ? Classifier.CLEAR_LIKE : Classifier.LIKE;
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, classifierAction, getActivity());
tagCallback.updateTagView(key, classifierType,classifierAction);
ClassifierDialogFragment.this.dismiss();
}
@OnClick(R.id.dialog_message) void onClickNeutral() {
FeedUtils.updateClassifier(feedId, key, classifier, classifierType, Classifier.CLEAR_LIKE, getActivity());
tagCallback.updateTagView(key, classifierType, Classifier.CLEAR_LIKE);
ClassifierDialogFragment.this.dismiss();
}
public interface TagUpdateCallback extends Serializable{
public void updateTagView(String value, int classifierType, int classifierAction);

View file

@ -10,6 +10,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.util.DefaultFeedView;
import com.newsblur.util.DefaultFeedViewChangedListener;
@ -17,10 +21,12 @@ import com.newsblur.util.DefaultFeedViewChangedListener;
/**
* Created by mark on 09/01/2014.
*/
public class DefaultFeedViewDialogFragment extends DialogFragment implements View.OnClickListener {
public class DefaultFeedViewDialogFragment extends DialogFragment {
private static String CURRENT_VIEW = "currentView";
private DefaultFeedView currentValue;
@FindView(R.id.radio_story) RadioButton storyButton;
@FindView(R.id.radio_text) RadioButton textButton;
public static DefaultFeedViewDialogFragment newInstance(DefaultFeedView currentValue) {
DefaultFeedViewDialogFragment dialog = new DefaultFeedViewDialogFragment();
@ -40,11 +46,9 @@ public class DefaultFeedViewDialogFragment extends DialogFragment implements Vie
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = (DefaultFeedView) getArguments().getSerializable(CURRENT_VIEW);
View v = inflater.inflate(R.layout.defaultfeedview_dialog, null);
RadioButton storyButton = (RadioButton) v.findViewById(R.id.radio_story);
storyButton.setOnClickListener(this);
ButterKnife.bind(this, v);
storyButton.setChecked(currentValue == DefaultFeedView.STORY);
RadioButton textButton = (RadioButton) v.findViewById(R.id.radio_text);
textButton.setOnClickListener(this);
textButton.setChecked(currentValue == DefaultFeedView.TEXT);
getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);
@ -54,19 +58,18 @@ public class DefaultFeedViewDialogFragment extends DialogFragment implements Vie
return v;
}
@Override
public void onClick(View v) {
DefaultFeedViewChangedListener listener = (DefaultFeedViewChangedListener)getActivity();
if (v.getId() == R.id.radio_story) {
if (currentValue == DefaultFeedView.TEXT) {
listener.defaultFeedViewChanged(DefaultFeedView.STORY);
}
} else {
if (currentValue == DefaultFeedView.STORY) {
listener.defaultFeedViewChanged(DefaultFeedView.TEXT);
}
@OnClick(R.id.radio_story) void selectStory() {
if (currentValue != DefaultFeedView.STORY) {
((DefaultFeedViewChangedListener) getActivity()).defaultFeedViewChanged(DefaultFeedView.STORY);
}
dismiss();
}
@OnClick(R.id.radio_text) void selectText() {
if (currentValue != DefaultFeedView.TEXT) {
((DefaultFeedViewChangedListener) getActivity()).defaultFeedViewChanged(DefaultFeedView.TEXT);
}
dismiss();
}
}

View file

@ -19,8 +19,13 @@ import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnChildClick;
import butterknife.OnGroupClick;
import butterknife.OnGroupCollapse;
import butterknife.OnGroupExpand;
import com.newsblur.R;
import com.newsblur.activity.AllStoriesItemsList;
@ -42,12 +47,8 @@ import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
public class FolderListFragment extends NbFragment implements OnGroupClickListener,
OnChildClickListener,
OnCreateContextMenuListener,
LoaderManager.LoaderCallbacks<Cursor>,
ExpandableListView.OnGroupCollapseListener,
ExpandableListView.OnGroupExpandListener {
public class FolderListFragment extends NbFragment implements OnCreateContextMenuListener,
LoaderManager.LoaderCallbacks<Cursor> {
private static final int SOCIALFEEDS_LOADER = 1;
private static final int FOLDERS_LOADER = 2;
@ -57,7 +58,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
private FolderListAdapter adapter;
public StateFilter currentState = StateFilter.SOME;
private SharedPreferences sharedPreferences;
private ExpandableListView list;
@FindView(R.id.folderfeed_list) ExpandableListView list;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -139,7 +140,8 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_folderfeedlist, container);
list = (ExpandableListView) v.findViewById(R.id.folderfeed_list);
ButterKnife.bind(this, v);
list.setGroupIndicator(getResources().getDrawable(R.drawable.transparent));
list.setOnCreateContextMenuListener(this);
@ -150,10 +152,6 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
list.setChildDivider(getActivity().getResources().getDrawable(R.drawable.divider_light));
list.setAdapter(adapter);
list.setOnGroupClickListener(this);
list.setOnChildClickListener(this);
list.setOnGroupCollapseListener(this);
list.setOnGroupExpandListener(this);
// Main activity needs to listen for scrolls to prevent refresh from firing unnecessarily
list.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity());
@ -249,8 +247,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
hasUpdated();
}
@Override
public boolean onGroupClick(ExpandableListView list, View group, int groupPosition, long id) {
@OnGroupClick(R.id.folderfeed_list) boolean onGroupClick(ExpandableListView list, View group, int groupPosition, long id) {
if (adapter.isFolderRoot(groupPosition)) {
Intent i = new Intent(getActivity(), AllStoriesItemsList.class);
i.putExtra(ItemsList.EXTRA_STATE, currentState);
@ -279,8 +276,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
}
}
@Override
public void onGroupExpand(int groupPosition) {
@OnGroupExpand(R.id.folderfeed_list) void onGroupExpand(int groupPosition) {
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
@ -296,8 +292,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
checkOpenFolderPreferences();
}
@Override
public void onGroupCollapse(int groupPosition) {
@OnGroupCollapse(R.id.folderfeed_list) void onGroupCollapse(int groupPosition) {
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
@ -311,8 +306,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
adapter.forceRecount();
}
@Override
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
@OnChildClick(R.id.folderfeed_list) boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
String childName = adapter.getChild(groupPosition, childPosition);
if (groupPosition == FolderListAdapter.ALL_SHARED_STORIES_GROUP_POSITION) {
SocialFeed socialFeed = adapter.getSocialFeed(childName);

View file

@ -24,6 +24,9 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.FindView;
import com.newsblur.R;
import com.newsblur.activity.ItemsList;
import com.newsblur.database.DatabaseConstants;
@ -43,7 +46,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
public static int ITEMLIST_LOADER = 0x01;
protected ItemsList activity;
protected ListView itemList;
@FindView(R.id.itemlistfragment_list) ListView itemList;
protected StoryItemsAdapter adapter;
protected DefaultFeedView defaultFeedView;
protected StateFilter currentState;
@ -55,7 +58,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
// loading indicator for when stories are present and fresh (at bottom of list)
protected ProgressThrobber footerProgressView;
// loading indicator for when no stories are loaded yet (instead of list)
protected ProgressThrobber emptyProgressView;
@FindView(R.id.empty_view_loading_throb) ProgressThrobber emptyProgressView;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -68,8 +71,8 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_itemlist, null);
itemList = (ListView) v.findViewById(R.id.itemlistfragment_list);
emptyProgressView = (ProgressThrobber) v.findViewById(R.id.empty_view_loading_throb);
ButterKnife.bind(this, v);
emptyProgressView.setColors(getResources().getColor(R.color.refresh_1),
getResources().getColor(R.color.refresh_2),
getResources().getColor(R.color.refresh_3),
@ -148,10 +151,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
}
private void updateLoadingMessage() {
View v = this.getView();
if (v == null) return; // we might have beat construction?
ListView itemList = (ListView) v.findViewById(R.id.itemlistfragment_list);
if (itemList == null) {
Log.w(this.getClass().getName(), "ItemListFragment does not have the expected ListView.");
return;
@ -168,10 +167,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
}
public void scrollToTop() {
View v = this.getView();
if (v == null) return; // we might have beat construction?
ListView itemList = (ListView) v.findViewById(R.id.itemlistfragment_list);
if (itemList == null) {
Log.w(this.getClass().getName(), "ItemListFragment does not have the expected ListView.");
return;
@ -293,8 +288,8 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
FeedUtils.markFeedsRead(getFeedSet(), null, story.timestamp, activity);
return true;
case R.id.menu_shared:
FeedUtils.shareStory(story, activity);
case R.id.menu_send_story:
FeedUtils.sendStory(story, activity);
return true;
case R.id.menu_save_story:

View file

@ -16,6 +16,9 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.ButterKnife;
import butterknife.FindView;
import com.newsblur.R;
import com.newsblur.activity.Login;
import com.newsblur.activity.Main;
@ -27,9 +30,11 @@ import com.newsblur.util.UIUtils;
public class LoginProgressFragment extends Fragment {
private APIManager apiManager;
private TextView updateStatus, retrievingFeeds;
private ImageView loginProfilePicture;
private ProgressBar feedProgress, loggingInProgress;
@FindView(R.id.login_logging_in) TextView updateStatus;
@FindView(R.id.login_retrieving_feeds) TextView retrievingFeeds;
@FindView(R.id.login_profile_picture) ImageView loginProfilePicture;
@FindView(R.id.login_feed_progress) ProgressBar feedProgress;
@FindView(R.id.login_logging_in_progress) ProgressBar loggingInProgress;
private LoginTask loginTask;
private String username;
private String password;
@ -56,12 +61,7 @@ public class LoginProgressFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_loginprogress, null);
updateStatus = (TextView) v.findViewById(R.id.login_logging_in);
retrievingFeeds = (TextView) v.findViewById(R.id.login_retrieving_feeds);
feedProgress = (ProgressBar) v.findViewById(R.id.login_feed_progress);
loggingInProgress = (ProgressBar) v.findViewById(R.id.login_logging_in_progress);
loginProfilePicture = (ImageView) v.findViewById(R.id.login_profile_picture);
ButterKnife.bind(this, v);
loginTask = new LoginTask();
loginTask.execute();
@ -70,7 +70,6 @@ public class LoginProgressFragment extends Fragment {
}
private class LoginTask extends AsyncTask<Void, Void, NewsBlurResponse> {
@Override
protected void onPreExecute() {
Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_up);
@ -104,7 +103,6 @@ public class LoginProgressFragment extends Fragment {
Intent startMain = new Intent(getActivity(), Main.class);
c.startActivity(startMain);
} else {
UIUtils.safeToast(c, result.getErrorMessage(), Toast.LENGTH_LONG);
startActivity(new Intent(c, Login.class));

View file

@ -16,29 +16,28 @@ import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ViewSwitcher;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.LoginProgress;
import com.newsblur.activity.RegisterProgress;
public class LoginRegisterFragment extends Fragment implements OnClickListener {
public class LoginRegisterFragment extends Fragment {
private EditText username, password;
private ViewSwitcher viewSwitcher;
private EditText register_username, register_password, register_email;
@FindView(R.id.login_username) EditText username;
@FindView(R.id.login_password) EditText password;
@FindView(R.id.registration_username) EditText register_username;
@FindView(R.id.registration_password) EditText register_password;
@FindView(R.id.registration_email) EditText register_email;
@FindView(R.id.login_viewswitcher) ViewSwitcher viewSwitcher;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_loginregister, container, false);
ButterKnife.bind(this, v);
final Button loginButton = (Button) v.findViewById(R.id.login_button);
final Button registerButton = (Button) v.findViewById(R.id.registration_button);
loginButton.setOnClickListener(this);
registerButton.setOnClickListener(this);
username = (EditText) v.findViewById(R.id.login_username);
password = (EditText) v.findViewById(R.id.login_password);
password.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) {
@ -49,10 +48,6 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener {
}
});
register_username = (EditText) v.findViewById(R.id.registration_username);
register_password = (EditText) v.findViewById(R.id.registration_password);
register_email = (EditText) v.findViewById(R.id.registration_email);
register_email.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) {
@ -63,44 +58,10 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener {
}
});
viewSwitcher = (ViewSwitcher) v.findViewById(R.id.login_viewswitcher);
TextView changeToLogin = (TextView) v.findViewById(R.id.login_change_to_login);
TextView changeToRegister = (TextView) v.findViewById(R.id.login_change_to_register);
changeToLogin.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
viewSwitcher.showPrevious();
}
});
changeToRegister.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
viewSwitcher.showNext();
}
});
return v;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onClick(View viewClicked) {
if (viewClicked.getId() == R.id.login_button) {
logIn();
} else if (viewClicked.getId() == R.id.registration_button) {
signUp();
}
}
private void logIn() {
@OnClick(R.id.login_button) void logIn() {
if (!TextUtils.isEmpty(username.getText().toString())) {
Intent i = new Intent(getActivity(), LoginProgress.class);
i.putExtra("username", username.getText().toString());
@ -109,7 +70,7 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener {
}
}
private void signUp() {
@OnClick(R.id.registration_button) void signUp() {
Intent i = new Intent(getActivity(), RegisterProgress.class);
i.putExtra("username", register_username.getText().toString());
i.putExtra("password", register_password.getText().toString());
@ -117,5 +78,12 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener {
startActivity(i);
}
@OnClick(R.id.login_change_to_login) void showLogin() {
viewSwitcher.showPrevious();
}
@OnClick(R.id.login_change_to_register) void showRegister() {
viewSwitcher.showNext();
}
}

View file

@ -1,25 +1,29 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.ReadFilterChangedListener;
public class ReadFilterDialogFragment extends DialogFragment implements OnClickListener {
public class ReadFilterDialogFragment extends DialogFragment {
private static String CURRENT_FILTER = "currentFilter";
private ReadFilter currentValue;
@FindView(R.id.radio_all) RadioButton allButton;
@FindView(R.id.radio_unread) RadioButton unreadButton;
public static ReadFilterDialogFragment newInstance(ReadFilter currentValue) {
ReadFilterDialogFragment dialog = new ReadFilterDialogFragment();
@ -39,11 +43,9 @@ public class ReadFilterDialogFragment extends DialogFragment implements OnClickL
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = (ReadFilter) getArguments().getSerializable(CURRENT_FILTER);
View v = inflater.inflate(R.layout.readfilter_dialog, null);
RadioButton allButton = (RadioButton) v.findViewById(R.id.radio_all);
allButton.setOnClickListener(this);
ButterKnife.bind(this, v);
allButton.setChecked(currentValue == ReadFilter.ALL);
RadioButton unreadButton = (RadioButton) v.findViewById(R.id.radio_unread);
unreadButton.setOnClickListener(this);
unreadButton.setChecked(currentValue == ReadFilter.UNREAD);
getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);
@ -53,19 +55,18 @@ public class ReadFilterDialogFragment extends DialogFragment implements OnClickL
return v;
}
@Override
public void onClick(View v) {
ReadFilterChangedListener listener = (ReadFilterChangedListener)getActivity();
if (v.getId() == R.id.radio_all) {
if (currentValue == ReadFilter.UNREAD) {
listener.readFilterChanged(ReadFilter.ALL);
}
} else {
if (currentValue == ReadFilter.ALL) {
listener.readFilterChanged(ReadFilter.UNREAD);
}
@OnClick(R.id.radio_all) void selectAll() {
if (currentValue != ReadFilter.ALL) {
((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.ALL);
}
dismiss();
}
@OnClick(R.id.radio_unread) void selectUnread() {
if (currentValue != ReadFilter.UNREAD) {
((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.UNREAD);
}
dismiss();
}
}

View file

@ -31,14 +31,16 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.NewsBlurApplication;
import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.APIManager;
import com.newsblur.network.SetupCommentSectionTask;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.DefaultFeedView;
import com.newsblur.util.FeedUtils;
@ -59,28 +61,28 @@ import java.util.regex.Pattern;
import java.util.HashSet;
import java.util.Set;
public class ReadingItemFragment extends NbFragment implements ClassifierDialogFragment.TagUpdateCallback, ShareDialogFragment.SharedCallbackDialog {
public class ReadingItemFragment extends NbFragment implements ClassifierDialogFragment.TagUpdateCallback {
public static final String TEXT_SIZE_CHANGED = "textSizeChanged";
public static final String TEXT_SIZE_VALUE = "textSizeChangeValue";
public Story story;
private LayoutInflater inflater;
private APIManager apiManager;
private ImageLoader imageLoader;
private String feedColor, feedTitle, feedFade, feedBorder, feedIconUrl, faviconText;
private Classifier classifier;
private NewsblurWebview web;
private BroadcastReceiver receiver;
private TextView itemAuthors;
private TextView itemFeed;
@FindView(R.id.reading_item_authors) TextView itemAuthors;
@FindView(R.id.reading_feed_title) TextView itemFeed;
private boolean displayFeedDetails;
private FlowLayout tagContainer;
private View view;
private UserDetails user;
public String previouslySavedShareText;
private ImageView feedIcon;
private Reading activity;
private DefaultFeedView selectedFeedView;
@FindView(R.id.save_story_button) Button saveButton;
@FindView(R.id.share_story_button) Button shareButton;
/** The story HTML, as provided by the 'content' element of the stories API. */
private String storyContent;
@ -126,7 +128,6 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
imageLoader = ((NewsBlurApplication) getActivity().getApplicationContext()).getImageLoader();
apiManager = new APIManager(getActivity());
story = getArguments() != null ? (Story) getArguments().getSerializable("story") : null;
inflater = getActivity().getLayoutInflater();
@ -182,21 +183,17 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
}
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_readingitem, null);
ButterKnife.bind(this, view);
web = (NewsblurWebview) view.findViewById(R.id.reading_webview);
registerForContextMenu(web);
setupItemMetadata();
setupShareButton();
setupSaveButton();
updateShareButton();
updateSaveButton();
if (story.sharedUserIds.length > 0 || story.commentCount > 0 ) {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
setupItemCommentsAndShares(view);
}
setupItemCommentsAndShares();
NonfocusScrollview scrollView = (NonfocusScrollview) view.findViewById(R.id.reading_scrollview);
scrollView.registerScrollChangeListener(this.activity);
@ -261,51 +258,38 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
super.onCreateContextMenu(menu, v, menuInfo);
}
}
private void setupSaveButton() {
final Button saveButton = (Button) view.findViewById(R.id.save_story_button);
saveButton.setText(story.starred ? R.string.unsave_this : R.string.save_this);
saveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story.starred) {
FeedUtils.setStorySaved(story, false, activity);
} else {
FeedUtils.setStorySaved(story,true, activity);
}
}
});
}
@OnClick(R.id.save_story_button) void clickSave() {
if (story.starred) {
FeedUtils.setStorySaved(story, false, activity);
} else {
FeedUtils.setStorySaved(story,true, activity);
}
}
private void updateSaveButton() {
if (view == null) { return; }
Button saveButton = (Button) view.findViewById(R.id.save_story_button);
if (saveButton == null) { return; }
if (saveButton == null) return;
saveButton.setText(story.starred ? R.string.unsave_this : R.string.save_this);
}
private void setupShareButton() {
Button shareButton = (Button) view.findViewById(R.id.share_story_button);
@OnClick(R.id.share_story_button) void clickShare() {
DialogFragment newFragment = ShareDialogFragment.newInstance(story, sourceUserId);
newFragment.show(getFragmentManager(), "dialog");
}
private void updateShareButton() {
if (shareButton == null) return;
for (String userId : story.sharedUserIds) {
if (TextUtils.equals(userId, user.id)) {
shareButton.setText(R.string.edit);
break;
shareButton.setText(R.string.already_shared);
return;
}
}
shareButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
DialogFragment newFragment = ShareDialogFragment.newInstance(ReadingItemFragment.this, story, previouslySavedShareText, sourceUserId);
newFragment.show(getFragmentManager(), "dialog");
}
});
shareButton.setText(R.string.share_this);
}
private void setupItemCommentsAndShares(final View view) {
new SetupCommentSectionTask(getActivity(), view, getFragmentManager(), inflater, apiManager, story, imageLoader).execute();
private void setupItemCommentsAndShares() {
new SetupCommentSectionTask(getActivity(), view, getFragmentManager(), inflater, story, imageLoader).execute();
}
private void setupItemMetadata() {
@ -313,8 +297,6 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
View feedHeaderBorder = view.findViewById(R.id.item_feed_border);
TextView itemTitle = (TextView) view.findViewById(R.id.reading_item_title);
TextView itemDate = (TextView) view.findViewById(R.id.reading_item_date);
itemAuthors = (TextView) view.findViewById(R.id.reading_item_authors);
itemFeed = (TextView) view.findViewById(R.id.reading_feed_title);
feedIcon = (ImageView) view.findViewById(R.id.reading_feed_icon);
if (TextUtils.equals(feedColor, "#null") || TextUtils.equals(feedFade, "#null")) {
@ -460,7 +442,9 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
public void handleUpdate() {
updateSaveButton();
updateShareButton();
reloadStoryContent();
setupItemCommentsAndShares();
}
private void loadOriginalText() {
@ -625,74 +609,6 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
break;
}
}
@Override
public void sharedCallback(String sharedText, boolean hasAlreadyBeenShared) {
try {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
if (!hasAlreadyBeenShared) {
if (!TextUtils.isEmpty(sharedText)) {
View commentView = inflater.inflate(R.layout.include_comment, null);
commentView.setTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setTag("commentBy" + user.id);
commentText.setText(sharedText);
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
if (!TextUtils.isEmpty(user.location)) {
commentLocation.setText(user.location.toUpperCase());
} else {
commentLocation.setVisibility(View.GONE);
}
if (PrefsUtils.getUserImage(getActivity()) != null) {
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
commentImage.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
}
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
commentSharedDate.setText(R.string.now);
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
commentUsername.setText(user.username);
((LinearLayout) view.findViewById(R.id.reading_friend_comment_container)).addView(commentView);
ViewUtils.setupCommentCount(getActivity(), view, story.commentCount + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_commentimages)).addView(image);
} else {
ViewUtils.setupShareCount(getActivity(), view, story.sharedUserIds.length + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_shareimages)).addView(image);
}
} else {
View commentViewForUser = view.findViewWithTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
TextView commentText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_BY + user.id);
commentText.setText(sharedText);
TextView commentDateText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_DATE_BY + user.id);
commentDateText.setText(R.string.now);
}
} catch (Exception e) {
// this entire method does not respect context state and can be triggered on stale fragments. it should
// be replaced with a proper Loader, or it will always risk crashing the application
Log.w(this.getClass().getName(), "async error in callback", e);
}
}
@Override
public void setPreviouslySavedShareText(String previouslySavedShareText) {
this.previouslySavedShareText = previouslySavedShareText;
}
private class ImmersiveViewHandler extends GestureDetector.SimpleOnGestureListener implements View.OnSystemUiVisibilityChangeListener {
private View view;

View file

@ -14,6 +14,10 @@ import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ViewSwitcher;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.activity.AddSocial;
import com.newsblur.activity.Login;
@ -28,9 +32,9 @@ public class RegisterProgressFragment extends Fragment {
private String password;
private String email;
private RegisterTask registerTask;
private ViewSwitcher switcher;
private Button next;
private ImageView registerProgressLogo;
@FindView(R.id.register_viewswitcher) ViewSwitcher switcher;
@FindView(R.id.registering_next_1) Button next;
@FindView(R.id.registerprogress_logo) ImageView registerProgressLogo;
public static RegisterProgressFragment getInstance(String username, String password, String email) {
RegisterProgressFragment fragment = new RegisterProgressFragment();
@ -51,19 +55,15 @@ public class RegisterProgressFragment extends Fragment {
username = getArguments().getString("username");
password = getArguments().getString("password");
email = getArguments().getString("email");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_registerprogress, null);
switcher = (ViewSwitcher) v.findViewById(R.id.register_viewswitcher);
ButterKnife.bind(this, v);
registerProgressLogo = (ImageView) v.findViewById(R.id.registerprogress_logo);
registerProgressLogo.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate));
next = (Button) v.findViewById(R.id.registering_next_1);
if (registerTask != null) {
switcher.showNext();
} else {
@ -71,17 +71,14 @@ public class RegisterProgressFragment extends Fragment {
registerTask.execute();
}
next.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent i = new Intent(getActivity(), AddSocial.class);
startActivity(i);
}
});
return v;
}
@OnClick(R.id.registering_next_1) void next() {
Intent i = new Intent(getActivity(), AddSocial.class);
startActivity(i);
}
private class RegisterTask extends AsyncTask<Void, Void, RegisterResponse> {
@Override

View file

@ -15,6 +15,7 @@ import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
public class ReplyDialogFragment extends DialogFragment {
@ -25,9 +26,6 @@ public class ReplyDialogFragment extends DialogFragment {
private String commentUserId, commentUsername;
private Story story;
private APIManager apiManager;
public static ReplyDialogFragment newInstance(final Story story, final String commentUserId, final String commentUsername) {
ReplyDialogFragment frag = new ReplyDialogFragment();
Bundle args = new Bundle();
@ -40,17 +38,14 @@ public class ReplyDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
story = (Story) getArguments().getSerializable(STORY);
commentUserId = getArguments().getString(COMMENT_USER_ID);
commentUsername = getArguments().getString(COMMENT_USERNAME);
final Activity activity = getActivity();
apiManager = new APIManager(activity);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final String shareString = getResources().getString(R.string.reply_to);
String shareString = getResources().getString(R.string.reply_to);
builder.setTitle(String.format(shareString, getArguments().getString(COMMENT_USERNAME)));
LayoutInflater layoutInflater = LayoutInflater.from(activity);
@ -61,23 +56,7 @@ public class ReplyDialogFragment extends DialogFragment {
builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
return apiManager.replyToComment(story.id, story.feedId, commentUserId, reply.getText().toString());
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
Toast.makeText(activity, R.string.replied, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, R.string.error_replying, Toast.LENGTH_LONG).show();
}
ReplyDialogFragment.this.dismiss();
};
}.execute();
FeedUtils.replyToComment(story.id, story.feedId, commentUserId, reply.getText().toString(), activity);
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {

View file

@ -0,0 +1,336 @@
package com.newsblur.fragment;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.activity.Profile;
import com.newsblur.domain.Comment;
import com.newsblur.domain.Reply;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.domain.UserProfile;
import com.newsblur.fragment.ReplyDialogFragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.FlowLayout;
public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
private ArrayList<View> publicCommentViews;
private ArrayList<View> friendCommentViews;
private ArrayList<View> friendShareViews;
private final Story story;
private final LayoutInflater inflater;
private final ImageLoader imageLoader;
private WeakReference<View> viewHolder;
private final Context context;
private UserDetails user;
private final FragmentManager manager;
private List<Comment> comments;
public SetupCommentSectionTask(Context context, View view, FragmentManager manager, LayoutInflater inflater, Story story, ImageLoader imageLoader) {
this.context = context;
this.manager = manager;
this.inflater = inflater;
this.story = story;
this.imageLoader = imageLoader;
viewHolder = new WeakReference<View>(view);
user = PrefsUtils.getUserDetails(context);
}
@Override
protected Void doInBackground(Void... arg0) {
comments = FeedUtils.dbHelper.getComments(story.id);
publicCommentViews = new ArrayList<View>();
friendCommentViews = new ArrayList<View>();
friendShareViews = new ArrayList<View>();
for (final Comment comment : comments) {
// skip public comments if they are disabled
if (!comment.byFriend && !PrefsUtils.showPublicComments(context)) {
continue;
}
UserProfile commentUser = FeedUtils.dbHelper.getUserProfile(comment.userId);
// rarely, we get a comment but never got the user's profile, so we can't display it
if (commentUser == null) {
Log.w(this.getClass().getName(), "cannot display comment from missing user ID: " + comment.userId);
continue;
}
View commentView = inflater.inflate(R.layout.include_comment, null);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setText(Html.fromHtml(comment.commentText));
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
// TODO: this uses hard-coded "ago" values, which will be wrong when reading prefetched stories
if (comment.sharedDate != null) {
commentSharedDate.setText(comment.sharedDate + " ago");
}
final FlowLayout favouriteContainer = (FlowLayout) commentView.findViewById(R.id.comment_favourite_avatars);
final ImageView favouriteIcon = (ImageView) commentView.findViewById(R.id.comment_favourite_icon);
final ImageView replyIcon = (ImageView) commentView.findViewById(R.id.comment_reply_icon);
if (comment.likingUsers != null) {
if (Arrays.asList(comment.likingUsers).contains(user.id)) {
favouriteIcon.setImageResource(R.drawable.have_favourite);
}
for (String id : comment.likingUsers) {
ImageView favouriteImage = new ImageView(context);
UserProfile user = FeedUtils.dbHelper.getUserProfile(id);
imageLoader.displayImage(user.photoUrl, favouriteImage, 10f);
favouriteContainer.addView(favouriteImage);
}
// users cannot fave their own comments. attempting to do so will actually queue a fatally invalid API call
if (TextUtils.equals(comment.userId, user.id)) {
favouriteIcon.setVisibility(View.GONE);
} else {
favouriteIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!Arrays.asList(comment.likingUsers).contains(user.id)) {
FeedUtils.likeComment(story, comment.userId, context);
} else {
FeedUtils.unlikeComment(story, comment.userId, context);
}
}
});
}
}
replyIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story != null) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(comment.userId);
DialogFragment newFragment = ReplyDialogFragment.newInstance(story, comment.userId, user.username);
newFragment.show(manager, "dialog");
}
}
});
List<Reply> replies = FeedUtils.dbHelper.getCommentReplies(comment.id);
for (Reply reply : replies) {
View replyView = inflater.inflate(R.layout.include_reply, null);
TextView replyText = (TextView) replyView.findViewById(R.id.reply_text);
replyText.setText(Html.fromHtml(reply.text));
ImageView replyImage = (ImageView) replyView.findViewById(R.id.reply_user_image);
final UserProfile replyUser = FeedUtils.dbHelper.getUserProfile(reply.userId);
if (replyUser != null) {
imageLoader.displayImage(replyUser.photoUrl, replyImage);
replyImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, replyUser.userId);
context.startActivity(i);
}
});
TextView replyUsername = (TextView) replyView.findViewById(R.id.reply_username);
replyUsername.setText(replyUser.username);
} else {
TextView replyUsername = (TextView) replyView.findViewById(R.id.reply_username);
replyUsername.setText(R.string.unknown_user);
}
if (reply.shortDate != null) {
TextView replySharedDate = (TextView) replyView.findViewById(R.id.reply_shareddate);
replySharedDate.setText(reply.shortDate + " ago");
}
((LinearLayout) commentView.findViewById(R.id.comment_replies_container)).addView(replyView);
}
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
commentUsername.setText(commentUser.username);
String userPhoto = commentUser.photoUrl;
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
if (!TextUtils.isEmpty(commentUser.location)) {
commentLocation.setText(commentUser.location.toUpperCase());
} else {
commentLocation.setVisibility(View.GONE);
}
if (!TextUtils.isEmpty(comment.sourceUserId)) {
commentImage.setVisibility(View.INVISIBLE);
ImageView usershareImage = (ImageView) commentView.findViewById(R.id.comment_user_reshare_image);
ImageView sourceUserImage = (ImageView) commentView.findViewById(R.id.comment_sharesource_image);
sourceUserImage.setVisibility(View.VISIBLE);
usershareImage.setVisibility(View.VISIBLE);
commentImage.setVisibility(View.INVISIBLE);
UserProfile sourceUser = FeedUtils.dbHelper.getUserProfile(comment.sourceUserId);
if (sourceUser != null) {
imageLoader.displayImage(sourceUser.photoUrl, sourceUserImage, 10f);
imageLoader.displayImage(userPhoto, usershareImage, 10f);
}
} else {
imageLoader.displayImage(userPhoto, commentImage, 10f);
}
commentImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, comment.userId);
context.startActivity(i);
}
});
if (comment.isPseudo) {
friendShareViews.add(commentView);
} else if (comment.byFriend) {
friendCommentViews.add(commentView);
} else {
publicCommentViews.add(commentView);
}
}
return null;
}
protected void onPostExecute(Void result) {
View view = viewHolder.get();
if (view == null) return; // fragment was dismissed before we rendered
if (story.sharedUserIds.length > 0 || publicCommentViews.size() > 0 || friendCommentViews.size() > 0) {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
} else {
view.findViewById(R.id.reading_share_bar).setVisibility(View.GONE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.GONE);
}
FlowLayout sharedGrid = (FlowLayout) view.findViewById(R.id.reading_social_shareimages);
FlowLayout commentGrid = (FlowLayout) view.findViewById(R.id.reading_social_commentimages);
TextView friendCommentTotal = ((TextView) view.findViewById(R.id.reading_friend_comment_total));
TextView friendShareTotal = ((TextView) view.findViewById(R.id.reading_friend_emptyshare_total));
TextView publicCommentTotal = ((TextView) view.findViewById(R.id.reading_public_comment_total));
int actualCommentCount = comments.size() - friendShareViews.size(); // comment-less shares are modeled as comments, exclude them
ViewUtils.setupCommentCount(context, view, actualCommentCount);
ViewUtils.setupShareCount(context, view, story.sharedUserIds.length);
Set<String> commentingUserIds = new HashSet<String>();
for (Comment comment : comments) {
if (!comment.isPseudo) {
commentingUserIds.add(comment.userId);
}
}
sharedGrid.removeAllViews();
for (String userId : story.sharedUserIds) {
// only show an icon in either the share grid or the comment grid, not both
if (!commentingUserIds.contains(userId)) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(userId);
if (user != null) {
ImageView image = ViewUtils.createSharebarImage(context, imageLoader, user.photoUrl, user.userId);
sharedGrid.addView(image);
}
}
}
commentGrid.removeAllViews();
for (String userId : commentingUserIds) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(userId);
ImageView image = ViewUtils.createSharebarImage(context, imageLoader, user.photoUrl, user.userId);
commentGrid.addView(image);
}
if (publicCommentViews.size() > 0) {
String commentCount = context.getString(R.string.public_comment_count);
if (publicCommentViews.size() == 1) {
commentCount = commentCount.substring(0, commentCount.length() - 1);
}
publicCommentTotal.setText(String.format(commentCount, publicCommentViews.size()));
view.findViewById(R.id.reading_public_comment_header).setVisibility(View.VISIBLE);
} else {
view.findViewById(R.id.reading_public_comment_header).setVisibility(View.GONE);
}
if (friendCommentViews.size() > 0) {
String commentCount = context.getString(R.string.friends_comments_count);
if (friendCommentViews.size() == 1) {
commentCount = commentCount.substring(0, commentCount.length() - 1);
}
friendCommentTotal.setText(String.format(commentCount, friendCommentViews.size()));
view.findViewById(R.id.reading_friend_comment_header).setVisibility(View.VISIBLE);
} else {
view.findViewById(R.id.reading_friend_comment_header).setVisibility(View.GONE);
}
if (friendShareViews.size() > 0) {
String commentCount = context.getString(R.string.friends_shares_count);
if (friendShareViews.size() == 1) {
commentCount = commentCount.substring(0, commentCount.length() - 1);
}
friendShareTotal.setText(String.format(commentCount, friendShareViews.size()));
view.findViewById(R.id.reading_friend_emptyshare_header).setVisibility(View.VISIBLE);
} else {
view.findViewById(R.id.reading_friend_emptyshare_header).setVisibility(View.GONE);
}
LinearLayout publicCommentListContainer = (LinearLayout) view.findViewById(R.id.reading_public_comment_container);
publicCommentListContainer.removeAllViews();
for (int i = 0; i < publicCommentViews.size(); i++) {
if (i == publicCommentViews.size() - 1) {
publicCommentViews.get(i).findViewById(R.id.comment_divider).setVisibility(View.GONE);
}
publicCommentListContainer.addView(publicCommentViews.get(i));
}
LinearLayout friendCommentListContainer = (LinearLayout) view.findViewById(R.id.reading_friend_comment_container);
friendCommentListContainer.removeAllViews();
for (int i = 0; i < friendCommentViews.size(); i++) {
if (i == friendCommentViews.size() - 1) {
friendCommentViews.get(i).findViewById(R.id.comment_divider).setVisibility(View.GONE);
}
friendCommentListContainer.addView(friendCommentViews.get(i));
}
LinearLayout friendShareListContainer = (LinearLayout) view.findViewById(R.id.reading_friend_emptyshare_container);
friendShareListContainer.removeAllViews();
for (int i = 0; i < friendShareViews.size(); i++) {
if (i == friendShareViews.size() - 1) {
friendShareViews.get(i).findViewById(R.id.comment_divider).setVisibility(View.GONE);
}
friendShareListContainer.addView(friendShareViews.get(i));
}
}
}

View file

@ -1,12 +1,9 @@
package com.newsblur.fragment;
import java.io.Serializable;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.DialogFragment;
import android.text.Html;
@ -14,40 +11,29 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.domain.Comment;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
public class ShareDialogFragment extends DialogFragment {
private static final String STORY = "story";
private static final String CALLBACK= "callback";
private static final String PREVIOUSLY_SAVED_SHARE_TEXT = "previouslySavedComment";
private static final String SOURCE_USER_ID = "sourceUserId";
private APIManager apiManager;
private SharedCallbackDialog callback;
private Story story;
private UserDetails user;
private boolean hasBeenShared = false;
private Comment previousComment;
private String previouslySavedShareText;
private boolean hasShared = false;
private EditText commentEditText;
private String sourceUserId;
public static ShareDialogFragment newInstance(final SharedCallbackDialog sharedCallback, final Story story, final String previouslySavedShareText, final String sourceUserId) {
public static ShareDialogFragment newInstance(final Story story, final String sourceUserId) {
ShareDialogFragment frag = new ShareDialogFragment();
Bundle args = new Bundle();
args.putSerializable(STORY, story);
args.putString(PREVIOUSLY_SAVED_SHARE_TEXT, previouslySavedShareText);
args.putSerializable(CALLBACK, sharedCallback);
args.putString(SOURCE_USER_ID, sourceUserId);
frag.setArguments(args);
return frag;
@ -55,16 +41,12 @@ public class ShareDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
story = (Story) getArguments().getSerializable(STORY);
callback = (SharedCallbackDialog) getArguments().getSerializable(CALLBACK);
user = PrefsUtils.getUserDetails(activity);
previouslySavedShareText = getArguments().getString(PREVIOUSLY_SAVED_SHARE_TEXT);
sourceUserId = getArguments().getString(SOURCE_USER_ID);
apiManager = new APIManager(getActivity());
boolean hasBeenShared = false;
for (String sharedUserId : story.sharedUserIds) {
if (TextUtils.equals(user.id, sharedUserId)) {
hasBeenShared = true;
@ -77,8 +59,7 @@ public class ShareDialogFragment extends DialogFragment {
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final String shareString = getResources().getString(R.string.share_newsblur);
builder.setTitle(String.format(shareString, Html.fromHtml(story.title)));
builder.setTitle(String.format(getResources().getString(R.string.share_newsblur), Html.fromHtml(story.title)));
LayoutInflater layoutInflater = LayoutInflater.from(activity);
View replyView = layoutInflater.inflate(R.layout.share_dialog, null);
@ -87,45 +68,18 @@ public class ShareDialogFragment extends DialogFragment {
int positiveButtonText = R.string.share_this_story;
if (hasBeenShared) {
positiveButtonText = R.string.edit;
positiveButtonText = R.string.update_shared;
if (previousComment != null ) {
commentEditText.setText(previousComment.commentText);
}
} else if (!TextUtils.isEmpty(previouslySavedShareText)) {
commentEditText.setText(previouslySavedShareText);
}
builder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final String shareComment = commentEditText.getText().toString();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
// If story.sourceUsedId is set then we should use that as the sourceUserId for the share.
// Otherwise, use the sourceUsedId passed to the fragment.
if (story.sourceUserId == null) {
return apiManager.shareStory(story.id, story.feedId, shareComment, sourceUserId);
} else {
return apiManager.shareStory(story.id, story.feedId, shareComment, story.sourceUserId);
}
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
hasShared = true;
UIUtils.safeToast(activity, R.string.shared, Toast.LENGTH_LONG);
callback.sharedCallback(shareComment, hasBeenShared);
} else {
UIUtils.safeToast(activity, R.string.error_sharing, Toast.LENGTH_LONG);
}
ShareDialogFragment.this.dismiss();
};
}.execute();
String shareComment = commentEditText.getText().toString();
FeedUtils.shareStory(story, shareComment, sourceUserId, activity);
ShareDialogFragment.this.dismiss();
}
});
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@ -136,19 +90,5 @@ public class ShareDialogFragment extends DialogFragment {
});
return builder.create();
}
@Override
public void onDestroy() {
if (!hasShared && commentEditText.length() > 0) {
previouslySavedShareText = commentEditText.getText().toString();
callback.setPreviouslySavedShareText(previouslySavedShareText);
}
super.onDestroy();
}
public interface SharedCallbackDialog extends Serializable{
public void setPreviouslySavedShareText(String previouslySavedShareText);
public void sharedCallback(String sharedText, boolean alreadyShared);
}
}

View file

@ -1,25 +1,29 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.app.DialogFragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RadioButton;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.StoryOrderChangedListener;
public class StoryOrderDialogFragment extends DialogFragment implements OnClickListener {
public class StoryOrderDialogFragment extends DialogFragment {
private static String CURRENT_ORDER = "currentOrder";
private StoryOrder currentValue;
@FindView(R.id.radio_newest) RadioButton newestButton;
@FindView(R.id.radio_oldest) RadioButton oldestButton;
public static StoryOrderDialogFragment newInstance(StoryOrder currentValue) {
StoryOrderDialogFragment dialog = new StoryOrderDialogFragment();
@ -39,11 +43,9 @@ public class StoryOrderDialogFragment extends DialogFragment implements OnClickL
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
currentValue = (StoryOrder) getArguments().getSerializable(CURRENT_ORDER);
View v = inflater.inflate(R.layout.storyorder_dialog, null);
RadioButton newestButton = (RadioButton) v.findViewById(R.id.radio_newest);
newestButton.setOnClickListener(this);
ButterKnife.bind(this, v);
newestButton.setChecked(currentValue == StoryOrder.NEWEST);
RadioButton oldestButton = (RadioButton) v.findViewById(R.id.radio_oldest);
oldestButton.setOnClickListener(this);
oldestButton.setChecked(currentValue == StoryOrder.OLDEST);
getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);
@ -53,19 +55,18 @@ public class StoryOrderDialogFragment extends DialogFragment implements OnClickL
return v;
}
@Override
public void onClick(View v) {
StoryOrderChangedListener listener = (StoryOrderChangedListener)getActivity();
if (v.getId() == R.id.radio_oldest) {
if (currentValue == StoryOrder.NEWEST) {
listener.storyOrderChanged(StoryOrder.OLDEST);
}
} else {
if (currentValue == StoryOrder.OLDEST) {
listener.storyOrderChanged(StoryOrder.NEWEST);
}
@OnClick(R.id.radio_newest) void selectNewest() {
if (currentValue != StoryOrder.NEWEST) {
((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.NEWEST);
}
dismiss();
}
@OnClick(R.id.radio_oldest) void selectOldest() {
if (currentValue != StoryOrder.OLDEST) {
((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.OLDEST);
}
dismiss();
}
}

View file

@ -334,7 +334,7 @@ public class APIManager {
}
}
public Boolean shareStory(final String storyId, final String feedId, final String comment, final String sourceUserId) {
public NewsBlurResponse shareStory(final String storyId, final String feedId, final String comment, final String sourceUserId) {
final ContentValues values = new ContentValues();
if (!TextUtils.isEmpty(comment)) {
values.put(APIConstants.PARAMETER_SHARE_COMMENT, comment);
@ -345,12 +345,8 @@ public class APIManager {
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_STORYID, storyId);
final APIResponse response = post(APIConstants.URL_SHARE_STORY, values);
if (!response.isError()) {
return true;
} else {
return false;
}
APIResponse response = post(APIConstants.URL_SHARE_STORY, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
/**
@ -373,6 +369,8 @@ public class APIManager {
// note: this response is complex enough, we have to do a custom parse in the FFR
FeedFolderResponse result = new FeedFolderResponse(response.getResponseBody(), gson);
// bind a litle extra instrumentation to this response, since it powers the feedback link
result.connTime = response.connectTime;
result.readTime = response.readTime;
return result;
}
@ -480,32 +478,32 @@ public class APIManager {
}
}
public boolean favouriteComment(String storyId, String commentId, String feedId) {
public NewsBlurResponse favouriteComment(String storyId, String commentUserId, String feedId) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = post(APIConstants.URL_LIKE_COMMENT, values);
return (!response.isError());
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
APIResponse response = post(APIConstants.URL_LIKE_COMMENT, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public Boolean unFavouriteComment(String storyId, String commentId, String feedId) {
public NewsBlurResponse unFavouriteComment(String storyId, String commentUserId, String feedId) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = post(APIConstants.URL_UNLIKE_COMMENT, values);
return (!response.isError());
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
APIResponse response = post(APIConstants.URL_UNLIKE_COMMENT, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public boolean replyToComment(String storyId, String storyFeedId, String commentUserId, String reply) {
public NewsBlurResponse replyToComment(String storyId, String storyFeedId, String commentUserId, String reply) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, storyFeedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
final APIResponse response = post(APIConstants.URL_REPLY_TO, values);
return (!response.isError());
APIResponse response = post(APIConstants.URL_REPLY_TO, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public boolean addFeed(String feedUrl, String folderName) {

View file

@ -28,6 +28,7 @@ public class APIResponse {
private String errorMessage;
private String cookie;
private String responseBody;
public long connectTime;
public long readTime;
/**
@ -47,7 +48,9 @@ public class APIResponse {
this.errorMessage = context.getResources().getString(R.string.error_unset_message);
try {
long startTime = System.currentTimeMillis();
Response response = httpClient.newCall(request).execute();
connectTime = System.currentTimeMillis() - startTime;
if (response.isSuccessful()) {
if (response.code() != expectedReturnCode) {
@ -60,7 +63,7 @@ public class APIResponse {
this.cookie = response.header("Set-Cookie");
try {
long startTime = System.currentTimeMillis();
startTime = System.currentTimeMillis();
this.responseBody = response.body().string();
readTime = System.currentTimeMillis() - startTime;
} catch (Exception e) {
@ -82,6 +85,10 @@ public class APIResponse {
}
}
if (AppConstants.VERBOSE_LOG_NET) {
Log.d(this.getClass().getName(), String.format("called %s in %dms and %dms to read %dB", request.urlString(), connectTime, readTime, responseBody.length()));
}
} else {
Log.e(this.getClass().getName(), "API call unsuccessful, error code" + response.code());
this.isError = true;

View file

@ -1,77 +0,0 @@
package com.newsblur.network;
import java.lang.ref.WeakReference;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.widget.ImageView;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.domain.Comment;
import com.newsblur.domain.UserDetails;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
import com.newsblur.view.FlowLayout;
public class LikeCommentTask extends AsyncTask<Void, Void, Boolean>{
final WeakReference<ImageView> favouriteIconViewHolder;
private final APIManager apiManager;
private final String storyId;
private final Comment comment;
private final String feedId;
private final Context context;
private final String userId;
private Bitmap userImage;
private WeakReference<FlowLayout> favouriteAvatarHolder;
private UserDetails user;
public LikeCommentTask(final Context context, final APIManager apiManager, final ImageView favouriteIcon, final FlowLayout favouriteAvatarLayout, final String storyId, final Comment comment, final String feedId, final String userId) {
this.apiManager = apiManager;
this.storyId = storyId;
this.comment = comment;
this.feedId = feedId;
this.context = context;
this.userId = userId;
favouriteAvatarHolder = new WeakReference<FlowLayout>(favouriteAvatarLayout);
favouriteIconViewHolder = new WeakReference<ImageView>(favouriteIcon);
userImage = PrefsUtils.getUserImage(context);
user = PrefsUtils.getUserDetails(context);
}
@Override
protected Boolean doInBackground(Void... params) {
return apiManager.favouriteComment(storyId, comment.userId, feedId);
}
@Override
protected void onPostExecute(Boolean result) {
if (favouriteIconViewHolder.get() != null) {
if (result.booleanValue()) {
favouriteIconViewHolder.get().setImageResource(R.drawable.have_favourite);
if (userImage != null) {
ImageView favouriteImage = new ImageView(context);
favouriteImage.setTag(user.id);
userImage = UIUtils.roundCorners(userImage, 10f);
favouriteImage.setImageBitmap(userImage);
favouriteAvatarHolder.get().addView(favouriteImage);
}
String[] newArray = new String[comment.likingUsers.length + 1];
System.arraycopy(comment.likingUsers, 0, newArray, 0, comment.likingUsers.length);
newArray[newArray.length - 1] = userId;
comment.likingUsers = newArray;
Toast.makeText(context, R.string.comment_favourited, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, R.string.error_liking_comment, Toast.LENGTH_SHORT).show();
}
}
}
}

View file

@ -1,294 +0,0 @@
package com.newsblur.network;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.activity.Profile;
import com.newsblur.domain.Comment;
import com.newsblur.domain.Reply;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.domain.UserProfile;
import com.newsblur.fragment.ReplyDialogFragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ViewUtils;
import com.newsblur.view.FlowLayout;
public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
public static final String COMMENT_BY = "commentBy";
public static final String COMMENT_DATE_BY = "commentDateBy";
public static final String COMMENT_VIEW_BY = "commentViewBy";
private ArrayList<View> publicCommentViews;
private ArrayList<View> friendCommentViews;
private final APIManager apiManager;
private final Story story;
private final LayoutInflater inflater;
private final ImageLoader imageLoader;
private WeakReference<View> viewHolder;
private final Context context;
private UserDetails user;
private final FragmentManager manager;
private List<Comment> comments;
public SetupCommentSectionTask(final Context context, final View view, final FragmentManager manager, LayoutInflater inflater, final APIManager apiManager, final Story story, final ImageLoader imageLoader) {
this.context = context;
this.manager = manager;
this.inflater = inflater;
this.apiManager = apiManager;
this.story = story;
this.imageLoader = imageLoader;
viewHolder = new WeakReference<View>(view);
user = PrefsUtils.getUserDetails(context);
}
@Override
protected Void doInBackground(Void... arg0) {
comments = FeedUtils.dbHelper.getComments(story.id);
publicCommentViews = new ArrayList<View>();
friendCommentViews = new ArrayList<View>();
for (final Comment comment : comments) {
// skip public comments if they are disabled
if (!comment.byFriend && !PrefsUtils.showPublicComments(context)) {
continue;
}
UserProfile commentUser = FeedUtils.dbHelper.getUserProfile(comment.userId);
// rarely, we get a comment but never got the user's profile, so we can't display it
if (commentUser == null) {
Log.w(this.getClass().getName(), "cannot display comment from missing user ID: " + comment.userId);
continue;
}
View commentView = inflater.inflate(R.layout.include_comment, null);
commentView.setTag(COMMENT_VIEW_BY + comment.userId);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setText(Html.fromHtml(comment.commentText));
commentText.setTag(COMMENT_BY + comment.userId);
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
commentSharedDate.setText(comment.sharedDate + " ago");
commentSharedDate.setTag(COMMENT_DATE_BY + comment.userId);
final FlowLayout favouriteContainer = (FlowLayout) commentView.findViewById(R.id.comment_favourite_avatars);
final ImageView favouriteIcon = (ImageView) commentView.findViewById(R.id.comment_favourite_icon);
final ImageView replyIcon = (ImageView) commentView.findViewById(R.id.comment_reply_icon);
if (comment.likingUsers != null) {
if (Arrays.asList(comment.likingUsers).contains(user.id)) {
favouriteIcon.setImageResource(R.drawable.have_favourite);
}
for (String id : comment.likingUsers) {
ImageView favouriteImage = new ImageView(context);
UserProfile user = FeedUtils.dbHelper.getUserProfile(id);
imageLoader.displayImage(user.photoUrl, favouriteImage, 10f);
favouriteImage.setTag(id);
favouriteContainer.addView(favouriteImage);
}
favouriteIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!Arrays.asList(comment.likingUsers).contains(user.id)) {
new LikeCommentTask(context, apiManager, favouriteIcon, favouriteContainer, story.id, comment, story.feedId, user.id).execute();
} else {
new UnLikeCommentTask(context, apiManager, favouriteIcon, favouriteContainer, story.id, comment, story.feedId, user.id).execute();
}
}
});
}
replyIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story != null) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(comment.userId);
DialogFragment newFragment = ReplyDialogFragment.newInstance(story, comment.userId, user.username);
newFragment.show(manager, "dialog");
}
}
});
List<Reply> replies = FeedUtils.dbHelper.getCommentReplies(comment.id);
for (Reply reply : replies) {
View replyView = inflater.inflate(R.layout.include_reply, null);
TextView replyText = (TextView) replyView.findViewById(R.id.reply_text);
replyText.setText(Html.fromHtml(reply.text));
ImageView replyImage = (ImageView) replyView.findViewById(R.id.reply_user_image);
final UserProfile replyUser = FeedUtils.dbHelper.getUserProfile(reply.userId);
if (replyUser != null) {
imageLoader.displayImage(replyUser.photoUrl, replyImage);
replyImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, replyUser.userId);
context.startActivity(i);
}
});
TextView replyUsername = (TextView) replyView.findViewById(R.id.reply_username);
replyUsername.setText(replyUser.username);
} else {
TextView replyUsername = (TextView) replyView.findViewById(R.id.reply_username);
replyUsername.setText(R.string.unknown_user);
}
TextView replySharedDate = (TextView) replyView.findViewById(R.id.reply_shareddate);
replySharedDate.setText(reply.shortDate + " ago");
((LinearLayout) commentView.findViewById(R.id.comment_replies_container)).addView(replyView);
}
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
commentUsername.setText(commentUser.username);
String userPhoto = commentUser.photoUrl;
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
if (!TextUtils.isEmpty(commentUser.location)) {
commentLocation.setText(commentUser.location.toUpperCase());
} else {
commentLocation.setVisibility(View.GONE);
}
if (!TextUtils.isEmpty(comment.sourceUserId)) {
commentImage.setVisibility(View.INVISIBLE);
ImageView usershareImage = (ImageView) commentView.findViewById(R.id.comment_user_reshare_image);
ImageView sourceUserImage = (ImageView) commentView.findViewById(R.id.comment_sharesource_image);
sourceUserImage.setVisibility(View.VISIBLE);
usershareImage.setVisibility(View.VISIBLE);
commentImage.setVisibility(View.INVISIBLE);
UserProfile sourceUser = FeedUtils.dbHelper.getUserProfile(comment.sourceUserId);
if (sourceUser != null) {
imageLoader.displayImage(sourceUser.photoUrl, sourceUserImage, 10f);
imageLoader.displayImage(userPhoto, usershareImage, 10f);
}
} else {
imageLoader.displayImage(userPhoto, commentImage, 10f);
}
if (comment.byFriend) {
friendCommentViews.add(commentView);
} else {
publicCommentViews.add(commentView);
}
commentImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, comment.userId);
context.startActivity(i);
}
});
}
return null;
}
protected void onPostExecute(Void result) {
if (viewHolder.get() != null) {
FlowLayout sharedGrid = (FlowLayout) viewHolder.get().findViewById(R.id.reading_social_shareimages);
FlowLayout commentGrid = (FlowLayout) viewHolder.get().findViewById(R.id.reading_social_commentimages);
TextView friendCommentTotal = ((TextView) viewHolder.get().findViewById(R.id.reading_friend_comment_total));
TextView publicCommentTotal = ((TextView) viewHolder.get().findViewById(R.id.reading_public_comment_total));
ViewUtils.setupCommentCount(context, viewHolder.get(), comments.size());
ViewUtils.setupShareCount(context, viewHolder.get(), story.sharedUserIds.length);
Set<String> commentIds = new HashSet<String>();
for (Comment comment : comments) {
commentIds.add(comment.userId);
}
for (String userId : story.sharedUserIds) {
if (!commentIds.contains(userId)) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(userId);
if (user != null) {
ImageView image = ViewUtils.createSharebarImage(context, imageLoader, user.photoUrl, user.userId);
sharedGrid.addView(image);
}
}
}
for (Comment comment : comments) {
UserProfile user = FeedUtils.dbHelper.getUserProfile(comment.userId);
ImageView image = ViewUtils.createSharebarImage(context, imageLoader, user.photoUrl, user.userId);
commentGrid.addView(image);
}
if (publicCommentViews.size() > 0) {
String commentCount = context.getString(R.string.public_comment_count);
if (publicCommentViews.size() == 1) {
commentCount = commentCount.substring(0, commentCount.length() - 1);
}
publicCommentTotal.setText(String.format(commentCount, publicCommentViews.size()));
viewHolder.get().findViewById(R.id.reading_public_comment_header).setVisibility(View.VISIBLE);
}
if (friendCommentViews.size() > 0) {
String commentCount = context.getString(R.string.friends_comments_count);
if (friendCommentViews.size() == 1) {
commentCount = commentCount.substring(0, commentCount.length() - 1);
}
friendCommentTotal.setText(String.format(commentCount, friendCommentViews.size()));
viewHolder.get().findViewById(R.id.reading_friend_comment_header).setVisibility(View.VISIBLE);
}
for (int i = 0; i < publicCommentViews.size(); i++) {
if (i == publicCommentViews.size() - 1) {
publicCommentViews.get(i).findViewById(R.id.comment_divider).setVisibility(View.GONE);
}
((LinearLayout) viewHolder.get().findViewById(R.id.reading_public_comment_container)).addView(publicCommentViews.get(i));
}
for (int i = 0; i < friendCommentViews.size(); i++) {
if (i == friendCommentViews.size() - 1) {
friendCommentViews.get(i).findViewById(R.id.comment_divider).setVisibility(View.GONE);
}
((LinearLayout) viewHolder.get().findViewById(R.id.reading_friend_comment_container)).addView(friendCommentViews.get(i));
}
}
}
}

View file

@ -1,77 +0,0 @@
package com.newsblur.network;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.domain.Comment;
import com.newsblur.domain.UserDetails;
import com.newsblur.util.PrefsUtils;
import com.newsblur.view.FlowLayout;
public class UnLikeCommentTask extends AsyncTask<Void, Void, Boolean>{
private static final String TAG = "LikeCommentTask";
final WeakReference<ImageView> favouriteIconViewHolder;
private final APIManager apiManager;
private final String storyId;
private final Comment comment;
private final String feedId;
private final Context context;
private final String userId;
private WeakReference<FlowLayout> favouriteAvatarHolder;
private UserDetails user;
public UnLikeCommentTask(final Context context, final APIManager apiManager, final ImageView favouriteIcon, final FlowLayout favouriteAvatarContainer, final String storyId, final Comment comment, final String feedId, final String userId) {
this.apiManager = apiManager;
this.storyId = storyId;
this.comment = comment;
this.feedId = feedId;
this.context = context;
this.userId = userId;
favouriteIconViewHolder = new WeakReference<ImageView>(favouriteIcon);
favouriteAvatarHolder = new WeakReference<FlowLayout>(favouriteAvatarContainer);
user = PrefsUtils.getUserDetails(context);
}
@Override
protected Boolean doInBackground(Void... params) {
return apiManager.unFavouriteComment(storyId, comment.userId, feedId);
}
@Override
protected void onPostExecute(Boolean result) {
if (favouriteIconViewHolder.get() != null) {
if (result.booleanValue()) {
favouriteIconViewHolder.get().setImageResource(R.drawable.favourite);
View v = favouriteAvatarHolder.get().findViewWithTag(user.id);
favouriteAvatarHolder.get().removeView(v);
ArrayList<String> likingUsers = new ArrayList<String>();
for (String user : comment.likingUsers) {
if (!TextUtils.equals(user, userId) && TextUtils.isEmpty(user)) {
likingUsers.add(user);
}
}
String[] newArray = new String[likingUsers.size()];
likingUsers.toArray(newArray);
comment.likingUsers = newArray;
Toast.makeText(context, "Removed favourite", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "Error removing favorite from comment", Toast.LENGTH_SHORT).show();
}
}
}
}

View file

@ -22,8 +22,10 @@ import com.newsblur.util.AppConstants;
public class FeedFolderResponse {
/** Helper variable so users of the parser can pass along how long it took to read the JSON stream, for instrumentation. */
/** Helper variables so users of the parser can pass along instrumentation. */
public long connTime;
public long readTime;
public long parseTime;
public Set<Folder> folders;
public Set<Feed> feeds;
@ -35,6 +37,7 @@ public class FeedFolderResponse {
public int starredCount;
public FeedFolderResponse(String json, Gson gson) {
long startTime = System.currentTimeMillis();
JsonParser parser = new JsonParser();
JsonObject asJsonObject = parser.parse(json).getAsJsonObject();
@ -93,6 +96,8 @@ public class FeedFolderResponse {
folders.add(emptyRootFolder);
Log.d( this.getClass().getName(), "root folder was missing. added it.");
}
parseTime = System.currentTimeMillis() - startTime;
}
/**

View file

@ -5,6 +5,7 @@ import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import android.text.TextUtils;
import android.util.Log;
@ -15,8 +16,15 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
public class DateStringTypeAdapter implements JsonDeserializer<Date> {
// 2012-07-23 02:43:02
private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final DateFormat df;
public DateStringTypeAdapter() {
// API sends dates like 2012-07-23 02:43:02
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// API doesn't indicate TZ, but it is UTC
df.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@Override
public Date deserialize(JsonElement element, Type type, JsonDeserializationContext arg2) throws JsonParseException {

View file

@ -69,6 +69,10 @@ public class ImagePrefetchService extends SubService {
return ImageQueue.size();
}
public static void clear() {
ImageQueue.clear();
}
public static boolean running() {
return Running;
}

View file

@ -97,7 +97,9 @@ public class NBSyncService extends Service {
private volatile static boolean isMemoryLow = false;
private static long lastFeedCount = 0L;
private static long lastFFConnMillis = 0L;
private static long lastFFReadMillis = 0L;
private static long lastFFParseMillis = 0L;
private static long lastFFWriteMillis = 0L;
/** Feed set that we need to sync immediately for the UI. */
@ -386,7 +388,7 @@ public class NBSyncService extends Service {
ExhaustedFeeds.clear();
FeedPagesSeen.clear();
FeedStoriesSeen.clear();
UnreadsService.clearHashes();
UnreadsService.clear();
RecountCandidates.clear();
FeedFolderResponse feedResponse = apiManager.getFolderFeedMapping(true);
@ -402,7 +404,9 @@ public class NBSyncService extends Service {
return;
}
lastFFConnMillis = feedResponse.connTime;
lastFFReadMillis = feedResponse.readTime;
lastFFParseMillis = feedResponse.parseTime;
long startTime = System.currentTimeMillis();
isPremium = feedResponse.isPremium;
@ -838,6 +842,19 @@ public class NBSyncService extends Service {
HaltNow = true;
}
/**
* Resets any internal temp vars or queues. Called when switching accounts.
*/
public static void clearState() {
clearPendingStoryRequest();
FollowupActions.clear();
RecountCandidates.clear();
resetFeeds();
OriginalTextService.clear();
UnreadsService.clear();
ImagePrefetchService.clear();
}
public static void resumeFromInterrupt() {
HaltNow = false;
}
@ -879,7 +896,11 @@ public class NBSyncService extends Service {
public static String getSpeedInfo() {
StringBuilder s = new StringBuilder();
s.append(lastFeedCount).append(" in ").append(lastFFReadMillis).append(" and ").append(lastFFWriteMillis);
s.append(lastFeedCount).append(" feeds in ");
s.append(" conn:").append(lastFFConnMillis);
s.append(" read:").append(lastFFReadMillis);
s.append(" parse:").append(lastFFParseMillis);
s.append(" store:").append(lastFFWriteMillis);
return s.toString();
}

View file

@ -70,6 +70,11 @@ public class OriginalTextService extends SubService {
return (Hashes.size() + PriorityHashes.size());
}
public static void clear() {
Hashes.clear();
PriorityHashes.clear();
}
public static boolean running() {
return Running;
}

View file

@ -145,7 +145,7 @@ public class UnreadsService extends SubService {
return true;
}
public static void clearHashes() {
public static void clear() {
StoryHashQueue.clear();
}

View file

@ -54,7 +54,7 @@ public class FeedUtils {
protected Void doInBackground(Void... arg) {
ReadingAction ra = (saved ? ReadingAction.saveStory(story.storyHash) : ReadingAction.unsaveStory(story.storyHash));
ra.doLocal(dbHelper);
NbActivity.updateAllActivities();
NbActivity.updateAllActivities(true);
dbHelper.enqueueAction(ra);
triggerSync(context);
return null;
@ -167,7 +167,6 @@ public class FeedUtils {
}
public static void updateClassifier(final String feedId, final String key, final Classifier classifier, final int classifierType, final int classifierAction, final Context context) {
// first, update the server
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
@ -190,7 +189,7 @@ public class FeedUtils {
dbHelper.insertClassifier(classifier);
}
public static void shareStory(Story story, Context context) {
public static void sendStory(Story story, Context context) {
if (story == null ) { return; }
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
@ -199,7 +198,42 @@ public class FeedUtils {
final String shareString = context.getResources().getString(R.string.share);
intent.putExtra(Intent.EXTRA_TEXT, String.format(shareString, new Object[] { Html.fromHtml(story.title),
story.permalink }));
context.startActivity(Intent.createChooser(intent, "Share using"));
context.startActivity(Intent.createChooser(intent, "Send using"));
}
public static void shareStory(Story story, String comment, String sourceUserId, Context context) {
if (story.sourceUserId != null) {
sourceUserId = story.sourceUserId;
}
ReadingAction ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(true);
triggerSync(context);
}
public static void likeComment(Story story, String commentUserId, Context context) {
ReadingAction ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(true);
triggerSync(context);
}
public static void unlikeComment(Story story, String commentUserId, Context context) {
ReadingAction ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(true);
triggerSync(context);
}
public static void replyToComment(String storyId, String feedId, String commentUserId, String replyText, Context context) {
ReadingAction ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(true);
triggerSync(context);
}
public static FeedSet feedSetFromFolderName(String folderName) {

View file

@ -99,6 +99,7 @@ public class PrefsUtils {
public static void logout(Context context) {
NBSyncService.softInterrupt();
NBSyncService.clearState();
// wipe the prefs store
context.getSharedPreferences(PrefConstants.PREFERENCES, 0).edit().clear().commit();
@ -114,6 +115,7 @@ public class PrefsUtils {
public static void clearPrefsAndDbForLoginAs(Context context) {
NBSyncService.softInterrupt();
NBSyncService.clearState();
// wipe the prefs store except for the cookie and login keys since we need to
// authenticate further API calls

View file

@ -18,7 +18,11 @@ public class ReadingAction {
MARK_READ,
MARK_UNREAD,
SAVE,
UNSAVE
UNSAVE,
SHARE,
REPLY,
LIKE_COMMENT,
UNLIKE_COMMENT
};
private ActionType type;
@ -26,6 +30,11 @@ public class ReadingAction {
private FeedSet feedSet;
private Long olderThan;
private Long newerThan;
private String storyId;
private String feedId;
private String sourceUserId;
private String commentReplyText; // used for both comments and replies
private String commentUserId;
private ReadingAction() {
; // must use helpers
@ -68,6 +77,45 @@ public class ReadingAction {
return ra;
}
public static ReadingAction shareStory(String hash, String storyId, String feedId, String sourceUserId, String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.SHARE;
ra.storyHash = hash;
ra.storyId = storyId;
ra.feedId = feedId;
ra.sourceUserId = sourceUserId;
ra.commentReplyText = commentReplyText;
return ra;
}
public static ReadingAction likeComment(String storyId, String commentUserId, String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.LIKE_COMMENT;
ra.storyId = storyId;
ra.commentUserId = commentUserId;
ra.feedId = feedId;
return ra;
}
public static ReadingAction unlikeComment(String storyId, String commentUserId, String feedId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.UNLIKE_COMMENT;
ra.storyId = storyId;
ra.commentUserId = commentUserId;
ra.feedId = feedId;
return ra;
}
public static ReadingAction replyToComment(String storyId, String feedId, String commentUserId, String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.REPLY;
ra.storyId = storyId;
ra.commentUserId = commentUserId;
ra.feedId = feedId;
ra.commentReplyText = commentReplyText;
return ra;
}
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.ACTION_TIME, System.currentTimeMillis());
@ -101,6 +149,37 @@ public class ReadingAction {
values.put(DatabaseConstants.ACTION_STORY_HASH, storyHash);
break;
case SHARE:
values.put(DatabaseConstants.ACTION_SHARE, 1);
values.put(DatabaseConstants.ACTION_STORY_HASH, storyHash);
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_SOURCE_USER_ID, sourceUserId);
values.put(DatabaseConstants.ACTION_COMMENT_TEXT, commentReplyText);
break;
case LIKE_COMMENT:
values.put(DatabaseConstants.ACTION_LIKE_COMMENT, 1);
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_COMMENT_ID, commentUserId);
break;
case UNLIKE_COMMENT:
values.put(DatabaseConstants.ACTION_UNLIKE_COMMENT, 1);
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_COMMENT_ID, commentUserId);
break;
case REPLY:
values.put(DatabaseConstants.ACTION_REPLY, 1);
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_COMMENT_ID, commentUserId);
values.put(DatabaseConstants.ACTION_COMMENT_TEXT, commentReplyText);
break;
default:
throw new IllegalStateException("cannot serialise uknown type of action.");
@ -135,6 +214,29 @@ public class ReadingAction {
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_UNSAVE)) == 1) {
ra.type = ActionType.UNSAVE;
ra.storyHash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_SHARE)) == 1) {
ra.type = ActionType.SHARE;
ra.storyHash = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_HASH));
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.sourceUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_SOURCE_USER_ID));
ra.commentReplyText = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_TEXT));
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_LIKE_COMMENT)) == 1) {
ra.type = ActionType.LIKE_COMMENT;
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_UNLIKE_COMMENT)) == 1) {
ra.type = ActionType.UNLIKE_COMMENT;
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
} else if (c.getInt(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_REPLY)) == 1) {
ra.type = ActionType.REPLY;
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
ra.commentReplyText = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_TEXT));
} else {
throw new IllegalStateException("cannot deserialise uknown type of action.");
}
@ -164,6 +266,18 @@ public class ReadingAction {
case UNSAVE:
return apiManager.markStoryAsUnstarred(storyHash);
case SHARE:
return apiManager.shareStory(storyId, feedId, commentReplyText, sourceUserId);
case LIKE_COMMENT:
return apiManager.favouriteComment(storyId, commentUserId, feedId);
case UNLIKE_COMMENT:
return apiManager.unFavouriteComment(storyId, commentUserId, feedId);
case REPLY:
return apiManager.replyToComment(storyId, feedId, commentUserId, commentReplyText);
default:
}
@ -197,6 +311,23 @@ public class ReadingAction {
dbHelper.setStoryStarred(storyHash, false);
break;
case SHARE:
dbHelper.setStoryShared(storyHash);
dbHelper.insertUpdateComment(storyId, feedId, commentReplyText);
break;
case LIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
break;
case UNLIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
break;
case REPLY:
dbHelper.replyToComment(storyId, feedId, commentUserId, commentReplyText);
break;
default:
// not all actions have these, which is fine
}

View file

@ -1513,7 +1513,21 @@
sourceName]];
[[UIApplication sharedApplication] openURL:activityURL];
return;
return;
} else if ([[preferences stringForKey:@"story_browser"] isEqualToString:@"opera_mini"] &&
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"opera-http://"]]) {
NSString *operaURL;
NSRange prefix = [[url absoluteString] rangeOfString: @"http"];
if (NSNotFound != prefix.location) {
operaURL = [[url absoluteString]
stringByReplacingCharactersInRange: prefix
withString: @"opera-http"];
}
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:operaURL]];
return;
} else {
self.activeOriginalStoryURL = url;

View file

@ -414,6 +414,7 @@
<string>In-app browser</string>
<string>Safari</string>
<string>Chrome</string>
<string>Opera Mini</string>
</array>
<key>DefaultValue</key>
<string>inapp</string>
@ -422,6 +423,7 @@
<string>inapp</string>
<string>safari</string>
<string>chrome</string>
<string>opera_mini</string>
</array>
<key>Key</key>
<string>story_browser</string>

View file

@ -434,6 +434,7 @@
<string>In-app browser</string>
<string>Safari</string>
<string>Chrome</string>
<string>Opera Mini</string>
</array>
<key>DefaultValue</key>
<string>inapp</string>
@ -442,6 +443,7 @@
<string>inapp</string>
<string>safari</string>
<string>chrome</string>
<string>opera_mini</string>
</array>
<key>Key</key>
<string>story_browser</string>

View file

@ -86,7 +86,7 @@ backend gunicorn
backend gunicorn_counts
balance roundrobin
option httpchk GET /_haproxychk
server gunicorndebug 127.0.0.1:8000 check inter 2000ms
server gunicorndebug 127.0.0.1:8000 check inter 600000ms
backend maintenance
option httpchk HEAD /maintenance HTTP/1.1\r\nHost:\ www

View file

@ -108,8 +108,8 @@ a:active {
blockquote {
background-color: #F0F0F0;
border-left: 1px solid #9b9b9b;
padding: 0.5em 2em;
border-left: 1px solid #9B9B9B;
padding: .5em 2em;
margin: 10px;
}
@ -187,7 +187,7 @@ blockquote {
box-sizing: border-box;
}
.NB-blue-button:hover {
border: 1px solid #3573a5;
border: 1px solid #3573A5;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5aa1d8), color-stop(100%, #4d98d2));
background-image: -webkit-linear-gradient(#5aa1d8, #4d98d2);
background-image: -moz-linear-gradient(#5aa1d8, #4d98d2);
@ -195,7 +195,7 @@ blockquote {
background-image: linear-gradient(#5aa1d8, #4d98d2);
}
.NB-blue-button:active {
border: 1px solid #2b5c84;
border: 1px solid #2B5C84;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4d98d2), color-stop(100%, #4d98d2));
background-image: -webkit-linear-gradient(#4d98d2, #4d98d2);
background-image: -moz-linear-gradient(#4d98d2, #4d98d2);
@ -569,8 +569,8 @@ blockquote {
}
.NB-story {
border-left: 4px solid #6a6a6a;
border-right: 4px solid #6a6a6a;
border-left: 4px solid #6A6A6A;
border-right: 4px solid #6A6A6A;
background: #fff;
-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.15);
@ -797,7 +797,7 @@ blockquote {
z-index: 10;
margin: 0 30px;
background: #f6f6f6;
border: 1px solid #cccccc;
border: 1px solid #CCC;
padding: 0 20px;
border-top: 0;
}
@ -908,6 +908,19 @@ blockquote {
display: none;
}
}
.NB-story-comments-container .NB-story-comments-shares .NB-story-comment .NB-story-comment-author-container {
margin-top: 5px;
}
.NB-story-comments-container .NB-story-comments-shares .NB-user-avatar img {
width: 18px;
height: 18px;
margin-left: 8px;
}
.NB-story-comments-container .NB-story-comments-shares .NB-user-avatar.NB-story-comment-reshare img {
width: 18px;
height: 18px;
margin-left: 4px;
}
.NB-story-comments-container .NB-story-comment-reply {
margin-top: 15px;
padding: 15px 0 0px 44px;
@ -1169,6 +1182,10 @@ blockquote {
border-bottom: 1px solid #bdbdbd;
padding: 3px 0;
}
.NB-story-comments-container .NB-story-comments-public-header-wrapper.NB-public-top {
margin-top: 0;
padding-top: 9px;
}
.NB-story-comments-container .NB-story-comment-likes {
overflow: hidden;
height: 14px;
@ -1222,10 +1239,10 @@ blockquote {
overflow: hidden;
color: rgba(0, 0, 0, 0.5);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
-webkit-transition: all 0.32s ease-out;
-moz-transition: all 0.32s ease-out;
-o-transition: all 0.32s ease-out;
-ms-transition: all 0.32s ease-out;
-webkit-transition: all .32s ease-out;
-moz-transition: all .32s ease-out;
-o-transition: all .32s ease-out;
-ms-transition: all .32s ease-out;
}
.NB-page-controls-next .NB-page-controls-text,
.NB-page-controls-end .NB-page-controls-text {
@ -1302,7 +1319,7 @@ footer {
right: 60px;
display: inline-block;
border-right: 7px solid rgba(0, 0, 0, 0);
border-bottom: 7px solid #cccccc;
border-bottom: 7px solid #CCC;
border-left: 7px solid rgba(0, 0, 0, 0);
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
@ -1313,7 +1330,7 @@ footer {
right: 61px;
display: inline-block;
border-right: 6px solid rgba(0, 0, 0, 0);
border-bottom: 6px solid white;
border-bottom: 6px solid #FFF;
border-left: 6px solid rgba(0, 0, 0, 0);
content: '';
}
@ -1342,7 +1359,7 @@ footer {
}
.popover .popover-title {
padding: 16px 14px;
border-bottom: 1px solid silver;
border-bottom: 1px solid #C0C0C0;
position: relative;
}
.popover .popover-content {
@ -1351,7 +1368,7 @@ footer {
}
.popover input {
border: 1px solid #bdbdbd;
border: 1px solid #bdbdbd;
border: 1px solid #BDBDBD;
padding: 5px;
width: 100%;
margin-bottom: 8px;

View file

@ -67,7 +67,7 @@ $default-box-shadow-blur: 1px;
&:active {
border: 1px solid $border-color-active;
@include background-image(linear-gradient(#e9e9e9, #e9e9e9));
@include single-box-shadow(#c1c1c1, 0px, 0px, 10px, false, true);
@include single-box-shadow($color: #c1c1c1, $hoff:0px, $voff:0px, $blur:10px, $inset:true);
color: $text-color;
}
@media screen and (max-width: 580px) {
@ -101,7 +101,7 @@ $default-box-shadow-blur: 1px;
&:active {
border: 1px solid #2B5C84;
@include background-image(linear-gradient(#4d98d2, #4d98d2));
@include single-box-shadow(#1f74b8, 0px, 0px, 10px, false, true);
@include single-box-shadow($color: #1f74b8, $hoff:0px, $voff:0px, $blur:10px, $inset:true);
}
@media screen and (max-width: 580px) {
padding: 2px 6px;
@ -234,7 +234,7 @@ blockquote {
.NB-header {
@include single-box-shadow(#7d7d7d, 0px, 0px, 10px, false, false);
@include single-box-shadow($color: #7d7d7d, $hoff:0px, $voff:0px, $blur:10px, $inset:false);
width: 100%;
left: 0;
top: 0;
@ -563,7 +563,7 @@ blockquote {
border-left: 4px solid #6A6A6A;
border-right: 4px solid #6A6A6A;
background: #fff;
@include single-box-shadow(rgba(0, 0, 0, .15), 0px, 0px, 10px, false, false);
@include single-box-shadow($color: rgba(0, 0, 0, .15), $hoff:0px, $voff:0px, $blur:10px, $inset:false);
position: relative;
z-index: 1;
@ -829,14 +829,13 @@ blockquote {
@include border-radius(3px, 3px);
/* @include single-box-shadow; gets in the way of transparent PNGs */
@media screen and ( max-width: 580px) {
@media screen and ( max-width: 580px) {
height: 24px;
width: 24px;
top: 12px;
}
}
.NB-user-avatar.NB-story-comment-reshare img {
top: 27px;
left: 0px;
@ -916,6 +915,25 @@ blockquote {
}
}
.NB-story-comments-shares {
.NB-story-comment {
.NB-story-comment-author-container {
margin-top: 5px;
}
}
.NB-user-avatar img {
width: 18px;
height: 18px;
margin-left: 8px;
}
.NB-user-avatar.NB-story-comment-reshare img {
width: 18px;
height: 18px;
margin-left: 4px;
}
}
.NB-story-comment-reply {
margin-top: 15px;
padding: 15px 0 0px 44px;
@ -1088,6 +1106,11 @@ blockquote {
font-size: $smallest-font-size;
border-bottom: 1px solid $border-color;
padding: 3px 0;
&.NB-public-top {
margin-top: 0;
padding-top: 9px;
}
}
.NB-story-comment-likes {

View file

@ -3213,6 +3213,9 @@ body {
left: 6px;
z-index: 1;
}
.NB-story-comment .NB-story-comment-friend-share .NB-story-comment-author-avatar.NB-user-avatar.NB-story-comment-reshare {
top: 10px;
}
.NB-story-comment .NB-story-comment-author-avatar.NB-user-avatar img {
border-radius: 6px;
margin: 2px 0 0 1px;
@ -3223,10 +3226,21 @@ body {
height: 24px;
width: 24px;
}
.NB-story-comment .NB-story-comment-friend-share .NB-story-comment-author-avatar.NB-user-avatar img {
height: 24px;
width: 24px;
margin-left: 6px;
}
.NB-story-comment .NB-story-comment-friend-share .NB-story-comment-author-avatar.NB-user-avatar.NB-story-comment-reshare img {
margin-left: 0px;
}
.NB-story-comment .NB-story-comment-author-container {
overflow: hidden;
margin: 6px 0 0;
}
.NB-story-comment .NB-story-comment-friend-share .NB-story-comment-author-container {
margin-top: 10px;
}
.NB-story-comment .NB-story-comment-reshares {
position: absolute;
top: 0;
@ -3283,7 +3297,8 @@ body {
width: auto;
height: auto;
}
.NB-story-comment .NB-story-comment-likes-users .NB-story-share-profile .NB-user-avatar img {
.NB-story-comment .NB-story-comment-likes-users .NB-story-share-profile .NB-user-avatar img,
.NB-story-comment .NB-story-comment-friend-share .NB-story-comment-likes-users .NB-story-share-profile .NB-user-avatar img {
width: 12px;
height: 12px;
}
@ -3536,6 +3551,11 @@ body {
height: 22px;
border-radius: 3px;
}
.NB-story-comment-friend-share .NB-story-share-profile .NB-user-avatar img.NB-user-avatar-image {
height: 18px;
width: 18px;
}
.NB-story-share-profile .NB-user-avatar img.NB-user-avatar-private {
width: 8px;
height: 8px;
@ -7678,6 +7698,12 @@ form.opml_import_form input {
word-wrap: break-word;
overflow: hidden;
}
.NB-modal-admin .NB-admin-training-counts span {
padding-right: 8px;
}
.NB-modal-admin .NB-admin-training-counts span.NB-grey {
color: #D0D0D0;
}
/* ===================== */
/* = Email Story Modal = */
@ -11127,8 +11153,18 @@ form.opml_import_form input {
.NB-interaction .NB-splash-link:hover {
color: #405BA8;
}
.NB-interaction-content {
font-size: 11px;
padding-top: 4px;
line-height: 16px;
color: #9d9d9d;
}
.NB-interaction-content a {
color: #A6A9B0;
}
.NB-interaction:active:not(.NB-disabled) .NB-interaction-content .NB-splash-link {
color: #A85B40;
color: #405BA8;
text-decoration: none;
}
.NB-interaction-photo {
position: absolute;
@ -11144,7 +11180,7 @@ form.opml_import_form input {
height: 16px;
}
.NB-interaction-date {
color: #929697;
color: #5e828b;
font-size: 10px;
text-transform: uppercase;
padding: 5px 0 0;
@ -11159,12 +11195,6 @@ form.opml_import_form input {
padding: 2px 0 0 0;
opacity: .9;
}
.NB-interaction-content {
font-size: 11px;
padding-top: 4px;
line-height: 16px;
color: #b6b6b6;
}
.NB-interaction-username {
cursor: pointer;
}

View file

@ -1641,6 +1641,10 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.make_request('/profile/refund_premium', data, callback, error_callback);
},
never_expire_premium: function(data, callback, error_callback) {
this.make_request('/profile/never_expire_premium', data, callback, error_callback);
},
delete_saved_stories: function(timestamp, callback, error_callback) {
var self = this;
var pre_callback = function(data) {

View file

@ -14,6 +14,7 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
populate_comments: function(story, collection) {
this.friend_comments = new NEWSBLUR.Collections.Comments(this.get('friend_comments'));
this.friend_shares = new NEWSBLUR.Collections.Comments(this.get('friend_shares'));
this.public_comments = new NEWSBLUR.Collections.Comments(this.get('public_comments'));
},

View file

@ -1,5 +1,7 @@
NEWSBLUR.ReaderUserAdmin = function(options) {
var defaults = {};
var defaults = {
width: 700
};
this.options = $.extend({}, defaults, options);
this.model = NEWSBLUR.assets;
@ -78,6 +80,7 @@ _.extend(NEWSBLUR.ReaderUserAdmin.prototype, {
]));
$actions.append($.make('div', { className: "NB-modal-submit-button NB-modal-submit-green NB-admin-action-refund", style: "float: left" }, "Full Refund"));
$actions.append($.make('div', { className: "NB-modal-submit-button NB-modal-submit-green NB-admin-action-refund-partial", style: "float: left" }, "Refund $12"));
$actions.append($.make('div', { className: "NB-modal-submit-button NB-modal-submit-green NB-admin-action-never-expire", style: "float: left" }, "Never expire"));
} else {
$actions.append($.make('div', { className: "NB-modal-submit-button NB-modal-submit-green NB-admin-action-upgrade" }, "Upgrade to premium"));
}
@ -86,20 +89,29 @@ _.extend(NEWSBLUR.ReaderUserAdmin.prototype, {
$actions.append($.make('div', { className: "NB-modal-submit-button NB-modal-submit-green NB-admin-action-opml", style: "float: left" }, "OPML"));
$statistics.append($.make('dl', [
$.make('dt', 'Stripe Id:'),
$.make('dd', $.make('a', { href: "https://manage.stripe.com/customers/" + data.statistics.stripe_id, className: 'NB-splash-link' }, data.statistics.stripe_id)),
$.make('dt', 'Created:'),
$.make('dd', data.statistics.created_date),
$.make('dt', 'Last seen:'),
$.make('dd', data.statistics.last_seen_date),
$.make('dt', 'Timezone:'),
$.make('dd', data.statistics.timezone),
$.make('dt', 'Email:'),
$.make('dd', data.statistics.email),
$.make('dt', 'Stripe Id:'),
$.make('dd', $.make('a', { href: "https://manage.stripe.com/customers/" + data.statistics.stripe_id, className: 'NB-splash-link' }, data.statistics.stripe_id)),
$.make('dt', 'Feeds:'),
$.make('dd', Inflector.commas(data.statistics.feeds)),
$.make('dt', 'Feed opens:'),
$.make('dd', Inflector.commas(data.statistics.feed_opens)),
$.make('dt', 'Read Stories:'),
$.make('dd', Inflector.commas(data.statistics.read_story_count))
$.make('dd', Inflector.commas(data.statistics.read_story_count)),
$.make('dt', 'Training:'),
$.make('dd', { className: 'NB-admin-training-counts' }, [
$.make('span', { className: data.statistics.training.title ? '' : 'NB-grey' }, 'Title: ' + data.statistics.training.title),
$.make('span', { className: data.statistics.training.author ? '' : 'NB-grey' }, 'Author: ' + data.statistics.training.author),
$.make('span', { className: data.statistics.training.tag ? '' : 'NB-grey' }, 'Tag: ' + data.statistics.training.tag),
$.make('span', { className: data.statistics.training.feed ? '' : 'NB-grey' }, 'Feed: ' + data.statistics.training.feed)
])
]));
$(window).resize();
}, this));
@ -135,6 +147,17 @@ _.extend(NEWSBLUR.ReaderUserAdmin.prototype, {
$(".NB-admin-action-refund").replaceWith($.make('div', 'Error: ' + JSON.stringify(data)));
});
});
$.targetIs(e, { tagSelector: '.NB-admin-action-never-expire' }, function($t, $p) {
e.preventDefault();
NEWSBLUR.assets.never_expire_premium({
'user_id': self.user.get('user_id')
}, function(data) {
self.fetch_payment_history();
}, function(data) {
$(".NB-admin-action-never-expire").replaceWith($.make('div', 'Error: ' + JSON.stringify(data)));
});
});
$.targetIs(e, { tagSelector: '.NB-admin-action-upgrade' }, function($t, $p) {
e.preventDefault();

View file

@ -27,7 +27,7 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
var has_likes = _.any(this.model.get('liking_users'));
var liked = _.contains(this.model.get('liking_users'), NEWSBLUR.Globals.user_id);
var $comment = $.make('div', [
var $comment = $.make('div', { className: (this.options.friend_share ? "NB-story-comment-friend-share" : "") }, [
$.make('div', { className: 'NB-story-comment-author-avatar NB-user-avatar ' + reshare_class }, [
$.make('img', { src: this.user.get('photo_url') })
]),

View file

@ -17,8 +17,10 @@ NEWSBLUR.Views.StoryCommentsView = Backbone.View.extend({
}));
this.render_teaser();
this.render_comments_friends();
this.render_shares_friends();
this.render_comments_public();
this.$el.toggleClass('NB-hidden', !this.model.get('comment_count'));
this.$el.toggleClass('NB-hidden', (!this.model.get('comment_count') &&
!this.model.get('share_count_friends')));
}
return this;
@ -41,7 +43,7 @@ NEWSBLUR.Views.StoryCommentsView = Backbone.View.extend({
var $thumb = NEWSBLUR.Views.ProfileThumb.create(user_id).render().el;
$comments_public.append($thumb);
});
if (!this.model.friend_comments.length && !this.model.public_comments.length) {
if (!this.model.friend_comments.length && !this.model.public_comments.length && !this.model.friend_shares.length) {
this.$el.hide();
}
@ -86,6 +88,30 @@ NEWSBLUR.Views.StoryCommentsView = Backbone.View.extend({
this.$el.append($comment);
}, this));
},
render_shares_friends: function() {
var shares_without_comments = this.model.get('shared_by_friends');
if (shares_without_comments.length <= 0) return;
var $header = $.make('div', {
className: 'NB-story-comments-public-header-wrapper'
}, $.make('div', {
className: 'NB-story-comments-public-header NB-module-header'
}, [
Inflector.pluralize(' share', shares_without_comments.length, true)
]));
this.$el.append($header);
this.model.friend_shares.each(_.bind(function(comment) {
var $comment = new NEWSBLUR.Views.StoryComment({
model: comment,
story: this.model,
friend_share: true
}).render().el;
this.$el.append($comment);
}, this));
},
render_comments_public: function() {
if (!this.model.get('comment_count_public') || !this.model.get('comment_count')) return;

View file

@ -280,7 +280,8 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
var $original_comments = this.$('.NB-feed-story-comments-container,.NB-feed-story-comments');
var $original_shares = this.$('.NB-feed-story-shares-container,.NB-feed-story-shares');
if (this.model.get("comment_count") || this.model.get("share_count")) {
this.comments_view = new NEWSBLUR.Views.StoryCommentsView({model: this.model}).render();
var comments_view = new NEWSBLUR.Views.StoryCommentsView({model: this.model});
this.comments_view = comments_view.render();
var $comments = this.comments_view.el;
$original_comments.replaceWith($comments);
var $shares = $('.NB-story-comments-shares-teaser-wrapper', $comments);

View file

@ -28,7 +28,7 @@ app.get /^\/rss_feeds\/icon\/(\d+)\/?/, (req, res) =>
feed_id = parseInt(req.params, 10)
etag = req.header('If-None-Match')
@collection.findOne _id: feed_id, (err, docs) ->
console.log "Req: #{feed_id}, etag: #{etag}/#{docs?.color}"
console.log "Req: #{feed_id}, etag: #{etag}/#{docs?.color} (err: #{err}, docs? #{!!(docs and docs.data)})"
if not err and etag and docs and docs?.color == etag
res.send 304
else if not err and docs and docs.data

View file

@ -50,7 +50,7 @@
return _this.collection.findOne({
_id: feed_id
}, function(err, docs) {
console.log("Req: " + feed_id + ", etag: " + etag + "/" + (docs != null ? docs.color : void 0));
console.log("Req: " + feed_id + ", etag: " + etag + "/" + (docs != null ? docs.color : void 0) + " (err: " + err + ", docs? " + (!!(docs && docs.data)) + ")");
if (!err && etag && docs && (docs != null ? docs.color : void 0) === etag) {
return res.send(304);
} else if (!err && docs && docs.data) {

View file

@ -15,11 +15,11 @@ SECURE = !!process.env.NODE_SSL
if SECURE
privateKey = fs.readFileSync('./config/certificates/newsblur.com.key').toString()
certificate = fs.readFileSync('./config/certificates/newsblur.com.crt').toString()
ca = fs.readFileSync('./config/certificates/intermediate.crt').toString()
# ca = fs.readFileSync('./config/certificates/intermediate.crt').toString()
io = require('socket.io').listen 8889
key: privateKey
cert: certificate
ca: ca
# ca: ca
else
io = require('socket.io').listen 8888

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.4.0
(function() {
var REDIS_SERVER, SECURE, ca, certificate, fs, io, log, privateKey, redis;
var REDIS_SERVER, SECURE, certificate, fs, io, log, privateKey, redis;
fs = require('fs');
@ -15,11 +15,9 @@
if (SECURE) {
privateKey = fs.readFileSync('./config/certificates/newsblur.com.key').toString();
certificate = fs.readFileSync('./config/certificates/newsblur.com.crt').toString();
ca = fs.readFileSync('./config/certificates/intermediate.crt').toString();
io = require('socket.io').listen(8889, {
key: privateKey,
cert: certificate,
ca: ca
cert: certificate
});
} else {
io = require('socket.io').listen(8888);

View file

@ -35,7 +35,7 @@
{{ username }} replied to <span class="NB-interaction-username NB-splash-link" data-user-id="{{ activity.with_user_id }}">{{ activity.with_user.username }}</span>:
</div>
<div class="NB-interaction-content">
"<span class="NB-interaction-reply-content" data-story-id="{{ activity.content_id }}">{{ activity.content|safe|truncatewords:16 }}</span>"
"<span class="NB-interaction-reply-content" data-story-id="{{ activity.content_id }}">{{ activity.content|safe|truncatewords:128 }}</span>"
</div>
<div class="NB-interaction-date">
{{ activity.time_since }} ago
@ -48,7 +48,7 @@
{{ username }} favorited <span class="NB-interaction-username NB-splash-link" data-user-id="{{ activity.with_user_id }}">{{ activity.with_user.username }}</span>'s comments on <span class="NB-interaction-sharedstory-title NB-splash-link">{{ activity.title|safe|truncatewords:6 }}</span>:
</div>
<div class="NB-interaction-content">
"<span class="NB-interaction-sharedstory-content">{{ activity.content|truncatewords:16 }}</span>"
"<span class="NB-interaction-sharedstory-content">{{ activity.content|truncatewords:8 }}</span>"
</div>
<div class="NB-interaction-date">
{{ activity.time_since }} ago
@ -62,7 +62,7 @@
</div>
{% if activity.content %}
<div class="NB-interaction-content">
"<span class="NB-interaction-sharedstory-content">{{ activity.content|safe|truncatewords:16 }}</span>"
"<span class="NB-interaction-sharedstory-content">{{ activity.content|safe|truncatewords:36 }}</span>"
</div>
{% endif %}
<div class="NB-interaction-date">
@ -73,7 +73,7 @@
{% if activity.category == 'star' %}
<img class="NB-interaction-photo" src="{{ MEDIA_URL }}img/icons/circular/clock.png">
<div class="NB-interaction-title">
You saved "<span class="NB-interaction-starred-story-title NB-splash-link">{{ activity.content|safe|truncatewords:8 }}</span>".
You saved "<span class="NB-interaction-starred-story-title NB-splash-link">{{ activity.content|safe|truncatewords:6 }}</span>".
</div>
<div class="NB-interaction-date">
{{ activity.time_since }} ago

View file

@ -29,7 +29,7 @@
</span> replied to your {% if interaction.category == 'comment_reply' %}comment{% else %}reply{% endif %}:
</div>
<div class="NB-interaction-content">
<span class="NB-interaction-reply-content" data-social-user-id="{{ interaction.feed_id }}">{{ interaction.content|safe|truncatewords:16 }}</span>
<span class="NB-interaction-reply-content" data-social-user-id="{{ interaction.feed_id }}">{{ interaction.content|safe|truncatewords:128 }}</span>
</div>
<div class="NB-interaction-date">
{{ interaction.time_since }} ago

View file

@ -71,7 +71,7 @@
</div>
<div class="NB-story-comments-container">
{% if story.comment_count or not story.shared_by_user %}
{% if story.comment_count or not story.shared_by_user or story.friend_share_count %}
{% render_story_comments story %}
{% endif %}
</div>

View file

@ -7,6 +7,14 @@
{% render_story_comment story comment %}
{% endfor %}
{% if story.share_count_friends %}
<div class="NB-story-comments-shares">
{% for comment in story.friend_shares %}
{% render_story_comment story comment %}
{% endfor %}
</div>
{% endif %}
<div class="NB-story-comment-edit NB-story-comment {% if story.shared_by_user %}NB-hidden{% endif %}" data-user-id="{{ user_social_profile.user_id }}">
<a href="{{ user_social_profile.blurblog_url }}" class="NB-user-avatar">
<img src="{% if user_social_profile.photo_url %}{{ user_social_profile.photo_url }}{% else %}{{ MEDIA_URL }}/img/circular/circular_avatar.png{% endif %}">
@ -27,7 +35,7 @@
<div class="NB-story-comments-public">
{% if story.comment_count_public %}
<div class="NB-story-comments-public-header-wrapper">
<div class="NB-story-comments-public-header-wrapper {% if story.shared_by_user and not story.friend_comments %}NB-public-top{% endif %}">
<div class="NB-story-comments-public-header">
{{ story.comment_count_public }} public comment{{ story.comment_count_public|pluralize }}
</div>

File diff suppressed because it is too large Load diff

View file

@ -287,9 +287,13 @@ class ProcessFeed:
return FEED_SAME, ret_values
# 302: Temporary redirect: ignore
# 301: Permanent redirect: save it
# 301: Permanent redirect: save it (after 20 tries)
if self.fpf.status == 301:
if not self.fpf.href.endswith('feedburner.com/atom.xml'):
if self.fpf.href.endswith('feedburner.com/atom.xml'):
return FEED_ERRHTTP, ret_values
redirects, non_redirects = self.feed.count_redirects_in_history('feed')
self.feed.save_feed_history(self.fpf.status, "HTTP Redirect (%d to go)" % (20-len(redirects)))
if len(redirects) >= 20 or len(non_redirects) == 0:
self.feed.feed_address = self.fpf.href
if not self.feed.known_good:
self.feed.fetched_once = True
@ -359,8 +363,14 @@ class ProcessFeed:
self.feed.data.feed_tagline = utf8encode(tagline)
self.feed.data.save()
if not self.feed.feed_link_locked:
self.feed.feed_link = self.fpf.feed.get('link') or self.fpf.feed.get('id') or self.feed.feed_link
new_feed_link = self.fpf.feed.get('link') or self.fpf.feed.get('id') or self.feed.feed_link
if new_feed_link != self.feed.feed_link:
logging.debug(" ---> [%-30s] ~SB~FRFeed's page is different: %s to %s" % (self.feed.title[:30], self.feed.feed_link, new_feed_link))
redirects, non_redirects = self.feed.count_redirects_in_history('page')
self.feed.save_page_history(301, "HTTP Redirect (%s to go)" % (20-len(redirects)))
if len(redirects) >= 20 or len(non_redirects) == 0:
self.feed.feed_link = new_feed_link
self.feed = self.feed.save()
# Determine if stories aren't valid and replace broken guids

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python
import os
import re
import time
import select
import subprocess
@ -46,34 +47,44 @@ def create_streams_for_roles(role, role2, command=None, path=None):
path = "/srv/newsblur/logs/newsblur.log"
if not command:
command = "tail -f"
for hostname in (hosts[role] + hosts[role2]):
if isinstance(hostname, dict):
address = hostname['address']
hostname = hostname['name']
elif ':' in hostname:
hostname, address = hostname.split(':', 1)
elif isinstance(hostname, tuple):
hostname, address = hostname[0], hostname[1]
else:
address = hostname
if any(h in hostname for h in IGNORE_HOSTS): continue
if hostname in found: continue
if 'ec2' in hostname:
s = subprocess.Popen(["ssh",
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
"keys/ec2.pem")),
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
else:
s = subprocess.Popen(["ssh", "-l", NEWSBLUR_USERNAME,
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
"keys/newsblur.key")),
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
s.name = hostname
streams.append(s)
found.add(hostname)
if role in hosts:
for hostname in (hosts[role] + hosts[role2]):
if any(h in hostname for h in IGNORE_HOSTS) and role != 'push': continue
follow_host(streams, found, hostname, command, path)
else:
host = role
role = re.search(r'([^0-9]+)', host).group()
for hostname in hosts[role]:
if hostname['name'] == host:
follow_host(streams, found, hostname, command, path)
return streams
def follow_host(streams, found, hostname, command=None, path=None):
if isinstance(hostname, dict):
address = hostname['address']
hostname = hostname['name']
elif ':' in hostname:
hostname, address = hostname.split(':', 1)
elif isinstance(hostname, tuple):
hostname, address = hostname[0], hostname[1]
else:
address = hostname
if hostname in found: return
if 'ec2' in hostname:
s = subprocess.Popen(["ssh",
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
"keys/ec2.pem")),
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
else:
s = subprocess.Popen(["ssh", "-l", NEWSBLUR_USERNAME,
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
"keys/newsblur.key")),
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
s.name = hostname
streams.append(s)
found.add(hostname)
def read_streams(streams):
while True:
r, _, _ = select.select(

View file

@ -1,6 +1,11 @@
#!/usr/bin/env python
import tlnb
import sys
if __name__ == "__main__":
tlnb.main(role="task", role2="ec2task")
role = "task"
if len(sys.argv) > 1:
role = sys.argv[1]
tlnb.main(role=role, role2="ec2task")

View file

@ -116,7 +116,7 @@ class PayPalConfig(object):
# Set the CA_CERTS location. This can either be a None, a bool, or a
# string path.
if kwargs.get('API_CA_CERTS'):
if 'API_CA_CERTS' in kwargs:
self.API_CA_CERTS = kwargs['API_CA_CERTS']
if isinstance(self.API_CA_CERTS, basestring) and not os.path.exists(self.API_CA_CERTS):