package com.gh4a.activities; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.content.Loader; import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.text.TextUtils; 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.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.gh4a.BackgroundTask; import com.gh4a.BasePagerActivity; import com.gh4a.Gh4Application; import com.gh4a.R; import com.gh4a.db.BookmarksProvider; import com.gh4a.fragment.CommitListFragment; import com.gh4a.fragment.ContentListContainerFragment; import com.gh4a.fragment.RepositoryEventListFragment; import com.gh4a.fragment.RepositoryFragment; import com.gh4a.loader.BaseLoader; import com.gh4a.loader.BranchListLoader; import com.gh4a.loader.IsStarringLoader; import com.gh4a.loader.IsWatchingLoader; import com.gh4a.loader.LoaderCallbacks; import com.gh4a.loader.LoaderResult; import com.gh4a.loader.ProgressDialogLoaderCallbacks; import com.gh4a.loader.RepositoryLoader; import com.gh4a.loader.TagListLoader; import com.gh4a.utils.IntentUtils; import com.gh4a.utils.UiUtils; import org.eclipse.egit.github.core.Repository; import org.eclipse.egit.github.core.RepositoryBranch; import org.eclipse.egit.github.core.RepositoryId; import org.eclipse.egit.github.core.RepositoryTag; import org.eclipse.egit.github.core.service.StarService; import org.eclipse.egit.github.core.service.WatcherService; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class RepositoryActivity extends BasePagerActivity { public static Intent makeIntent(Context context, Repository repo) { return makeIntent(context, repo, null); } public static Intent makeIntent(Context context, Repository repo, String ref) { return makeIntent(context, repo.getOwner().getLogin(), repo.getName(), ref); } public static Intent makeIntent(Context context, String repoOwner, String repoName) { return makeIntent(context, repoOwner, repoName, null); } public static Intent makeIntent(Context context, String repoOwner, String repoName, String ref) { return makeIntent(context, repoOwner, repoName, ref, null, PAGE_REPO_OVERVIEW); } public static Intent makeIntent(Context context, String repoOwner, String repoName, String ref, String initialPath, int initialPage) { if (TextUtils.isEmpty(ref)) { ref = null; } return new Intent(context, RepositoryActivity.class) .putExtra("owner", repoOwner) .putExtra("repo", repoName) .putExtra("ref", ref) .putExtra("initial_path", initialPath) .putExtra("initial_page", initialPage); } private static final int LOADER_REPO = 0; private static final int LOADER_BRANCHES_AND_TAGS = 1; private static final int LOADER_WATCHING = 2; private static final int LOADER_STARRING = 3; public static final int PAGE_REPO_OVERVIEW = 0; public static final int PAGE_FILES = 1; public static final int PAGE_COMMITS = 2; public static final int PAGE_ACTIVITY = 3; private static final int[] TITLES = new int[] { R.string.about, R.string.repo_files, R.string.commits, R.string.repo_activity }; private final LoaderCallbacks<Repository> mRepoCallback = new LoaderCallbacks<Repository>(this) { @Override protected Loader<LoaderResult<Repository>> onCreateLoader() { return new RepositoryLoader(RepositoryActivity.this, mRepoOwner, mRepoName); } @Override protected void onResultReady(Repository result) { mRepository = result; updateTitle(); invalidateTabs(); // Apply initial page selection first time the repo is loaded if (mInitialPage >= PAGE_REPO_OVERVIEW && mInitialPage <= PAGE_ACTIVITY) { getPager().setCurrentItem(mInitialPage); mInitialPage = -1; } setContentShown(true); supportInvalidateOptionsMenu(); } }; private final LoaderCallbacks<Pair<List<RepositoryBranch>, List<RepositoryTag>>> mBranchesAndTagsCallback = new ProgressDialogLoaderCallbacks<Pair<List<RepositoryBranch>, List<RepositoryTag>>>(this, this) { @Override protected Loader<LoaderResult<Pair<List<RepositoryBranch>, List<RepositoryTag>>>> onCreateLoader() { return new BaseLoader<Pair<List<RepositoryBranch>, List<RepositoryTag>>>(RepositoryActivity.this) { @Override protected Pair<List<RepositoryBranch>, List<RepositoryTag>> doLoadInBackground() throws Exception { return Pair.create(new BranchListLoader(getContext(), mRepoOwner, mRepoName).doLoadInBackground(), new TagListLoader(getContext(), mRepoOwner, mRepoName).doLoadInBackground()); } }; } @Override protected void onResultReady(Pair<List<RepositoryBranch>, List<RepositoryTag>> result) { mBranches = result.first; mTags = result.second; showRefSelectionDialog(); getSupportLoaderManager().destroyLoader(LOADER_BRANCHES_AND_TAGS); } }; private final LoaderCallbacks<Boolean> mWatchCallback = new LoaderCallbacks<Boolean>(this) { @Override protected Loader<LoaderResult<Boolean>> onCreateLoader() { return new IsWatchingLoader(RepositoryActivity.this, mRepoOwner, mRepoName); } @Override protected void onResultReady(Boolean result) { mIsWatching = result; supportInvalidateOptionsMenu(); } }; private final LoaderCallbacks<Boolean> mStarCallback = new LoaderCallbacks<Boolean>(this) { @Override protected Loader<LoaderResult<Boolean>> onCreateLoader() { return new IsStarringLoader(RepositoryActivity.this, mRepoOwner, mRepoName); } @Override protected void onResultReady(Boolean result) { mIsStarring = result; supportInvalidateOptionsMenu(); } }; private String mRepoOwner; private String mRepoName; private ActionBar mActionBar; private int mInitialPage; private String mInitialPath; private Repository mRepository; private List<RepositoryBranch> mBranches; private List<RepositoryTag> mTags; private String mSelectedRef; private Boolean mIsWatching; private Boolean mIsStarring; private RepositoryFragment mRepositoryFragment; private ContentListContainerFragment mContentListFragment; private CommitListFragment mCommitListFragment; private RepositoryEventListFragment mActivityFragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActionBar = getSupportActionBar(); mActionBar.setTitle(mRepoOwner + "/" + mRepoName); mActionBar.setDisplayHomeAsUpEnabled(true); setContentShown(false); getSupportLoaderManager().initLoader(LOADER_REPO, null, mRepoCallback); if (Gh4Application.get().isAuthorized()) { getSupportLoaderManager().initLoader(LOADER_WATCHING, null, mWatchCallback); getSupportLoaderManager().initLoader(LOADER_STARRING, null, mStarCallback); } } @Override protected void onInitExtras(Bundle extras) { super.onInitExtras(extras); mRepoOwner = extras.getString("owner"); mRepoName = extras.getString("repo"); mSelectedRef = extras.getString("ref"); mInitialPage = extras.getInt("initial_page", -1); mInitialPath = extras.getString("initial_path"); } private void updateTitle() { mActionBar.setSubtitle(getCurrentRef()); invalidateFragments(); } private String getCurrentRef() { if (!TextUtils.isEmpty(mSelectedRef)) { return mSelectedRef; } return mRepository.getDefaultBranch(); } private String getBookmarkUrl() { String url = "https://github.com/" + mRepoOwner + "/" + mRepoName; String ref = getCurrentRef(); return ref.equals(mRepository.getDefaultBranch()) ? url : url + "/tree/" + ref; } @Override protected int[] getTabTitleResIds() { return mRepository != null ? TITLES : null; } @Override protected Fragment makeFragment(int position) { switch (position) { case 0: return RepositoryFragment.newInstance(mRepository, mSelectedRef); case 1: Fragment f = ContentListContainerFragment.newInstance(mRepository, mSelectedRef, mInitialPath); mInitialPath = null; return f; case 2: return CommitListFragment.newInstance(mRepository, mSelectedRef); case 3: return RepositoryEventListFragment.newInstance(mRepository); } return null; } @Override protected void onFragmentInstantiated(Fragment f, int position) { switch (position) { case 0: mRepositoryFragment = (RepositoryFragment) f; break; case 1: mContentListFragment = (ContentListContainerFragment) f; break; case 2: mCommitListFragment = (CommitListFragment) f; break; case 3: mActivityFragment = (RepositoryEventListFragment) f; break; } } @Override protected void onFragmentDestroyed(Fragment f) { if (f == mRepositoryFragment) { mRepositoryFragment = null; } else if (f == mContentListFragment) { mContentListFragment = null; } else if (f == mCommitListFragment) { mCommitListFragment = null; } else if (f == mActivityFragment) { mActivityFragment = null; } } @Override protected boolean fragmentNeedsRefresh(Fragment fragment) { if (fragment instanceof CommitListFragment && mCommitListFragment == null) { return true; } else if (fragment instanceof ContentListContainerFragment && mContentListFragment == null) { return true; } else if (fragment instanceof RepositoryFragment && mRepositoryFragment == null) { return true; } else if (fragment instanceof RepositoryEventListFragment && mActivityFragment == null) { return true; } return false; } @Override public void onRefresh() { mRepositoryFragment = null; mContentListFragment = null; mActivityFragment = null; mRepository = null; mIsStarring = null; mIsWatching = null; mBranches = null; mTags = null; clearRefDependentFragments(); setContentShown(false); invalidateTabs(); forceLoaderReload(0, 1, 2, 3); super.onRefresh(); } @Override public void onBackPressed() { if (mContentListFragment != null) { if (getPager().getCurrentItem() == 1 && mContentListFragment.handleBackPress()) { return; } } super.onBackPressed(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.repo_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { boolean authorized = Gh4Application.get().isAuthorized(); MenuItem watchAction = menu.findItem(R.id.watch); watchAction.setVisible(authorized); if (authorized) { if (mIsWatching == null) { MenuItemCompat.setActionView(watchAction, R.layout.ab_loading); MenuItemCompat.expandActionView(watchAction); } else if (mIsWatching) { watchAction.setTitle(R.string.repo_unwatch_action); } else { watchAction.setTitle(R.string.repo_watch_action); } } MenuItem starAction = menu.findItem(R.id.star); starAction.setVisible(authorized); if (authorized) { if (mIsStarring == null) { MenuItemCompat.setActionView(starAction, R.layout.ab_loading); MenuItemCompat.expandActionView(starAction); } else if (mIsStarring) { starAction.setTitle(R.string.repo_unstar_action); starAction.setIcon(R.drawable.unstar); } else { starAction.setTitle(R.string.repo_star_action); starAction.setIcon(R.drawable.star); } } if (mRepository == null) { menu.removeItem(R.id.ref); menu.removeItem(R.id.bookmark); menu.removeItem(R.id.zip_download); } else { MenuItem bookmarkAction = menu.findItem(R.id.bookmark); if (bookmarkAction != null) { bookmarkAction.setTitle(BookmarksProvider.hasBookmarked(this, getBookmarkUrl()) ? R.string.remove_bookmark : R.string.bookmark_repo); } } return super.onPrepareOptionsMenu(menu); } @Override protected Intent navigateUp() { return UserActivity.makeIntent(this, mRepoOwner); } @Override public boolean onOptionsItemSelected(MenuItem item) { String url = "https://github.com/" + mRepoOwner + "/" + mRepoName; switch (item.getItemId()) { case R.id.watch: MenuItemCompat.setActionView(item, R.layout.ab_loading); MenuItemCompat.expandActionView(item); new UpdateWatchTask().schedule(); return true; case R.id.star: MenuItemCompat.setActionView(item, R.layout.ab_loading); MenuItemCompat.expandActionView(item); new UpdateStarTask().schedule(); return true; case R.id.ref: if (mBranches == null) { getSupportLoaderManager().initLoader(LOADER_BRANCHES_AND_TAGS, null, mBranchesAndTagsCallback); } else { showRefSelectionDialog(); } return true; case R.id.share: Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_SUBJECT, mRepoOwner + "/" + mRepoName); shareIntent.putExtra(Intent.EXTRA_TEXT, url); shareIntent = Intent.createChooser(shareIntent, getString(R.string.share_title)); startActivity(shareIntent); return true; case R.id.browser: IntentUtils.launchBrowser(this, Uri.parse(url)); return true; case R.id.search: String initialSearch = "repo:" + mRepoOwner + "/" + mRepoName + " "; startActivity(SearchActivity.makeIntent(this, initialSearch, SearchActivity.SEARCH_TYPE_CODE)); return true; case R.id.bookmark: String bookmarkUrl = getBookmarkUrl(); if (BookmarksProvider.hasBookmarked(this, bookmarkUrl)) { BookmarksProvider.removeBookmark(this, bookmarkUrl); } else { BookmarksProvider.saveBookmark(this, mActionBar.getTitle().toString(), BookmarksProvider.Columns.TYPE_REPO, bookmarkUrl, getCurrentRef()); } return true; case R.id.zip_download: String zipUrl = url + "/archive/" + getCurrentRef() + ".zip"; UiUtils.enqueueDownloadWithPermissionCheck(this, zipUrl, "application/zip", mRepoName + "-" + getCurrentRef() + ".zip", null, null); return true; } return super.onOptionsItemSelected(item); } private void showRefSelectionDialog() { final BranchAndTagAdapter adapter = new BranchAndTagAdapter(); int current = -1, master = -1, count = adapter.getCount(); for (int i = 0; i < count; i++) { String name = adapter.getName(i); if (name.equals(mSelectedRef) || adapter.getSha(i).equals(mSelectedRef)) { current = i; } if (name.equals(mRepository.getDefaultBranch())) { master = i; } } if (mSelectedRef == null && current == -1) { current = master; } new AlertDialog.Builder(this) .setCancelable(true) .setTitle(R.string.repo_select_ref_dialog_title) .setSingleChoiceItems(adapter, current, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setSelectedRef(adapter.getName(which)); dialog.dismiss(); } }) .setNegativeButton(R.string.cancel, null) .show(); } private void setSelectedRef(String selectedRef) { mSelectedRef = selectedRef; clearRefDependentFragments(); updateTitle(); } private void clearRefDependentFragments() { if (mRepositoryFragment != null) { mRepositoryFragment.setRef(mSelectedRef); } if (mContentListFragment != null) { mContentListFragment.setRef(mSelectedRef); } mCommitListFragment = null; } private class BranchAndTagAdapter extends BaseAdapter { private final ArrayList<Object> mItems; private final LayoutInflater mInflater; private final int mBranchDrawableResId; private final int mTagDrawableResId; public BranchAndTagAdapter() { mItems = new ArrayList<>(); mItems.addAll(mBranches); mItems.addAll(mTags); mInflater = LayoutInflater.from(RepositoryActivity.this); mBranchDrawableResId = UiUtils.resolveDrawable(RepositoryActivity.this, R.attr.branchIcon); mTagDrawableResId = UiUtils.resolveDrawable(RepositoryActivity.this, R.attr.tagIcon); } private String getName(int position) { Object item = mItems.get(position); if (item instanceof RepositoryBranch) { return ((RepositoryBranch) item).getName(); } else { return ((RepositoryTag) item).getName(); } } private String getSha(int position) { Object item = mItems.get(position); if (item instanceof RepositoryBranch) { return ((RepositoryBranch) item).getCommit().getSha(); } else { return ((RepositoryTag) item).getCommit().getSha(); } } @Override public int getCount() { return mItems.size(); } @Override public Object getItem(int position) { return getName(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.row_branch, parent, false); } ImageView icon = (ImageView) convertView.findViewById(R.id.icon); TextView title = (TextView) convertView.findViewById(R.id.title); icon.setImageResource(mItems.get(position) instanceof RepositoryTag ? mTagDrawableResId : mBranchDrawableResId); title.setText(getName(position)); return convertView; } } private class UpdateStarTask extends BackgroundTask<Void> { public UpdateStarTask() { super(RepositoryActivity.this); } @Override protected Void run() throws IOException { StarService starService = (StarService) Gh4Application.get().getService(Gh4Application.STAR_SERVICE); RepositoryId repoId = new RepositoryId(mRepoOwner, mRepoName); if (mIsStarring) { starService.unstar(repoId); } else { starService.star(repoId); } return null; } @Override protected void onSuccess(Void result) { if (mIsStarring == null) { // user refreshed while the action was in progress return; } mIsStarring = !mIsStarring; if (mRepositoryFragment != null) { mRepositoryFragment.updateStargazerCount(mIsStarring); } supportInvalidateOptionsMenu(); } } private class UpdateWatchTask extends BackgroundTask<Void> { public UpdateWatchTask() { super(RepositoryActivity.this); } @Override protected Void run() throws IOException { WatcherService watcherService = (WatcherService) Gh4Application.get().getService(Gh4Application.WATCHER_SERVICE); RepositoryId repoId = new RepositoryId(mRepoOwner, mRepoName); if (mIsWatching) { watcherService.unwatch(repoId); } else { watcherService.watch(repoId); } return null; } @Override protected void onSuccess(Void result) { if (mIsWatching == null) { // user refreshed while the action was in progress return; } mIsWatching = !mIsWatching; supportInvalidateOptionsMenu(); } } }