Java tutorial
/* * Copyright (c) 2015 PocketHub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.pockethub.android.ui.gist; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GistFile; import com.meisolsson.githubsdk.model.GitHubComment; import com.meisolsson.githubsdk.model.User; import com.github.pockethub.android.R; import com.github.pockethub.android.accounts.AccountUtils; import com.github.pockethub.android.core.OnLoadListener; import com.github.pockethub.android.core.gist.FullGist; import com.github.pockethub.android.core.gist.GistStore; import com.github.pockethub.android.core.gist.RefreshGistTask; import com.github.pockethub.android.rx.ProgressObserverAdapter; import com.github.pockethub.android.ui.ConfirmDialogFragment; import com.github.pockethub.android.ui.DialogFragment; import com.github.pockethub.android.ui.HeaderFooterListAdapter; import com.github.pockethub.android.ui.StyledText; import com.github.pockethub.android.ui.comment.CommentListAdapter; import com.github.pockethub.android.ui.comment.DeleteCommentListener; import com.github.pockethub.android.ui.comment.EditCommentListener; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.HttpImageGetter; import com.github.pockethub.android.util.ShareUtils; import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.service.gists.GistCommentService; import com.meisolsson.githubsdk.service.gists.GistService; import com.google.inject.Inject; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import retrofit2.Response; import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.github.pockethub.android.Intents.EXTRA_COMMENT; import static com.github.pockethub.android.Intents.EXTRA_GIST_ID; import static com.github.pockethub.android.RequestCodes.COMMENT_CREATE; import static com.github.pockethub.android.RequestCodes.COMMENT_DELETE; import static com.github.pockethub.android.RequestCodes.COMMENT_EDIT; /** * Activity to display an existing Gist */ public class GistFragment extends DialogFragment implements OnItemClickListener { private static final String TAG = "GistFragment"; private String gistId; private List<GitHubComment> comments; private Gist gist; private ListView list; private ProgressBar progress; @Inject private GistStore store; @Inject private HttpImageGetter imageGetter; private View headerView; private View footerView; private TextView created; private TextView updated; private TextView description; private View loadingView; private HeaderFooterListAdapter<CommentListAdapter> adapter; private boolean starred; private boolean loadFinished; @Inject private AvatarLoader avatars; private List<View> fileHeaders = new ArrayList<>(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); gistId = getArguments().getString(EXTRA_GIST_ID); gist = store.getGist(gistId); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_comment_list, container, false); headerView = inflater.inflate(R.layout.gist_header, null); created = (TextView) headerView.findViewById(R.id.tv_gist_creation); updated = (TextView) headerView.findViewById(R.id.tv_gist_updated); description = (TextView) headerView.findViewById(R.id.tv_gist_description); loadingView = inflater.inflate(R.layout.loading_item, null); ((TextView) loadingView.findViewById(R.id.tv_loading)).setText(R.string.loading_comments); footerView = inflater.inflate(R.layout.footer_separator, null); return root; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); list = (ListView) view.findViewById(android.R.id.list); progress = (ProgressBar) view.findViewById(R.id.pb_loading); Activity activity = getActivity(); User user = gist.owner(); String userName = null; if (user != null) { userName = user.login(); } adapter = new HeaderFooterListAdapter<>(list, new CommentListAdapter(activity.getLayoutInflater(), null, avatars, imageGetter, editCommentListener, deleteCommentListener, userName, isOwner(), null)); list.setAdapter(adapter); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); list.setOnItemClickListener(this); adapter.addHeader(headerView); adapter.addFooter(footerView); if (gist != null) { updateHeader(gist); updateFiles(gist); } if (gist == null || (gist.comments() > 0 && comments == null)) { adapter.addHeader(loadingView, null, false); } if (gist != null && comments != null) { updateList(gist, comments); } else { refreshGist(); } } private boolean isOwner() { if (gist == null) { return false; } User user = gist.owner(); if (user == null) { return false; } String login = AccountUtils.getLogin(getActivity()); return login != null && login.equals(user.login()); } private void updateHeader(Gist gist) { Date createdAt = gist.createdAt(); if (createdAt != null) { StyledText text = new StyledText(); text.append(getString(R.string.prefix_created)); text.append(createdAt); created.setText(text); created.setVisibility(VISIBLE); } else { created.setVisibility(GONE); } Date updatedAt = gist.updatedAt(); if (updatedAt != null && !updatedAt.equals(createdAt)) { StyledText text = new StyledText(); text.append(getString(R.string.prefix_updated)); text.append(updatedAt); updated.setText(text); updated.setVisibility(VISIBLE); } else { updated.setVisibility(GONE); } String desc = gist.description(); if (!TextUtils.isEmpty(desc)) { description.setText(desc); } else { description.setText(R.string.no_description_given); } progress.setVisibility(GONE); list.setVisibility(VISIBLE); } @Override public void onCreateOptionsMenu(Menu options, MenuInflater inflater) { inflater.inflate(R.menu.fragment_gist_view, options); } @Override public void onPrepareOptionsMenu(Menu menu) { boolean owner = isOwner(); if (!owner) { menu.removeItem(R.id.m_delete); MenuItem starItem = menu.findItem(R.id.m_star); starItem.setEnabled(loadFinished && !owner); if (starred) { starItem.setTitle(R.string.unstar); } else { starItem.setTitle(R.string.star); } } else { menu.removeItem(R.id.m_star); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (gist == null) { return super.onOptionsItemSelected(item); } switch (item.getItemId()) { case R.id.m_comment: startActivityForResult(CreateCommentActivity.createIntent(gist), COMMENT_CREATE); return true; case R.id.m_star: if (starred) { unstarGist(); } else { starGist(); } return true; case R.id.m_refresh: refreshGist(); return true; case R.id.m_share: shareGist(); return true; default: return super.onOptionsItemSelected(item); } } private void starGist() { ToastUtils.show(getActivity(), R.string.starring_gist); ServiceGenerator.createService(getActivity(), GistService.class).starGist(gistId) .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .compose(this.<Response<Boolean>>bindToLifecycle()) .subscribe(response -> starred = response.code() == 204, e -> ToastUtils.show((Activity) getContext(), e.getMessage())); } private void shareGist() { StringBuilder subject = new StringBuilder("Gist "); String id = gist.id(); subject.append(id); User user = gist.owner(); if (user != null && !TextUtils.isEmpty(user.login())) { subject.append(" by ").append(user.login()); } startActivity(ShareUtils.create(subject, "https://gist.github.com/" + id)); } private void unstarGist() { ToastUtils.show(getActivity(), R.string.unstarring_gist); ServiceGenerator.createService(getActivity(), GistService.class).unstarGist(gistId) .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .compose(this.<Response<Boolean>>bindToLifecycle()) .subscribe(response -> starred = !(response.code() == 204), e -> ToastUtils.show((Activity) getContext(), e.getMessage())); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (RESULT_OK != resultCode || data == null) { return; } switch (requestCode) { case COMMENT_CREATE: GitHubComment comment = data.getParcelableExtra(EXTRA_COMMENT); if (comments != null) { comments.add(comment); gist = gist.toBuilder().comments(gist.comments() + 1).build(); updateList(gist, comments); } else { refreshGist(); } return; case COMMENT_EDIT: comment = data.getParcelableExtra(EXTRA_COMMENT); if (comments != null && comment != null) { int position = Collections.binarySearch(comments, comment, (lhs, rhs) -> Integer.valueOf(lhs.id()).compareTo(rhs.id())); imageGetter.removeFromCache(comment.id()); comments.set(position, comment); updateList(gist, comments); } else { refreshGist(); } return; } super.onActivityResult(requestCode, resultCode, data); } private void updateFiles(Gist gist) { final Activity activity = getActivity(); if (activity == null) { return; } for (View header : fileHeaders) { adapter.removeHeader(header); } fileHeaders.clear(); Map<String, GistFile> files = gist.files(); if (files == null || files.isEmpty()) { return; } final LayoutInflater inflater = activity.getLayoutInflater(); for (GistFile file : files.values()) { View fileView = inflater.inflate(R.layout.gist_file_item, null); ((TextView) fileView.findViewById(R.id.tv_file)).setText(file.filename()); adapter.addHeader(fileView, file, true); fileHeaders.add(fileView); } } private void updateList(Gist gist, List<GitHubComment> comments) { adapter.getWrappedAdapter().setItems(comments.toArray(new GitHubComment[comments.size()])); adapter.removeHeader(loadingView); headerView.setVisibility(VISIBLE); updateHeader(gist); updateFiles(gist); } private void refreshGist() { Single.create(new RefreshGistTask(getActivity(), gistId, imageGetter)).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).filter(fullGist -> isUsable()) .compose(this.<FullGist>bindToLifecycle()).subscribe(fullGist -> { FragmentActivity activity = getActivity(); if (activity instanceof OnLoadListener) { ((OnLoadListener<Gist>) activity).loaded(fullGist.getGist()); } starred = fullGist.isStarred(); loadFinished = true; gist = fullGist.getGist(); comments = fullGist; updateList(fullGist.getGist(), fullGist); }, e -> ToastUtils.show(getActivity(), e, R.string.error_gist_load)); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Object item = parent.getItemAtPosition(position); if (item instanceof GistFile) { startActivity(GistFilesViewActivity.createIntent(gist, position - 1)); } } @Override public void onDialogResult(int requestCode, int resultCode, Bundle arguments) { if (RESULT_OK != resultCode) { return; } switch (requestCode) { case COMMENT_DELETE: final GitHubComment comment = arguments.getParcelable(EXTRA_COMMENT); ServiceGenerator.createService(getActivity(), GistCommentService.class) .deleteGistComment(gistId, comment.id()).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).compose(this.<Response<Boolean>>bindToLifecycle()) .subscribe(new ProgressObserverAdapter<Response<Boolean>>(getActivity(), R.string.deleting_comment) { @Override public void onSuccess(Response<Boolean> response) { super.onSuccess(response); // Update comment list if (comments != null) { int position = Collections.binarySearch(comments, comment, (lhs, rhs) -> Integer.valueOf(lhs.id()).compareTo(rhs.id())); comments.remove(position); updateList(gist, comments); } else { refreshGist(); } } @Override public void onError(Throwable e) { super.onError(e); Log.d(TAG, "Exception deleting comment on gist", e); ToastUtils.show((Activity) getContext(), e.getMessage()); } }.start()); break; } } /** * Edit existing comment */ final EditCommentListener editCommentListener = new EditCommentListener() { @Override public void onEditComment(GitHubComment comment) { startActivityForResult(EditCommentActivity.createIntent(gist, comment), COMMENT_EDIT); } }; /** * Delete existing comment */ final DeleteCommentListener deleteCommentListener = comment -> { Bundle args = new Bundle(); args.putParcelable(EXTRA_COMMENT, comment); ConfirmDialogFragment.show(getActivity(), COMMENT_DELETE, getActivity().getString(R.string.confirm_comment_delete_title), getActivity().getString(R.string.confirm_comment_delete_message), args); }; }