package com.buddycloud.fragments; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONObject; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Rect; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.buddycloud.ChannelDetailActivity; import com.buddycloud.GenericChannelActivity; import com.buddycloud.MainActivity; import com.buddycloud.PostNewTopicActivity; import com.buddycloud.R; import com.buddycloud.card.Card; import com.buddycloud.card.CardListAdapter; import com.buddycloud.card.PostCard; import com.buddycloud.fragments.adapter.FollowersAdapter; import com.buddycloud.fragments.adapter.PendingSubscriptionsAdapter; import com.buddycloud.fragments.adapter.SimilarChannelsAdapter; import com.buddycloud.model.ChannelMetadataModel; import com.buddycloud.model.ModelCallback; import com.buddycloud.model.ModelListener; import com.buddycloud.model.PostsModel; import com.buddycloud.model.SubscribedChannelsModel; import com.buddycloud.model.TopicChannelModel; import com.buddycloud.preferences.Preferences; import com.buddycloud.utils.InputUtils; import com.nostra13.universalimageloader.core.ImageLoader; import com.software.shell.fab.FloatingActionButton; public class ChannelStreamFragment extends ContentFragment implements ModelListener { private FloatingActionButton addPostTopicBtn; private SwipeRefreshLayout postStreamRefreshLayout; private ListView postStream; private CardListAdapter cardAdapter; private EndlessScrollListener scrollListener; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View activityRootView = inflater.inflate(R.layout.fragment_channel_stream, container, false); showProgress(activityRootView); postStream = (ListView) activityRootView.findViewById(R.id.postsStream); postStream.setEmptyView(getEmptyListView(activityRootView)); postStreamRefreshLayout = (SwipeRefreshLayout) activityRootView.findViewById(R.id.refreshLayout); postStreamRefreshLayout.setColorSchemeResources(R.color.bc_green_blue_color); postStreamRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { postStreamRefreshLayout.setRefreshing(true); scrollListener.setRefreshing(true); // refresh the post stream synchPostStream(); } }); addPostTopicBtn = (FloatingActionButton) activityRootView.findViewById(R.id.addNewTopicBtn); addPostTopicBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(getActivity(), PostNewTopicActivity.class); intent.putExtra(GenericChannelsFragment.CHANNEL, getChannelJid()); getActivity().startActivityForResult(intent, PostNewTopicActivity.REQUEST_CODE); } }); if (!SubscribedChannelsModel.canPost(getRole())) { addPostTopicBtn.setVisibility(View.GONE); } else { addPostTopicBtn.setVisibility(View.VISIBLE); } // hide add '+' post topic button, when keyboard visible activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Rect r = new Rect(); //r will be populated with the coordinates of your view that area still visible. activityRootView.getWindowVisibleDisplayFrame(r); int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top); if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard... hideAddPostTopicBtn(); // hide add '+' post topic button } } }); PostsModel.getInstance().setListener(this); synchChannelMetadata(); synchPostStream(); return activityRootView; } @Override public void onResume() { super.onResume(); ImageLoader.getInstance().resume(); } @Override public void onStop() { super.onStop(); ImageLoader.getInstance().stop(); } public void showAddPostTopicBtn() { boolean canPost = SubscribedChannelsModel.canPost(getRole()); if (addPostTopicBtn != null && canPost) { addPostTopicBtn.show(); } } public void hideAddPostTopicBtn() { boolean canPost = SubscribedChannelsModel.canPost(getRole()); if (addPostTopicBtn != null && canPost) { addPostTopicBtn.hide(); } } public ListView getPostStreamView() { return postStream; } public SwipeRefreshLayout getSwipeRefreshLayout() { return postStreamRefreshLayout; } public CardListAdapter getPostStreamAdapter() { return cardAdapter; } private String getChannelJid() { return getArguments().getString(GenericChannelsFragment.CHANNEL); } private View getEmptyListView(View view) { View emptyView = null; if (view != null) { emptyView = view.findViewById(android.R.id.empty); TextView tv = (TextView)emptyView.findViewById(R.id.results_not_found); if (tv != null) { tv.setText(getString(R.string.message_no_topics_found)); tv.setTextColor(getActivity().getResources().getColor(R.color.bc_dark_grey_color)); } } return emptyView; } /** * Synchronize the channel metadata * */ private void synchChannelMetadata() { ChannelMetadataModel.getInstance().fill(getActivity(), new ModelCallback<Void>() { @Override public void success(Void response) { getSherlockActivity().supportInvalidateOptionsMenu(); } @Override public void error(Throwable throwable) {} }, getChannelJid()); } /** * Synchronize the post stream with latest posts * from remote */ public void synchPostStream() { if (cardAdapter == null) { this.cardAdapter = new CardListAdapter(); cardAdapter.setFragment(this); postStream.setAdapter(cardAdapter); this.scrollListener = new EndlessScrollListener(this); postStream.setOnScrollListener(scrollListener); } // get the latest post from remote fillRemotely(null, null); } protected void fillMore() { scrollListener.setLoading(true); String lastUpdated = null; String lastId = null; JSONObject lastPost = getLastPost(); if (lastPost != null) { lastUpdated = lastPost.optString("updated"); lastId = lastPost.optString("id"); } fillAdapter(getActivity(), lastUpdated, fillAdapterCallback(lastUpdated, lastId)); } private JSONObject getLastPost() { JSONObject post = null; for (int i = cardAdapter.getCount() - 1; i >= 0; i--) { Card lastCard = (Card) cardAdapter.getItem(i); if (!PostsModel.isPending(lastCard.getPost())) { post = lastCard.getPost(); break; } } return post; } public void scrollUp() { final ListView postsStream = (ListView) getView().findViewById(R.id.postsStream); postsStream.post(new Runnable() { @Override public void run() { postsStream.setSelection(0); postsStream.smoothScrollToPosition(0); } }); } /** * A post card adapter callback * * @param lastUpdated * @param lastId * @return */ private ModelCallback<Boolean> fillAdapterCallback(final String lastUpdated, final String lastId) { return smartify(new ModelCallback<Boolean>() { @Override public void success(Boolean response) { fillRemotely(lastId, lastUpdated); } @Override public void error(Throwable throwable) {} }); } /** * Get the posts from the local cache w.r.t last updated * timestamp. * * @param context * @param lastUpdated * @param callback */ private void fillAdapter(final Context context, final String lastUpdated, final ModelCallback<Boolean> callback) { if (!isAttachedToActivity()) { return; } new AsyncTask<Void, Void, List<PostCard>>() { @Override protected List<PostCard> doInBackground(Void... params) { JSONArray cachedPosts = PostsModel.getInstance().getFromCache(context, getChannelJid(), lastUpdated); List<PostCard> posts = new LinkedList<PostCard>(); for (int i = 0; i < cachedPosts.length(); i++) { JSONObject post = cachedPosts.optJSONObject(i); PostCard card = toCard(post, getChannelJid()); posts.add(card); } return posts; } protected void onPostExecute(List<PostCard> cachedPosts) { for (PostCard card : cachedPosts) { cardAdapter.addCard(card); } cardAdapter.sort(); if (callback != null) { callback.success(cachedPosts.size() > 0); } }; }.execute(); } /** * Get the latest posts from the remote w.r.t last * post id and last updated timestamp. * * @param lastId * @param lastUpdated */ public void fillRemotely(final String lastId, final String lastUpdated) { PostsModel.getInstance().fillMore(getActivity(), smartify(new ModelCallback<Void>() { @Override public void success(Void response) { fillAdapter(getActivity(), lastUpdated, smartify(new ModelCallback<Boolean>() { @Override public void success(Boolean response) { if (response) { postStreamRefreshLayout.setRefreshing(false); scrollListener.setRefreshing(false); scrollListener.setLoading(false); } hideProgress(getView()); } @Override public void error(Throwable throwable) { Toast.makeText(getActivity(), getString(R.string.message_post_fetch_failed), Toast.LENGTH_LONG).show(); postStreamRefreshLayout.setRefreshing(false); scrollListener.setRefreshing(false); scrollListener.setLoading(false); hideProgress(getView()); } })); } @Override public void error(Throwable throwable) { Toast.makeText(getActivity(), getString(R.string.message_post_fetch_failed), Toast.LENGTH_LONG).show(); postStreamRefreshLayout.setRefreshing(false); scrollListener.setRefreshing(false); scrollListener.setLoading(false); hideProgress(getView()); } }), getChannelJid(), lastId); } private PostCard toCard(JSONObject post, final String channelJid) { return new PostCard(channelJid, post, this, getRole()); } private void loadTitle(Activity activity) { SlidingFragmentActivity sherlockActivity = (SlidingFragmentActivity) activity; sherlockActivity.getSupportActionBar().setTitle(getChannelJid()); } public static void configurePostSection(EditText postContent, final ImageView postButton) { postContent.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable arg0) { } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { boolean enabled = arg0 != null && arg0.length() > 0; postButton.setEnabled(enabled); } }); } @Override public void attached(Activity activity) { loadTitle(activity); } @Override public void createOptions(Menu menu) { if (!isAttachedToActivity()) { return; } getSherlockActivity().getSupportMenuInflater().inflate( R.menu.channel_fragment_options, menu); MenuItem followItem = menu.findItem(R.id.menu_follow); if (SubscribedChannelsModel.isFollowing(getRole())) { followItem.setTitle(R.string.menu_unfollow); } else { followItem.setTitle(R.string.menu_follow); } MenuItem pendingSubscriptionsItem = menu.findItem(R.id.menu_pending_subscriptions); boolean canApprove = SubscribedChannelsModel.canChangeAffiliation(getRole()); pendingSubscriptionsItem.setVisible(canApprove); MenuItem deleteChannelItem = menu.findItem(R.id.menu_delete_channel); JSONObject metadata = ChannelMetadataModel.getInstance().getFromCache(getActivity(), getChannelJid()); boolean isPersonal = metadata == null || metadata.optString("channelType").equals("personal"); boolean canDelete = SubscribedChannelsModel.canDeleteChannel(getRole()); deleteChannelItem.setVisible(canDelete && !isPersonal); } private String getRole() { JSONObject subscribed = SubscribedChannelsModel.getInstance().getFromCache(getActivity()); if (!subscribed.has(getChannelJid())) { return null; } return subscribed.optString(getChannelJid()); } @Override public boolean menuItemSelected(int featureId, MenuItem item) { switch (item.getItemId()) { case R.id.menu_similar_channels: showSimilarChannels(); return true; case R.id.menu_follow: toogleRole(); return true; case R.id.menu_channel_followers: showFollowers(); return true; case R.id.menu_channel_details: showDetails(); return true; case R.id.menu_pending_subscriptions: showPendingSubscriptions(); return true; case R.id.menu_delete_channel: confirmDeleteChannel(); return true; default: return false; } } private void confirmDeleteChannel() { Context context = getActivity(); new AlertDialog.Builder(context) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(context.getString(R.string.title_confirm_delete_channel)) .setMessage(context.getString(R.string.message_confirm_delete_channel)) .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { deleteChannel(); } }).setNegativeButton(R.string.no, null).show(); } private void deleteChannel() { TopicChannelModel.getInstance().delete(getActivity(), new ModelCallback<Void>() { @Override public void success(Void response) { Toast.makeText(getActivity(), getString(R.string.message_channel_deleted), Toast.LENGTH_LONG).show(); MainActivity mainActivity = (MainActivity) getSherlockActivity(); String myJid = (String) Preferences.getPreference(getActivity(), Preferences.MY_CHANNEL_JID); mainActivity.showChannelFragment(myJid); mainActivity.showMenu(); } @Override public void error(Throwable throwable) { Toast.makeText(getActivity(), getString(R.string.message_channel_deletion_failed), Toast.LENGTH_LONG).show(); } }, getChannelJid()); } private void showDetails() { Intent intent = new Intent(); intent.setClass(getActivity(), ChannelDetailActivity.class); intent.putExtra(GenericChannelsFragment.CHANNEL, getChannelJid()); intent.putExtra(SubscribedChannelsModel.ROLE, getRole()); getActivity().startActivity(intent); } private void toogleRole() { final boolean isFollowing = SubscribedChannelsModel.isFollowing(getRole()); Map<String, String> subscription = new HashMap<String, String>(); String newRole = isFollowing ? SubscribedChannelsModel.SUBSCRIPTION_NONE : SubscribedChannelsModel.ROLE_PUBLISHER; final String channelJid = getChannelJid(); subscription.put(channelJid + SubscribedChannelsModel.POST_NODE_SUFIX, newRole); SubscribedChannelsModel.getInstance().save(getActivity(), new JSONObject(subscription), smartify(new ModelCallback<JSONObject>() { @Override public void success(JSONObject response) { Toast.makeText(getActivity(), getString(isFollowing ? R.string.action_unfollowed : R.string.action_followed, channelJid), Toast.LENGTH_LONG).show(); ((MainActivity) getActivity()).showChannelFragment(channelJid, true, true); } @Override public void error(Throwable throwable) { Toast.makeText(getActivity(), getString(R.string.action_follow_failed, channelJid), Toast.LENGTH_LONG).show(); } })); } private void showFollowers() { showGenericChannelActivity(FollowersAdapter.ADAPTER_NAME); } private void showPendingSubscriptions() { showGenericChannelActivity(PendingSubscriptionsAdapter.ADAPTER_NAME); } private void showSimilarChannels() { showGenericChannelActivity(SimilarChannelsAdapter.ADAPTER_NAME); } private void showProgress(View container) { SherlockFragmentActivity activity = (SherlockFragmentActivity) getActivity(); activity.setProgressBarIndeterminateVisibility(true); } private void hideProgress(View container) { SherlockFragmentActivity activity = (SherlockFragmentActivity) getActivity(); activity.setProgressBarIndeterminateVisibility(false); } private void showGenericChannelActivity(String adapterName) { Intent intent = new Intent(); intent.setClass(getActivity(), GenericChannelActivity.class); intent.putExtra(GenericChannelActivity.ADAPTER_NAME, adapterName); intent.putExtra(GenericChannelsFragment.CHANNEL, getChannelJid()); intent.putExtra(SubscribedChannelsModel.ROLE, getRole()); getActivity().startActivityForResult( intent, GenericChannelActivity.REQUEST_CODE); } @Override public void dataChanged() { if (!isAttachedToActivity()) { return; } showProgress(getView()); fillRemotely(null, null); } @Override public void itemRemoved(String channelJid, String itemId, String parentId) { if (!channelJid.equals(getChannelJid()) || !isAttachedToActivity()) { return; } CardListAdapter adapter = getAdapter(itemId, parentId); if (adapter != null) { adapter.remove(itemId); adapter.notifyDataSetChanged(); } } private CardListAdapter getAdapter(String itemId, String parentId) { Card card = cardAdapter.getCard(itemId); if (card != null) { return cardAdapter; } else if (parentId != null) { PostCard parentCard = (PostCard) cardAdapter.getCard(parentId); if (parentCard == null) { return null; } return parentCard.getRepliesAdapter(); } return null; } @Override public void pendingItemAdded(String channelJid, JSONObject pendingItem) { if (!channelJid.equals(getChannelJid()) || !isAttachedToActivity()) { return; } String replyTo = pendingItem.optString("replyTo", null); if (replyTo == null) { PostCard card = toCard(pendingItem, getChannelJid()); cardAdapter.addCard(card); cardAdapter.sort(); } else { PostCard parentCard = (PostCard) cardAdapter.getCard(replyTo); if (parentCard != null) { parentCard.addPendingCard(pendingItem); } } InputUtils.hideKeyboard(getActivity()); scrollUp(); } }