list) {
22 | super(list);
23 | }
24 |
25 | @Override
26 | protected int getLayoutResource() {
27 | return R.layout.commit_item;
28 | }
29 |
30 | @Override
31 | protected void bindView(View v, RevCommit commit) {
32 | TextView hash = $(v, R.id.hash);
33 | TextView message = $(v, R.id.message);
34 | TextView author = $(v, R.id.author);
35 | TextView date = $(v, R.id.date);
36 |
37 | hash.setText(ObjectId.toString(commit.getId()));
38 | message.setText(commit.getShortMessage());
39 | author.setText(commit.getAuthorIdent().getName() + " <" + commit.getAuthorIdent().getEmailAddress() + ">");
40 |
41 | // Date
42 | SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSSZ");
43 | format.setTimeZone(commit.getAuthorIdent().getTimeZone());
44 | Date commitDate = new Date();
45 | commitDate.setTime(commit.getCommitTime() * 1000l);
46 | date.setText(format.format(commitDate));
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/commit.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
10 |
11 |
23 |
24 |
33 |
34 |
43 |
44 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/commit_item.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
16 |
17 |
28 |
29 |
40 |
41 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/push.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
10 |
11 |
22 |
23 |
35 |
36 |
37 |
45 |
46 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/widget/CheckableWrapperRelativeLayout.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.widget.Checkable;
7 | import android.widget.RelativeLayout;
8 |
9 | import net.typeblog.git.R;
10 | import static net.typeblog.git.support.Utility.*;
11 |
12 | public class CheckableWrapperRelativeLayout extends RelativeLayout implements Checkable
13 | {
14 | private int mCheckableId;
15 | private Checkable mCheckable;
16 |
17 | public CheckableWrapperRelativeLayout(Context context) {
18 | this(context, null);
19 | }
20 |
21 | public CheckableWrapperRelativeLayout(Context context, AttributeSet attrs) {
22 | this(context, attrs, 0);
23 | }
24 |
25 | public CheckableWrapperRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
26 | super(context, attrs, defStyle);
27 |
28 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckableWrapper, defStyle, 0);
29 | mCheckableId = a.getResourceId(R.styleable.CheckableWrapper_target, 0);
30 | a.recycle();
31 |
32 | if (mCheckableId == 0) {
33 | throw new IllegalArgumentException("checkable not provided");
34 | }
35 | }
36 |
37 | @Override
38 | protected void onFinishInflate() {
39 | super.onFinishInflate();
40 | mCheckable = $(this, mCheckableId);
41 |
42 | if (mCheckable == null) {
43 | throw new IllegalStateException("No checkable child is found");
44 | }
45 | }
46 |
47 | @Override
48 | public boolean isChecked() {
49 | return mCheckable.isChecked();
50 | }
51 |
52 | @Override
53 | public void setChecked(boolean checked) {
54 | mCheckable.setChecked(checked);
55 | }
56 |
57 | @Override
58 | public void toggle() {
59 | mCheckable.toggle();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/tasks/GitTask.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.tasks;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Context;
5 | import android.os.AsyncTask;
6 | import android.widget.Toast;
7 |
8 | import org.eclipse.jgit.api.errors.GitAPIException;
9 |
10 | import net.typeblog.git.R;
11 | import net.typeblog.git.support.GitProvider;
12 |
13 | public abstract class GitTask extends AsyncTask
14 | {
15 | private Context mContext;
16 | private GitProvider mProvider;
17 | private ProgressDialog mProgress;
18 |
19 | public GitTask(Context context, GitProvider provider) {
20 | mContext = context;
21 | mProvider = provider;
22 | }
23 |
24 | @Override
25 | protected void onPreExecute() {
26 | super.onPreExecute();
27 |
28 | mProgress = new ProgressDialog(mContext);
29 | mProgress.setCancelable(false);
30 | mProgress.setMessage(mContext.getString(R.string.wait));
31 | mProgress.show();
32 | }
33 |
34 | @Override
35 | protected String doInBackground(P... params) {
36 | try {
37 | doGitTask(mProvider, params);
38 | } catch (GitAPIException e) {
39 | return mContext.getString(R.string.error_git) + e.getMessage();
40 | } catch (RuntimeException e) {
41 | return mContext.getString(R.string.error_internal) + e.getMessage();
42 | }
43 | return null;
44 | }
45 |
46 | @Override
47 | protected void onPostExecute(String result) {
48 | super.onPostExecute(result);
49 | mProgress.dismiss();
50 |
51 | if (result != null) {
52 | Toast.makeText(mContext, result, Toast.LENGTH_LONG).show();
53 | }
54 | }
55 |
56 | @Override
57 | protected void onProgressUpdate(String... values) {
58 | super.onProgressUpdate(values);
59 | mProgress.setMessage(values[0]);
60 | }
61 |
62 | protected abstract void doGitTask(GitProvider provider, P... params) throws GitAPIException, RuntimeException;
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/support/Utility.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.support;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.ClipboardManager;
6 | import android.content.ClipData;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.os.AsyncTask;
10 | import android.view.View;
11 | import android.widget.Toast;
12 |
13 | import android.support.v7.app.AppCompatDialog;
14 |
15 | import net.typeblog.git.R;
16 |
17 | public class Utility
18 | {
19 | public static T $(Activity activity, int id) {
20 | return (T) activity.findViewById(id);
21 | }
22 |
23 | public static T $(View v, int id) {
24 | return (T) v.findViewById(id);
25 | }
26 |
27 | public static T $(AppCompatDialog dialog, int id) {
28 | return (T) dialog.findViewById(id);
29 | }
30 |
31 | public static void copyToClipboard(String text) {
32 | ClipboardManager cm = (ClipboardManager) GlobalContext.get().getSystemService(Context.CLIPBOARD_SERVICE);
33 | ClipData cd = ClipData.newPlainText("primary", text);
34 | cm.setPrimaryClip(cd);
35 | Toast.makeText(GlobalContext.get(), R.string.copied, Toast.LENGTH_SHORT).show();
36 | }
37 |
38 | public static void showConfirmDialog(Context context, String alert, final AsyncTask task, final T[] params) {
39 | new AlertDialog.Builder(context)
40 | .setMessage(alert)
41 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
42 | @Override
43 | public void onClick(DialogInterface dialog, int which) {
44 | task.execute(params);
45 | }
46 | })
47 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
48 | @Override
49 | public void onClick(DialogInterface dialog, int which) {
50 |
51 | }
52 | })
53 | .show();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/BaseRefCreateDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.content.Context;
4 | import android.widget.EditText;
5 | import android.widget.Toast;
6 |
7 | import org.eclipse.jgit.api.errors.GitAPIException;
8 |
9 | import net.typeblog.git.R;
10 | import net.typeblog.git.support.GitProvider;
11 | import net.typeblog.git.tasks.GitTask;
12 | import static net.typeblog.git.support.Utility.*;
13 |
14 | public abstract class BaseRefCreateDialog extends ToolbarDialog
15 | {
16 | protected GitProvider mProvider;
17 | protected EditText mText;
18 |
19 | protected abstract int getTitle();
20 | protected abstract void doCreate(String name) throws GitAPIException, RuntimeException;
21 |
22 | public BaseRefCreateDialog(Context context, GitProvider provider) {
23 | super(context);
24 | mProvider = provider;
25 | }
26 |
27 | @Override
28 | protected int getLayoutResource() {
29 | return R.layout.branch_create;
30 | }
31 |
32 | @Override
33 | protected void onInitView() {
34 | setTitle(getTitle());
35 |
36 | mText = $(this, R.id.branch_name);
37 | }
38 |
39 | @Override
40 | protected void onConfirm() {
41 | String text = mText.getText().toString().trim().toLowerCase();
42 |
43 | if (text.equals("") || text.contains(" ")) {
44 | Toast.makeText(getContext(), R.string.illegal_name, Toast.LENGTH_SHORT).show();
45 | } else {
46 | new RefCreateTask().execute(text);
47 | }
48 | }
49 |
50 | private class RefCreateTask extends GitTask {
51 |
52 | public RefCreateTask() {
53 | super(getContext(), mProvider);
54 | }
55 |
56 | @Override
57 | protected void doGitTask(GitProvider provider, String... params) throws GitAPIException, RuntimeException {
58 | doCreate(params[0]);
59 | }
60 |
61 | @Override
62 | protected void onPostExecute(String result) {
63 | super.onPostExecute(result);
64 |
65 | dismiss();
66 | }
67 |
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/add_repo.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
10 |
11 |
22 |
23 |
34 |
35 |
46 |
47 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/ToolbarDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.os.Bundle;
6 | import android.view.ContextThemeWrapper;
7 | import android.view.LayoutInflater;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 |
12 | import android.support.v7.app.AppCompatDialog;
13 | import android.support.v7.widget.Toolbar;
14 |
15 | import net.typeblog.git.R;
16 | import static net.typeblog.git.support.Utility.*;
17 |
18 | public abstract class ToolbarDialog extends AppCompatDialog
19 | {
20 | private Toolbar mToolbar;
21 | private String mTitle = "";
22 | //private LayoutInflater mInflater;
23 |
24 | public ToolbarDialog(Context context) {
25 | super(context, R.style.AppTheme_Dialog);
26 | }
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(getLayoutResource());
32 | getWindow().setBackgroundDrawableResource(android.R.color.transparent);
33 |
34 | // Toolbar
35 | mToolbar = $(this, R.id.toolbar);
36 |
37 | if (mToolbar == null)
38 | throw new IllegalStateException("No Toolbar");
39 |
40 | if (Build.VERSION.SDK_INT >= 21) {
41 | mToolbar.setElevation(15.6f);
42 | }
43 |
44 | mToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
45 | mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
46 | @Override
47 | public void onClick(View view) {
48 | dismiss();
49 | }
50 | });
51 | mToolbar.inflateMenu(R.menu.dialog);
52 | mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
53 | @Override
54 | public boolean onMenuItemClick(MenuItem item) {
55 | if (item.getItemId() == R.id.finish) {
56 | onConfirm();
57 | return true;
58 | } else {
59 | return false;
60 | }
61 | }
62 | });
63 | mToolbar.setTitle(mTitle);
64 |
65 | onInitView();
66 | }
67 |
68 | @Override
69 | public void setTitle(CharSequence title) {
70 | mTitle = title.toString();
71 |
72 | if (mToolbar != null) {
73 | mToolbar.setTitle(mTitle);
74 | }
75 | }
76 |
77 | protected abstract int getLayoutResource();
78 | protected abstract void onInitView();
79 | protected abstract void onConfirm();
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/TagListFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Activity;
4 |
5 | import org.eclipse.jgit.api.errors.GitAPIException;
6 | import org.eclipse.jgit.lib.Ref;
7 |
8 | import java.util.List;
9 |
10 | import net.typeblog.git.R;
11 | import net.typeblog.git.adapters.RefAdapter;
12 | import net.typeblog.git.dialogs.GitPushDialog;
13 | import net.typeblog.git.support.GitProvider;
14 | import net.typeblog.git.tasks.GitTask;
15 | import static net.typeblog.git.support.Utility.*;
16 |
17 | public class TagListFragment extends BaseListFragment
18 | {
19 | private GitProvider mProvider;
20 |
21 | @Override
22 | public void onAttach(Activity activity) {
23 | super.onAttach(activity);
24 | mProvider = (GitProvider) activity;
25 | }
26 |
27 | @Override
28 | protected RefAdapter createAdapter() {
29 | return new RefAdapter(mItemList);
30 | }
31 |
32 | @Override
33 | protected void onViewInflated() {
34 |
35 | }
36 |
37 | @Override
38 | protected boolean shouldEnterActionMode(int pos) {
39 | return true;
40 | }
41 |
42 | @Override
43 | protected boolean multiChoice() {
44 | return false;
45 | }
46 |
47 | @Override
48 | protected int getActionModeMenu() {
49 | return R.menu.action_mode_tags;
50 | }
51 |
52 | @Override
53 | protected boolean onActionModeItemSelected(int id) {
54 | Ref tag = mItemList.get(mList.getCheckedItemPosition());
55 | String name = tag.getName();
56 | switch (id) {
57 | case R.id.tag_push:
58 | new GitPushDialog(getActivity(), mProvider, name).show();
59 | return true;
60 | case R.id.tag_delete:
61 | showConfirmDialog(
62 | getActivity(),
63 | String.format(getString(R.string.delete_confirm), name),
64 | new DeleteTask(),
65 | new String[]{name});
66 | return true;
67 | default:
68 | return super.onActionModeItemSelected(id);
69 | }
70 | }
71 |
72 | @Override
73 | protected void doLoad(List[ list) {
74 | try {
75 | list.addAll(mProvider.git().tagList().call());
76 | } catch (GitAPIException e) {
77 |
78 | }
79 | }
80 |
81 | private class DeleteTask extends GitTask {
82 | DeleteTask() {
83 | super(getActivity(), mProvider);
84 | }
85 |
86 | @Override
87 | protected void doGitTask(GitProvider provider, String... params) throws GitAPIException, RuntimeException {
88 | provider.git().tagDelete().setTags(params[0]).call();
89 | }
90 |
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/GitStatusFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Activity;
4 |
5 | import org.eclipse.jgit.api.Status;
6 | import org.eclipse.jgit.api.errors.GitAPIException;
7 |
8 | import java.io.IOException;
9 | import java.util.Set;
10 |
11 | import net.typeblog.git.R;
12 | import net.typeblog.git.support.GitProvider;
13 |
14 | public class GitStatusFragment extends BaseTextFragment
15 | {
16 | private GitProvider mProvider;
17 |
18 | @Override
19 | public void onAttach(Activity activity) {
20 | super.onAttach(activity);
21 | mProvider = (GitProvider) activity;
22 | }
23 |
24 | @Override
25 | protected String doLoad() {
26 | StringBuilder sb = new StringBuilder();
27 | Status stat = null;
28 |
29 | try {
30 | stat = mProvider.git().status().call();
31 | } catch (GitAPIException e) {
32 | return null;
33 | }
34 |
35 | try {
36 | sb.append(getString(R.string.status_branch_current))
37 | .append(" ").append(mProvider.git().getRepository().getBranch())
38 | .append("\n\n");
39 | } catch (IOException e) {
40 |
41 | }
42 |
43 | if (stat.isClean()) {
44 | sb.append(getString(R.string.status_nothing));
45 | } else {
46 | Set toCommit = stat.getUncommittedChanges();
47 | if (toCommit.size() > 0) {
48 | sb.append(getString(R.string.status_changes_to_commit)).append("\n");
49 | addSetToBuilder(toCommit, sb);
50 | }
51 |
52 | Set untracked = stat.getUntracked();
53 | if (untracked.size() > 0) {
54 | sb.append(getString(R.string.status_untracked)).append("\n");
55 | addSetToBuilder(untracked, sb);
56 | }
57 |
58 | Set modified = stat.getModified();
59 | if (modified.size() > 0) {
60 | sb.append(getString(R.string.status_modified)).append("\n");
61 | addSetToBuilder(modified, sb);
62 | }
63 |
64 | Set removed = stat.getRemoved();
65 | if (removed.size() > 0) {
66 | sb.append(getString(R.string.status_deleted)).append("\n");
67 | addSetToBuilder(removed, sb);
68 | }
69 |
70 | Set conflicting = stat.getConflicting();
71 | if (conflicting.size() > 0) {
72 | sb.append(getString(R.string.status_conflicting)).append("\n");
73 | addSetToBuilder(conflicting, sb);
74 | }
75 |
76 | }
77 |
78 | return sb.toString();
79 | }
80 |
81 | private void addSetToBuilder(Set set, StringBuilder builder) {
82 | for (String str : set) {
83 | builder.append(" ").append(str).append("\n");
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/GitRevertDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Context;
5 | import android.os.AsyncTask;
6 | import android.widget.EditText;
7 |
8 | import org.eclipse.jgit.api.errors.GitAPIException;
9 | import org.eclipse.jgit.lib.AnyObjectId;
10 | import org.eclipse.jgit.lib.ObjectId;
11 |
12 | import net.typeblog.git.R;
13 | import net.typeblog.git.support.GitProvider;
14 | import net.typeblog.git.support.RepoManager;
15 | import net.typeblog.git.tasks.GitTask;
16 | import static net.typeblog.git.support.Utility.*;
17 |
18 | public class GitRevertDialog extends ToolbarDialog
19 | {
20 | private static final String REVERT_MSG = "Revert '%s'\n\nThis reverts commit %s";
21 |
22 | private GitProvider mProvider;
23 | private AnyObjectId mCommit;
24 | private String mShortMsg;
25 |
26 | private EditText mMessage;
27 |
28 | public GitRevertDialog(Context context, GitProvider provider, AnyObjectId commit, String shortMsg) {
29 | super(context);
30 | mProvider = provider;
31 | mCommit = commit;
32 | mShortMsg = shortMsg;
33 | }
34 |
35 | @Override
36 | protected int getLayoutResource() {
37 | return R.layout.revert;
38 | }
39 |
40 | @Override
41 | protected void onInitView() {
42 | setTitle(R.string.git_revert);
43 |
44 | mMessage = $(this, R.id.commit_message);
45 | mMessage.setText(String.format(REVERT_MSG, mShortMsg, ObjectId.toString(mCommit.toObjectId())));
46 | }
47 |
48 | @Override
49 | protected void onConfirm() {
50 | new RevertTask().execute();
51 | }
52 |
53 | private class RevertTask extends GitTask {
54 |
55 | public RevertTask() {
56 | super(getContext(), mProvider);
57 | }
58 |
59 | @Override
60 | protected void doGitTask(GitProvider provider, Void... params) throws GitAPIException, RuntimeException {
61 | RepoManager m = RepoManager.getInstance();
62 | String message = mMessage.getText().toString();
63 | String username = m.getCommitterName();
64 | String email = m.getCommitterEmail();
65 |
66 | provider.git().revert()
67 | .include(mCommit)
68 | .setOurCommitName(message)
69 | .call();
70 |
71 | // Dirty hack: Do not know how to set committer identity
72 | provider.git().commit()
73 | .setAmend(true)
74 | .setMessage(message)
75 | .setCommitter(username, email)
76 | .setAuthor(username, email)
77 | .call();
78 | }
79 |
80 | @Override
81 | protected void onPostExecute(String result) {
82 | super.onPostExecute(result);
83 | dismiss();
84 | }
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/activities/ToolbarActivity.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.activities;
2 |
3 | import android.os.Build;
4 | import android.os.Bundle;
5 | import android.view.MenuItem;
6 | import android.view.View;
7 | import android.view.ViewTreeObserver;
8 |
9 | import android.support.v4.view.ViewPager;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.view.ActionMode;
12 | import android.support.v7.widget.Toolbar;
13 |
14 | import net.typeblog.git.R;
15 | import net.typeblog.git.widget.SlidingTabLayout;
16 | import net.typeblog.git.widget.SlidingTabStrip;
17 | import static net.typeblog.git.support.Utility.*;
18 |
19 | public abstract class ToolbarActivity extends AppCompatActivity
20 | {
21 | private Toolbar mToolbar;
22 | private SlidingTabLayout mTabs;
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(getLayoutResource());
28 |
29 | // Find toolbar
30 | mToolbar = $(this, R.id.toolbar);
31 | mTabs = $(this, R.id.tabs);
32 |
33 | if (mToolbar == null)
34 | throw new IllegalStateException("No Toolbar");
35 |
36 | setSupportActionBar(mToolbar);
37 |
38 | if (Build.VERSION.SDK_INT >= 21) {
39 | findViewById(R.id.toolbar_wrapper).setElevation(15.6f);
40 | }
41 |
42 | onInitView();
43 | }
44 |
45 | protected void setupTabs(ViewPager pager) {
46 | mTabs.setVisibility(View.VISIBLE);
47 | mTabs.setViewPager(pager);
48 |
49 | final int color = getResources().getColor(R.color.white);
50 | mTabs.setCustomTabColorizer(new SlidingTabStrip.SimpleTabColorizer(getResources().getColor(R.color.color_primary)) {
51 | @Override
52 | public int getSelectedTitleColor(int position) {
53 | return color;
54 | }
55 |
56 | @Override
57 | public int getIndicatorColor(int position) {
58 | return color;
59 | }
60 | });
61 |
62 | mTabs.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
63 | @Override
64 | public void onGlobalLayout() {
65 | mTabs.notifyIndicatorColorChanged();
66 | mTabs.getViewTreeObserver().removeGlobalOnLayoutListener(this);
67 | }
68 | });
69 | }
70 |
71 | protected Toolbar getToolbar() {
72 | return mToolbar;
73 | }
74 |
75 | @Override
76 | public boolean onOptionsItemSelected(MenuItem item) {
77 | if (item.getItemId() == android.R.id.home) {
78 | finish();
79 | return true;
80 | } else {
81 | return super.onOptionsItemSelected(item);
82 | }
83 | }
84 |
85 | public ActionMode startMyActionMode(ActionMode.Callback callback) {
86 | return startSupportActionMode(callback);
87 | }
88 |
89 | protected abstract int getLayoutResource();
90 | protected abstract void onInitView();
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/RemoteTrackDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.content.Context;
4 | import android.widget.AbsListView;
5 | import android.widget.ArrayAdapter;
6 | import android.widget.ListView;
7 | import android.widget.Toast;
8 |
9 | import org.eclipse.jgit.lib.StoredConfig;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.io.IOException;
14 |
15 | import net.typeblog.git.R;
16 | import net.typeblog.git.support.GitProvider;
17 | import static net.typeblog.git.support.Utility.*;
18 |
19 | public class RemoteTrackDialog extends ToolbarDialog
20 | {
21 | private List mRemotes = new ArrayList<>();
22 | private GitProvider mProvider;
23 | private ListView mList;
24 | private String mBranch, mShortBranch;
25 |
26 | public RemoteTrackDialog(Context context, GitProvider provider, String branch) {
27 | super(context);
28 | mProvider = provider;
29 | mBranch = branch;
30 | }
31 |
32 | @Override
33 | protected int getLayoutResource() {
34 | return R.layout.track_remote;
35 | }
36 |
37 | @Override
38 | protected void onInitView() {
39 | setTitle(R.string.track_remote);
40 |
41 | mList = $(this, R.id.remotes);
42 |
43 | mRemotes.addAll(mProvider.git().getRepository().getRemoteNames());
44 | mRemotes.add(0, getContext().getString(R.string.none));
45 |
46 | if (mRemotes.size() == 0) {
47 | Toast.makeText(getContext(), R.string.no_remotes, Toast.LENGTH_SHORT).show();
48 | mList.post(new Runnable() {
49 | @Override
50 | public void run() {
51 | dismiss();
52 | }
53 | });
54 | return;
55 | }
56 |
57 | // Load tracking branch
58 | mShortBranch = mBranch.replace("refs/heads/", "");
59 | StoredConfig conf = mProvider.git().getRepository().getConfig();
60 | String trakcing = conf.getString("branch", mShortBranch, "remote");
61 | int index = mRemotes.indexOf(trakcing);
62 | if (index < 0) index = 0;
63 |
64 | mList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
65 | mList.setAdapter(new ArrayAdapter(getContext(), R.layout.item_with_radio, R.id.item_name, mRemotes));
66 | mList.setItemChecked(index, true);
67 | }
68 |
69 | @Override
70 | protected void onConfirm() {
71 | int pos = mList.getCheckedItemPosition();
72 |
73 | StoredConfig conf = mProvider.git().getRepository().getConfig();
74 |
75 | if (pos == 0) {
76 | conf.unset("branch", mShortBranch, "remote");
77 | conf.unset("branch", mShortBranch, "merge");
78 | } else {
79 | String remote = mRemotes.get(pos);
80 |
81 | conf.setString("branch", mShortBranch, "remote", remote);
82 | conf.setString("branch", mShortBranch, "merge", mBranch);
83 | }
84 |
85 | try {
86 | conf.save();
87 | } catch (IOException e) {
88 |
89 | }
90 |
91 | dismiss();
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/support/RepoManager.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.support;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import org.eclipse.jgit.lib.PersonIdent;
7 |
8 | import java.io.File;
9 |
10 | import net.typeblog.git.R;
11 |
12 | public class RepoManager
13 | {
14 | private static final String PREF = "repos";
15 | private static final String LOCATIONS = "locations";
16 | private static final String URL = "url";
17 | private static final String AUTH_PASS = "auth_pass";
18 | private static final String COMMITTER_NAME = "name";
19 | private static final String COMMITTER_EMAIL = "email";
20 | private static final String SEPERATOR = ",";
21 |
22 | private static RepoManager sInstance;
23 |
24 | public static RepoManager getInstance() {
25 | if (sInstance == null) {
26 | sInstance = new RepoManager();
27 | }
28 |
29 | return sInstance;
30 | }
31 |
32 | public static String checkRepo(String location) {
33 | File f = new File(location);
34 | File git = new File(location + "/.git");
35 |
36 | if (f.exists() && (!git.exists() || !git.isDirectory())) {
37 | return GlobalContext.get().getString(R.string.exist_no_git);
38 | } else {
39 | return null;
40 | }
41 | }
42 |
43 | private SharedPreferences mPref;
44 |
45 | private RepoManager() {
46 | mPref = GlobalContext.get().getSharedPreferences(PREF, Context.MODE_WORLD_READABLE);
47 | }
48 |
49 | public void addRepo(String location, String cloneUrl, String username, String password) {
50 | String origLocations = mPref.getString(LOCATIONS, "");
51 | mPref.edit()
52 | .putString(LOCATIONS, origLocations + SEPERATOR + location)
53 | .putString(location + SEPERATOR + URL, cloneUrl)
54 | .putString(location + SEPERATOR + AUTH_PASS, username + SEPERATOR + password)
55 | .commit();
56 | }
57 |
58 | public String[] getRepoLocationList() {
59 | return mPref.getString(LOCATIONS, "").replaceFirst(SEPERATOR, "").split(SEPERATOR);
60 | }
61 |
62 | public String getUrl(String location) {
63 | return mPref.getString(location + SEPERATOR + URL, "");
64 | }
65 |
66 | public String[] getAuthPass(String location) {
67 | return mPref.getString(location + SEPERATOR + AUTH_PASS, "").split(SEPERATOR);
68 | }
69 |
70 | public void setCommitterIdentity(String name, String email) {
71 | mPref.edit().putString(COMMITTER_NAME, name)
72 | .putString(COMMITTER_EMAIL, email)
73 | .commit();
74 | }
75 |
76 | public String getCommitterName() {
77 | String ret = mPref.getString(COMMITTER_NAME, "");
78 | if (ret.equals("")) {
79 | return "root";
80 | } else {
81 | return ret;
82 | }
83 | }
84 |
85 | public String getCommitterEmail() {
86 | String ret = mPref.getString(COMMITTER_EMAIL, "");
87 | if (ret.equals("")) {
88 | return "root@localhost";
89 | } else {
90 | return ret;
91 | }
92 | }
93 |
94 | public PersonIdent getCommitterIdent() {
95 | return new PersonIdent(getCommitterName(), getCommitterEmail());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PGit
5 | Hello world!
6 |
7 |
8 | Add repo
9 | Local path
10 | Clone URL
11 | Username (Optional)
12 | Password (Optional)
13 | OK
14 |
15 |
16 | Committer
17 | Name
18 | Email
19 |
20 |
21 |
22 | ]- Files
23 | - Commits
24 | - Status
25 | - Branches
26 | - Tags
27 | - Remotes
28 | - Remote Branches
29 |
30 | Add
31 | Push
32 | Force
33 | Pull
34 | Add Remote
35 | Remote name
36 | Remote url
37 | Set track remote
38 | None
39 | Commit
40 | Message
41 | All
42 | Amend
43 | Clean All
44 | Copy Id
45 | Revert
46 | Reset to
47 | Create branch
48 | Delete
49 | Name
50 | Checkout
51 | Create tag
52 | Delete
53 | Checkout Remote
54 |
55 |
56 | Directory exists but not a git repo
57 | (○\'ω\'○)
58 | Nothing to commit, work directory clean.
59 | Current branch:
60 | Changes to commit:
61 | Untracked files:
62 | Modified files:
63 | Deleted files:
64 | Conflicting files:
65 | No remote found.
66 | No modifications found.
67 | Message cannot be empty.
68 | Please wait...
69 | Copied to clipboard
70 | Really want to reset to %s?\n\nTHIS CANNOT BE REVERTED.
71 | Checkout %s?
72 | Delete %s?
73 | Clean all changes up?\n\nTHIS CANNOT BE REVERTED.
74 | Illegal name of an object
75 | Git error:
76 | Internal error:
77 | Clone repo %s?
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/BranchListFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Activity;
4 | import android.app.ProgressDialog;
5 | import android.os.AsyncTask;
6 |
7 | import org.eclipse.jgit.api.ListBranchCommand;
8 | import org.eclipse.jgit.api.errors.GitAPIException;
9 | import org.eclipse.jgit.lib.Ref;
10 |
11 | import java.util.List;
12 |
13 | import net.typeblog.git.R;
14 | import net.typeblog.git.adapters.RefAdapter;
15 | import net.typeblog.git.dialogs.GitPushDialog;
16 | import net.typeblog.git.dialogs.RemoteTrackDialog;
17 | import net.typeblog.git.support.GitProvider;
18 | import net.typeblog.git.tasks.GitTask;
19 | import static net.typeblog.git.support.Utility.*;
20 |
21 | public class BranchListFragment extends BaseListFragment
22 | {
23 | protected GitProvider mProvider;
24 |
25 | @Override
26 | public void onAttach(Activity activity) {
27 | super.onAttach(activity);
28 | mProvider = (GitProvider) activity;
29 | }
30 |
31 | @Override
32 | protected RefAdapter createAdapter() {
33 | return new RefAdapter(mItemList);
34 | }
35 |
36 | @Override
37 | protected void onViewInflated() {
38 |
39 | }
40 |
41 | @Override
42 | protected boolean shouldEnterActionMode(int pos) {
43 | return true;
44 | }
45 |
46 | @Override
47 | protected int getActionModeMenu() {
48 | return R.menu.action_mode_branch;
49 | }
50 |
51 | @Override
52 | protected boolean onActionModeItemSelected(int id) {
53 | String selected = mItemList.get(mList.getCheckedItemPosition()).getName();
54 | switch (id) {
55 | case R.id.push:
56 | new GitPushDialog(getActivity(), mProvider, selected).show();
57 | return true;
58 | case R.id.track_remote:
59 | new RemoteTrackDialog(getActivity(), mProvider, selected).show();
60 | return true;
61 | case R.id.checkout:
62 | showConfirmDialog(
63 | getActivity(),
64 | String.format(getString(R.string.checkout_confirm), selected),
65 | new CheckoutTask(),
66 | new String[]{selected});
67 | return true;
68 | case R.id.delete:
69 | showConfirmDialog(
70 | getActivity(),
71 | String.format(getString(R.string.delete_confirm), selected),
72 | new DeleteTask(),
73 | new String[]{selected});
74 | return true;
75 | default:
76 | return super.onActionModeItemSelected(id);
77 | }
78 | }
79 |
80 | @Override
81 | protected boolean multiChoice() {
82 | return false;
83 | }
84 |
85 | @Override
86 | protected void doLoad(List[ list) {
87 | try {
88 | list.addAll(mProvider.git().branchList().setListMode(getListMode()).call());
89 | } catch (GitAPIException e) {
90 |
91 | }
92 | }
93 |
94 | protected ListBranchCommand.ListMode getListMode() {
95 | return null;
96 | }
97 |
98 | private class CheckoutTask extends GitTask {
99 |
100 | public CheckoutTask() {
101 | super(getActivity(), mProvider);
102 | }
103 |
104 | @Override
105 | protected void doGitTask(GitProvider provider, String... params) throws GitAPIException, RuntimeException {
106 | provider.git().checkout()
107 | .setName(params[0])
108 | .call();
109 | }
110 |
111 | }
112 |
113 | private class DeleteTask extends GitTask {
114 |
115 | public DeleteTask() {
116 | super(getActivity(), mProvider);
117 | }
118 |
119 | @Override
120 | protected void doGitTask(GitProvider provider, String... params) throws GitAPIException, RuntimeException{
121 | mProvider.git().branchDelete()
122 | .setBranchNames(params[0])
123 | .call();
124 | }
125 |
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/FileListFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Activity;
4 | import android.os.AsyncTask;
5 | import android.util.Log;
6 | import android.util.SparseBooleanArray;
7 |
8 | import org.eclipse.jgit.api.AddCommand;
9 | import org.eclipse.jgit.api.errors.GitAPIException;
10 |
11 | import java.io.File;
12 | import java.text.Collator;
13 | import java.util.Arrays;
14 | import java.util.ArrayList;
15 | import java.util.Collections;
16 | import java.util.Comparator;
17 | import java.util.List;
18 |
19 | import net.typeblog.git.R;
20 | import net.typeblog.git.adapters.FileAdapter;
21 | import net.typeblog.git.support.GitProvider;
22 | import static net.typeblog.git.BuildConfig.DEBUG;
23 |
24 | public class FileListFragment extends BaseListFragment
25 | {
26 | private static final String TAG = FileListFragment.class.getSimpleName();
27 |
28 | private String mRepo, mCurrent;
29 | private GitProvider mProvider;
30 |
31 | @Override
32 | protected FileAdapter createAdapter() {
33 | return new FileAdapter(mItemList);
34 | }
35 |
36 | @Override
37 | protected void onViewInflated() {
38 |
39 | }
40 |
41 | @Override
42 | public void onAttach(Activity activity) {
43 | super.onAttach(activity);
44 | mRepo = getArguments().getString("location");
45 | mCurrent = mRepo;
46 | mProvider = (GitProvider) activity;
47 | }
48 |
49 | @Override
50 | protected void onItemClick(int position) {
51 | super.onItemClick(position);
52 | File f = mItemList.get(position);
53 |
54 | if (!f.isDirectory() || f.getName().equals(".git")) return;
55 |
56 | if (f.getAbsolutePath().endsWith("..")) {
57 | goBack();
58 | } else {
59 | mCurrent = f.getAbsolutePath();
60 | reload();
61 | }
62 | }
63 |
64 | @Override
65 | protected boolean shouldEnterActionMode(int pos) {
66 | return true;
67 | }
68 |
69 | @Override
70 | protected int getActionModeMenu() {
71 | return R.menu.action_mode_file;
72 | }
73 |
74 | @Override
75 | protected boolean onActionModeItemSelected(int id) {
76 | if (id == R.id.git_add) {
77 | // TODO: Should be done in an AsyncTask
78 | AddCommand add = mProvider.git().add();
79 | SparseBooleanArray items = mList.getCheckedItemPositions();
80 | for (int i = 0; i < items.size(); i++) {
81 | int item = items.keyAt(i);
82 | boolean checked = items.valueAt(i);
83 | if (checked) {
84 | String pattern = mItemList.get(item).getPath().replace(mRepo, "");
85 |
86 | if (pattern.startsWith("/"))
87 | pattern = pattern.replaceFirst("/", "");
88 |
89 | add.addFilepattern(pattern);
90 |
91 | if (DEBUG) {
92 | Log.d(TAG, pattern);
93 | }
94 | }
95 | }
96 |
97 | try {
98 | add.call();
99 | } catch (GitAPIException e) {
100 |
101 | }
102 |
103 | return true;
104 | } else {
105 | return super.onActionModeItemSelected(id);
106 | }
107 | }
108 |
109 | @Override
110 | protected void doLoad(List list) {
111 | File[] files = new File(mCurrent).listFiles();
112 | list.addAll(Arrays.asList(files));
113 | Collections.sort(list, new FileComparator());
114 |
115 | if (canGoBack()) {
116 | list.add(0, new File(mCurrent + "/.."));
117 | }
118 | }
119 |
120 | public void goBack() {
121 | mCurrent = new File(mCurrent).getParentFile().getAbsolutePath();
122 | reload();
123 | }
124 |
125 | public boolean canGoBack() {
126 | return !new File(mCurrent).equals(new File(mRepo));
127 | }
128 |
129 | private class FileComparator implements Comparator {
130 |
131 | @Override
132 | public int compare(File p1, File p2) {
133 | if (p1.isDirectory() && !p2.isDirectory()) {
134 | return -1;
135 | } else if (!p1.isDirectory() && p2.isDirectory()) {
136 | return 0;
137 | } else {
138 | return Collator.getInstance().compare(p1.getName(), p2.getName());
139 | }
140 | }
141 |
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/GitCommitDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Context;
5 | import android.util.SparseBooleanArray;
6 | import android.view.View;
7 | import android.widget.AbsListView;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.CheckBox;
10 | import android.widget.CompoundButton;
11 | import android.widget.EditText;
12 | import android.widget.ListView;
13 | import android.widget.Toast;
14 |
15 | import org.eclipse.jgit.api.CommitCommand;
16 | import org.eclipse.jgit.api.errors.GitAPIException;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import net.typeblog.git.R;
22 | import net.typeblog.git.support.GitProvider;
23 | import net.typeblog.git.support.RepoManager;
24 | import net.typeblog.git.tasks.GitTask;
25 | import static net.typeblog.git.support.Utility.*;
26 |
27 | public class GitCommitDialog extends ToolbarDialog
28 | {
29 | private List mUncommitted = new ArrayList<>();
30 | private GitProvider mProvider;
31 | private ListView mList;
32 | private CheckBox mAll, mAmend;
33 | private EditText mMessage;
34 |
35 | public GitCommitDialog(Context context, GitProvider provider) {
36 | super(context);
37 | mProvider = provider;
38 | }
39 |
40 | @Override
41 | protected int getLayoutResource() {
42 | return R.layout.commit;
43 | }
44 |
45 | @Override
46 | protected void onInitView() {
47 | setTitle(R.string.git_commit);
48 |
49 | mAll = $(this, R.id.commit_all);
50 | mAmend = $(this, R.id.commit_amend);
51 | mMessage = $(this, R.id.commit_message);
52 | mList = $(this, R.id.commit_file);
53 |
54 | try {
55 | mUncommitted.addAll(mProvider.git().status().call().getUncommittedChanges());
56 | } catch (GitAPIException e) {
57 |
58 | }
59 |
60 | if (mUncommitted.size() == 0) {
61 | Toast.makeText(getContext(), R.string.no_modify, Toast.LENGTH_SHORT).show();
62 | mList.setVisibility(View.GONE);
63 | mAll.setVisibility(View.GONE);
64 | mAmend.setChecked(true);
65 | }
66 |
67 | mList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
68 | mList.setAdapter(new ArrayAdapter(getContext(), R.layout.item_with_check_multiline, R.id.item_name, mUncommitted));
69 |
70 | mAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
71 | @Override
72 | public void onCheckedChanged(CompoundButton button, boolean checked) {
73 | mList.setVisibility(checked ? View.GONE : View.VISIBLE);
74 | }
75 | });
76 | }
77 |
78 | @Override
79 | protected void onConfirm() {
80 | new CommitTask().execute();
81 | }
82 |
83 | private class CommitTask extends GitTask {
84 | String message;
85 | boolean exit = false;
86 |
87 | public CommitTask() {
88 | super(getContext(), mProvider);
89 | }
90 |
91 | @Override
92 | protected void onPreExecute() {
93 | super.onPreExecute();
94 |
95 | message = mMessage.getText().toString().trim();
96 |
97 | if (message.equals("")) {
98 | Toast.makeText(getContext(), R.string.no_message, Toast.LENGTH_SHORT).show();
99 | exit = true;
100 | }
101 | }
102 |
103 | @Override
104 | protected void doGitTask(GitProvider provider, Void... params) throws GitAPIException, RuntimeException {
105 | if (!exit) {
106 | CommitCommand commit = provider.git().commit();
107 | commit.setAmend(mAmend.isChecked());
108 | commit.setMessage(message);
109 |
110 | boolean all = mAll.isChecked();
111 |
112 | if (all) {
113 | commit.setAll(true);
114 | } else {
115 | commit.setAll(false);
116 |
117 | SparseBooleanArray a = mList.getCheckedItemPositions();
118 | for (int i = 0; i < a.size(); i++) {
119 | int pos = a.keyAt(i);
120 | if (a.valueAt(i)) {
121 | commit.setOnly(mUncommitted.get(pos));
122 | }
123 | }
124 |
125 | }
126 |
127 | // Committer
128 | RepoManager m = RepoManager.getInstance();
129 | commit.setCommitter(m.getCommitterName(), m.getCommitterEmail());
130 |
131 | commit.call();
132 | }
133 | }
134 |
135 | @Override
136 | protected void onPostExecute(String result) {
137 | super.onPostExecute(result);
138 | dismiss();
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/BaseListFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Fragment;
4 | import android.os.AsyncTask;
5 | import android.os.Bundle;
6 | import android.view.LayoutInflater;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.AbsListView;
12 | import android.widget.AdapterView;
13 | import android.widget.BaseAdapter;
14 | import android.widget.Checkable;
15 | import android.widget.ListView;
16 |
17 | import android.support.v4.view.ViewCompat;
18 | import android.support.v7.view.ActionMode;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import net.typeblog.git.R;
24 | import net.typeblog.git.activities.ToolbarActivity;
25 | import static net.typeblog.git.support.Utility.*;
26 |
27 | public abstract class BaseListFragment extends Fragment implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener
28 | {
29 | protected ListView mList;
30 | protected List mItemList = new ArrayList<>();
31 | protected A mAdapter;
32 |
33 | protected abstract A createAdapter();
34 | protected abstract void onViewInflated();
35 | protected abstract void doLoad(List list);
36 | protected void onItemClick(int position) {};
37 | protected boolean shouldEnterActionMode(int pos) {
38 | return false;
39 | }
40 | protected int getActionModeMenu() {
41 | return 0;
42 | }
43 | protected boolean onActionModeItemSelected(int id) {
44 | return false;
45 | }
46 | protected boolean multiChoice() {
47 | return true;
48 | }
49 |
50 | @Override
51 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
52 | View v = inflater.inflate(R.layout.list, container, false);
53 |
54 | mList = $(v, R.id.list);
55 | mList.setOnItemClickListener(this);
56 | mList.setOnItemLongClickListener(this);
57 | mAdapter = createAdapter();
58 | mList.setAdapter(mAdapter);
59 |
60 | onViewInflated();
61 | reload();
62 |
63 | return v;
64 | }
65 |
66 | @Override
67 | public void onItemClick(AdapterView> list, View v, int position, long id) {
68 | onItemClick(position);
69 | }
70 |
71 | @Override
72 | public boolean onItemLongClick(AdapterView> list, View v, final int position, long id) {
73 |
74 | if (!shouldEnterActionMode(position)) return false;
75 |
76 | ((ToolbarActivity) getActivity()).startMyActionMode(new ActionMode.Callback() {
77 |
78 | @Override
79 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
80 | getActivity().getMenuInflater().inflate(getActionModeMenu(), menu);
81 | mList.setChoiceMode(multiChoice() ? AbsListView.CHOICE_MODE_MULTIPLE : AbsListView.CHOICE_MODE_SINGLE);
82 | mList.setItemChecked(position, true);
83 | mList.setOnItemClickListener(null);
84 | mList.setOnItemLongClickListener(null);
85 | return true;
86 | }
87 |
88 | @Override
89 | public boolean onPrepareActionMode(ActionMode p1, Menu p2) {
90 | return false;
91 | }
92 |
93 | @Override
94 | public boolean onActionItemClicked(ActionMode mode, MenuItem menu) {
95 | if (onActionModeItemSelected(menu.getItemId())) {
96 | mode.finish();
97 | return true;
98 | } else {
99 | return false;
100 | }
101 | }
102 |
103 | @Override
104 | public void onDestroyActionMode(ActionMode mode) {
105 | mList.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
106 | mList.clearChoices();
107 | mList.setOnItemClickListener(BaseListFragment.this);
108 | mList.setOnItemLongClickListener(BaseListFragment.this);
109 |
110 | for (int i = 0; i < mList.getChildCount(); i++) {
111 | ((Checkable) mList.getChildAt(i)).setChecked(false);
112 | }
113 | }
114 | });
115 | return true;
116 | }
117 |
118 | protected void reload() {
119 | new LoaderTask().execute();
120 | }
121 |
122 | private class LoaderTask extends AsyncTask> {
123 |
124 | @Override
125 | protected List doInBackground(Void... params) {
126 | List ret = new ArrayList<>();
127 | doLoad(ret);
128 | return ret;
129 | }
130 |
131 | @Override
132 | protected void onPostExecute(List result) {
133 | super.onPostExecute(result);
134 | mItemList.clear();
135 | mItemList.addAll(result);
136 | mAdapter.notifyDataSetChanged();
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/dialogs/BasePullPushDialog.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.dialogs;
2 |
3 | import android.content.Context;
4 | import android.widget.CheckBox;
5 | import android.widget.ArrayAdapter;
6 | import android.widget.AbsListView;
7 | import android.widget.ListView;
8 | import android.widget.TextView;
9 | import android.widget.Toast;
10 |
11 | import org.eclipse.jgit.api.errors.GitAPIException;
12 | import org.eclipse.jgit.lib.ProgressMonitor;
13 | import org.eclipse.jgit.lib.Ref;
14 | import org.eclipse.jgit.transport.CredentialsProvider;
15 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | import net.typeblog.git.R;
21 | import net.typeblog.git.support.GitProvider;
22 | import net.typeblog.git.support.RepoManager;
23 | import net.typeblog.git.tasks.GitTask;
24 | import static net.typeblog.git.support.Utility.*;
25 |
26 | public abstract class BasePullPushDialog extends ToolbarDialog
27 | {
28 | protected GitProvider mProvider;
29 | private String mRef;
30 |
31 | private List mRemotes = new ArrayList<>();
32 |
33 | protected CheckBox mForce;
34 | private ListView mList;
35 | private TextView mUserName, mPassword;
36 |
37 | protected abstract void doTask(ProgressMonitor monitor, String remote, String ref, CredentialsProvider authorization, boolean force) throws GitAPIException, RuntimeException;
38 |
39 | public BasePullPushDialog(Context context, GitProvider provider, String ref) {
40 | super(context);
41 | mProvider = provider;
42 | mRef = ref;
43 | }
44 |
45 | @Override
46 | protected int getLayoutResource() {
47 | return R.layout.push;
48 | }
49 |
50 | @Override
51 | protected void onInitView() {
52 | mForce = $(this, R.id.force);
53 | mList = $(this, R.id.remotes);
54 | mUserName = $(this, R.id.username);
55 | mPassword = $(this, R.id.password);
56 |
57 | String[] auth = RepoManager.getInstance().getAuthPass(mProvider.getLocation());
58 | if (auth != null && auth.length > 1) {
59 | mUserName.setText(auth[0]);
60 | mPassword.setText(auth[1]);
61 | }
62 |
63 | mRemotes.addAll(mProvider.git().getRepository().getRemoteNames());
64 |
65 | if (mRemotes.size() == 0) {
66 | Toast.makeText(getContext(), R.string.no_remotes, Toast.LENGTH_SHORT).show();
67 | mForce.post(new Runnable() {
68 | @Override
69 | public void run() {
70 | dismiss();
71 | }
72 | });
73 | return;
74 | }
75 |
76 | mList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
77 | mList.setAdapter(new ArrayAdapter(getContext(), R.layout.item_with_radio, R.id.item_name, mRemotes));
78 | mList.setItemChecked(0, true);
79 | }
80 |
81 | @Override
82 | protected void onConfirm() {
83 | new Task().execute();
84 | }
85 |
86 | private class Task extends GitTask {
87 | private String mRemote;
88 | private String mMessage;
89 | private String mName, mPass;
90 | private int mWorks = 0, mCompleted = 0;
91 |
92 | public Task() {
93 | super(getContext(), mProvider);
94 | }
95 |
96 | @Override
97 | protected void onPreExecute() {
98 | super.onPreExecute();
99 | mRemote = mRemotes.get(mList.getCheckedItemPosition());
100 | mName = mUserName.getText().toString();
101 | mPass = mPassword.getText().toString();
102 | mMessage = getContext().getString(R.string.wait);
103 | }
104 |
105 | @Override
106 | protected void doGitTask(GitProvider provider, Void... params) throws GitAPIException, RuntimeException {
107 | doTask(new ProgressMonitor() {
108 | @Override
109 | public void start(int p1) {
110 |
111 | }
112 |
113 | @Override
114 | public void beginTask(String name, int works) {
115 | mMessage = name;
116 | mWorks = works;
117 | mCompleted = 0;
118 | publishProgress(name);
119 | }
120 |
121 | @Override
122 | public void update(int completed) {
123 | mCompleted += completed;
124 | publishProgress(mMessage + " (" + mCompleted + "/" + mWorks + ")");
125 | }
126 |
127 | @Override
128 | public void endTask() {
129 |
130 | }
131 |
132 | @Override
133 | public boolean isCancelled() {
134 | return false;
135 | }
136 | }, mRemote, mRef, new UsernamePasswordCredentialsProvider(mName, mPass), mForce.isChecked());
137 | /*try {
138 |
139 | } catch (GitAPIException e) {
140 |
141 | }*/
142 | }
143 |
144 | @Override
145 | protected void onPostExecute(String result) {
146 | super.onPostExecute(result);
147 | dismiss();
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/fragments/CommitListFragment.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.fragments;
2 |
3 | import android.app.Activity;
4 | import android.app.ProgressDialog;
5 | import android.os.AsyncTask;
6 | import android.util.Log;
7 |
8 | import org.eclipse.jgit.api.Git;
9 | import org.eclipse.jgit.api.errors.GitAPIException;
10 | import org.eclipse.jgit.lib.ObjectId;
11 | import org.eclipse.jgit.lib.Ref;
12 | import org.eclipse.jgit.lib.Repository;
13 | import org.eclipse.jgit.revwalk.RevCommit;
14 | import org.eclipse.jgit.revwalk.RevWalk;
15 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Map;
22 |
23 | import net.typeblog.git.R;
24 | import net.typeblog.git.adapters.CommitAdapter;
25 | import net.typeblog.git.dialogs.GitRevertDialog;
26 | import net.typeblog.git.support.GitProvider;
27 | import static net.typeblog.git.support.Utility.*;
28 | import static net.typeblog.git.BuildConfig.DEBUG;
29 |
30 | public class CommitListFragment extends BaseListFragment
31 | {
32 | private static final String TAG = CommitListFragment.class.getSimpleName();
33 |
34 | private GitProvider mProvider;
35 |
36 | @Override
37 | protected CommitAdapter createAdapter() {
38 | return new CommitAdapter(mItemList);
39 | }
40 |
41 | @Override
42 | protected void onViewInflated() {
43 |
44 | }
45 |
46 | @Override
47 | public void onAttach(Activity activity) {
48 | super.onAttach(activity);
49 |
50 | if (!(activity instanceof GitProvider)) {
51 | throw new IllegalStateException("no provider");
52 | }
53 |
54 | mProvider = (GitProvider) activity;
55 | }
56 |
57 | @Override
58 | protected boolean shouldEnterActionMode(int pos) {
59 | return true;
60 | }
61 |
62 | @Override
63 | protected boolean multiChoice() {
64 | return false;
65 | }
66 |
67 | @Override
68 | protected int getActionModeMenu() {
69 | return R.menu.action_mode_commits;
70 | }
71 |
72 | @Override
73 | protected boolean onActionModeItemSelected(int id) {
74 | RevCommit commit = mItemList.get(mList.getCheckedItemPosition());
75 | String commitId = ObjectId.toString(commit.getId());
76 | String shortMessage = commit.getShortMessage();
77 | switch (id) {
78 | case R.id.copy_commit_id:
79 | copyToClipboard(commitId);
80 | return true;
81 | case R.id.revert:
82 | new GitRevertDialog(getActivity(), mProvider, commit, shortMessage).show();
83 | return true;
84 | case R.id.reset_to:
85 | showConfirmDialog(
86 | getActivity(),
87 | String.format(getString(R.string.reset_to_confirm), commitId),
88 | new ResetToTask(),
89 | new String[]{commitId});
90 | return true;
91 | default:
92 | return super.onActionModeItemSelected(id);
93 | }
94 | }
95 |
96 | @Override
97 | protected void doLoad(List list) {
98 | try {
99 | RevWalk walk = new RevWalk(mProvider.git().getRepository());
100 | String branch = "refs/heads/" + mProvider.git().getRepository().getBranch();
101 | for (RevCommit commit : mProvider.git().log().setMaxCount(200).call()) {
102 |
103 | // Trick: Pick out commits from this branch
104 | // Origin: http://stackoverflow.com/questions/15822544/jgit-how-to-get-all-commits-of-a-branch-without-changes-to-the-working-direct
105 | RevCommit targetCommit = walk.parseCommit(mProvider.git().getRepository().resolve(commit.getName()));
106 | boolean foundInThisBranch = false;
107 | for (Map.Entry m : mProvider.git().getRepository().getAllRefs().entrySet()) {
108 | if (m.getKey().startsWith("refs/heads")) {
109 | if (walk.isMergedInto(targetCommit, walk.parseCommit(m.getValue().getObjectId()))) {
110 | String foundInBranch = m.getValue().getName();
111 |
112 | if (foundInBranch.equals(branch)) {
113 | foundInThisBranch = true;
114 | }
115 | }
116 | }
117 | }
118 |
119 | if (!foundInThisBranch) continue;
120 |
121 | if (DEBUG) {
122 | Log.d(TAG, "adding commit " + commit.getShortMessage());
123 | }
124 |
125 | list.add(commit);
126 | }
127 | } catch (IOException e) {
128 | Log.e(TAG, Log.getStackTraceString(e));
129 | } catch (GitAPIException e) {
130 | Log.e(TAG, Log.getStackTraceString(e));
131 | }
132 | }
133 |
134 | private class ResetToTask extends AsyncTask {
135 | private ProgressDialog progress;
136 |
137 | @Override
138 | protected void onPreExecute() {
139 | super.onPreExecute();
140 |
141 | progress = new ProgressDialog(getActivity());
142 | progress.setCancelable(false);
143 | progress.setMessage(getString(R.string.wait));
144 | progress.show();
145 | }
146 |
147 | @Override
148 | protected Void doInBackground(String... params) {
149 | try {
150 | mProvider.git().reset()
151 | .setRef(params[0])
152 | .call();
153 | } catch (GitAPIException e) {
154 |
155 | }
156 | return null;
157 | }
158 |
159 | @Override
160 | protected void onPostExecute(Void result) {
161 | super.onPostExecute(result);
162 |
163 | progress.dismiss();
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/activities/RepoActivity.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.activities;
2 |
3 | import android.app.Fragment;
4 | import android.app.ProgressDialog;
5 | import android.view.Menu;
6 | import android.view.MenuItem;
7 |
8 | import android.support.v4.view.ViewPager;
9 | import android.support.v13.app.FragmentStatePagerAdapter;
10 |
11 | import org.eclipse.jgit.api.Git;
12 | import org.eclipse.jgit.api.errors.GitAPIException;
13 | import org.eclipse.jgit.lib.Repository;
14 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
15 |
16 | import java.io.File;
17 | import java.io.IOException;
18 |
19 | import net.typeblog.git.R;
20 | import net.typeblog.git.dialogs.BranchCreateDialog;
21 | import net.typeblog.git.dialogs.GitCommitDialog;
22 | import net.typeblog.git.dialogs.GitPullDialog;
23 | import net.typeblog.git.dialogs.RemoteAddDialog;
24 | import net.typeblog.git.dialogs.TagCreateDialog;
25 | import net.typeblog.git.fragments.BranchListFragment;
26 | import net.typeblog.git.fragments.CommitListFragment;
27 | import net.typeblog.git.fragments.FileListFragment;
28 | import net.typeblog.git.fragments.GitStatusFragment;
29 | import net.typeblog.git.fragments.RemoteBranchListFragment;
30 | import net.typeblog.git.fragments.RemoteListFragment;
31 | import net.typeblog.git.fragments.TagListFragment;
32 | import net.typeblog.git.tasks.GitTask;
33 | import net.typeblog.git.support.GitProvider;
34 | import net.typeblog.git.support.RepoManager;
35 | import static net.typeblog.git.support.Utility.*;
36 |
37 | public class RepoActivity extends ToolbarActivity implements GitProvider
38 | {
39 | private Repository mRepo;
40 | private Git mGit;
41 |
42 | private Fragment[] mFragments = {
43 | new FileListFragment(),
44 | new CommitListFragment(),
45 | new GitStatusFragment(),
46 | new BranchListFragment(),
47 | new TagListFragment(),
48 | new RemoteListFragment(),
49 | new RemoteBranchListFragment()
50 | };
51 |
52 | private ViewPager mPager;
53 |
54 | @Override
55 | protected int getLayoutResource() {
56 | return R.layout.repo;
57 | }
58 |
59 | @Override
60 | protected void onInitView() {
61 |
62 | try {
63 | mRepo = new FileRepositoryBuilder()
64 | .setGitDir(new File(getIntent().getStringExtra("location") + "/.git"))
65 | .readEnvironment()
66 | .findGitDir()
67 | .build();
68 | } catch (IOException e) {
69 | finish();
70 | }
71 | mGit = new Git(mRepo);
72 |
73 | String mail = RepoManager.getInstance().getCommitterEmail();
74 | String name = RepoManager.getInstance().getCommitterName();
75 |
76 | mGit.getRepository().getConfig().setString("user", null, "name", name);
77 | mGit.getRepository().getConfig().setString("user", null, "email", mail);
78 |
79 | try {
80 | mGit.getRepository().getConfig().save();
81 | } catch (Exception e) {
82 |
83 | }
84 |
85 | // Pager
86 | mPager = $(this, R.id.pager);
87 | mPager.setOffscreenPageLimit(Integer.MAX_VALUE);
88 |
89 | for (Fragment f : mFragments) {
90 | f.setArguments(getIntent().getExtras());
91 | }
92 |
93 | final String[] tabs = getResources().getStringArray(R.array.repo_tabs);
94 | mPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
95 | @Override
96 | public int getCount() {
97 | return mFragments.length;
98 | }
99 |
100 | @Override
101 | public Fragment getItem(int pos) {
102 | return mFragments[pos];
103 | }
104 |
105 | @Override
106 | public String getPageTitle(int pos) {
107 | return tabs[pos];
108 | }
109 | });
110 | setupTabs(mPager);
111 |
112 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
113 | getSupportActionBar().setTitle(getIntent().getStringExtra("name"));
114 |
115 | }
116 |
117 | @Override
118 | public Git git() {
119 | return mGit;
120 | }
121 |
122 | @Override
123 | public String getLocation() {
124 | return getIntent().getStringExtra("location");
125 | }
126 |
127 | @Override
128 | public void onBackPressed() {
129 | int cur = mPager.getCurrentItem();
130 | if (mFragments[cur] instanceof FileListFragment) {
131 | FileListFragment f = (FileListFragment) mFragments[cur];
132 |
133 | if (f.canGoBack()) {
134 | f.goBack();
135 | } else {
136 | super.onBackPressed();
137 | }
138 | } else {
139 | super.onBackPressed();
140 | }
141 | }
142 |
143 | @Override
144 | public boolean onCreateOptionsMenu(Menu menu) {
145 | getMenuInflater().inflate(R.menu.repo, menu);
146 | return true;
147 | }
148 |
149 | @Override
150 | public boolean onOptionsItemSelected(MenuItem item) {
151 | switch (item.getItemId()) {
152 | case R.id.commit:
153 | new GitCommitDialog(this, this).show();
154 | return true;
155 | case R.id.pull:
156 | new GitPullDialog(this, this).show();
157 | return true;
158 | case R.id.add_remote:
159 | new RemoteAddDialog(this, this).show();
160 | return true;
161 | case R.id.clean_all:
162 | showConfirmDialog(
163 | this,
164 | getString(R.string.cleanup_confirm),
165 | new CleanAllTask(),
166 | new Void[]{});
167 | return true;
168 | case R.id.branch_create:
169 | new BranchCreateDialog(this, this).show();
170 | return true;
171 | case R.id.tag_create:
172 | new TagCreateDialog(this, this).show();
173 | return true;
174 | default:
175 | return super.onOptionsItemSelected(item);
176 | }
177 | }
178 |
179 | private class CleanAllTask extends GitTask {
180 |
181 | public CleanAllTask() {
182 | super(RepoActivity.this, RepoActivity.this);
183 | }
184 |
185 | @Override
186 | protected void doGitTask(GitProvider provider, Void... params) throws GitAPIException, RuntimeException {
187 | mGit.reset().call();
188 | mGit.stashCreate().call();
189 | mGit.clean().call();
190 | }
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/activities/HomeActivity.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.activities;
2 |
3 | import android.content.Intent;
4 | import android.util.Log;
5 | import android.view.View;
6 | import android.widget.AdapterView;
7 | import android.widget.EditText;
8 | import android.widget.ListView;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.widget.Toast;
12 |
13 | import android.support.v7.widget.AppCompatEditText;
14 |
15 | import org.eclipse.jgit.api.Git;
16 | import org.eclipse.jgit.api.errors.GitAPIException;
17 | import org.eclipse.jgit.lib.ProgressMonitor;
18 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
19 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
20 |
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.util.Arrays;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | import net.typeblog.git.R;
28 | import net.typeblog.git.adapters.RepoAdapter;
29 | import net.typeblog.git.dialogs.ToolbarDialog;
30 | import net.typeblog.git.support.GitProvider;
31 | import net.typeblog.git.support.RepoManager;
32 | import net.typeblog.git.tasks.GitTask;
33 | import static net.typeblog.git.BuildConfig.DEBUG;
34 | import static net.typeblog.git.support.Utility.*;
35 |
36 | public class HomeActivity extends ToolbarActivity implements AdapterView.OnItemClickListener
37 | {
38 | private static final String TAG = HomeActivity.class.getSimpleName();
39 |
40 | private List mRepos = new ArrayList<>();
41 | private List mRepoNames = new ArrayList<>();
42 | private List mRepoUrls = new ArrayList<>();
43 | private RepoAdapter mAdapter;
44 | private ListView mList;
45 | private View mAdd;
46 |
47 | @Override
48 | protected int getLayoutResource() {
49 | return R.layout.main;
50 | }
51 |
52 | @Override
53 | protected void onInitView() {
54 | //new AddRepoDialog().show();
55 | mList = $(this, R.id.repo_list);
56 | mAdd = $(this, R.id.fab);
57 | mAdapter = new RepoAdapter(mRepoNames, mRepoUrls);
58 | mList.setOnItemClickListener(this);
59 | mList.setAdapter(mAdapter);
60 | reload();
61 |
62 | mAdd.setOnClickListener(new View.OnClickListener() {
63 | @Override
64 | public void onClick(View v) {
65 | new AddRepoDialog().show();
66 | }
67 | });
68 | }
69 |
70 | @Override
71 | public void onItemClick(AdapterView> list, View v, int pos, long id) {
72 | String path = mRepos.get(pos);
73 | if (new File(path).isDirectory() && new File(path + "/.git").isDirectory()) {
74 | Intent i = new Intent(Intent.ACTION_MAIN);
75 | i.setClass(this, RepoActivity.class);
76 | i.putExtra("location", mRepos.get(pos));
77 | i.putExtra("name", mRepoNames.get(pos));
78 | startActivity(i);
79 | } else {
80 | showConfirmDialog(
81 | this,
82 | String.format(getString(R.string.clone_confirm), mRepoUrls.get(pos)),
83 | new CloneTask(path, mRepoUrls.get(pos)),
84 | RepoManager.getInstance().getAuthPass(path));
85 | }
86 |
87 | }
88 |
89 | @Override
90 | public boolean onCreateOptionsMenu(Menu menu) {
91 | getMenuInflater().inflate(R.menu.home, menu);
92 | return true;
93 | }
94 |
95 | @Override
96 | public boolean onOptionsItemSelected(MenuItem item) {
97 | switch (item.getItemId()) {
98 | case R.id.committer:
99 | new CommitterDialog().show();
100 | return true;
101 | default:
102 | return super.onOptionsItemSelected(item);
103 | }
104 | }
105 |
106 | private void reload() {
107 | mRepos.clear();
108 | mRepoNames.clear();
109 | mRepoUrls.clear();
110 |
111 | RepoManager manager = RepoManager.getInstance();
112 |
113 | String[] repos = manager.getRepoLocationList();
114 | mRepos.addAll(Arrays.asList(repos));
115 |
116 | for (String repo : mRepos) {
117 |
118 | repo = repo.trim();
119 |
120 | if (repo.equals("")) continue;
121 |
122 | if (DEBUG) {
123 | Log.d(TAG, "processing repo " + repo);
124 | }
125 |
126 | if (repo.lastIndexOf("/") == repo.length() - 1) {
127 | repo = repo.substring(0, repo.length() - 1);
128 | }
129 |
130 | mRepoNames.add(repo.substring(repo.lastIndexOf("/") + 1, repo.length()));
131 | mRepoUrls.add(manager.getUrl(repo));
132 | }
133 |
134 | mAdapter.notifyDataSetChanged();
135 | }
136 |
137 | private GitProvider buildGitProvider(final String repo) {
138 | Git g = null;
139 |
140 | try {
141 | g = new Git(new FileRepositoryBuilder()
142 | .setGitDir(new File(repo + "/.git"))
143 | .readEnvironment()
144 | .findGitDir()
145 | .build());
146 | } catch (IOException e) {
147 | throw new RuntimeException(e);
148 | }
149 |
150 | final Git git = g;
151 |
152 | return new GitProvider() {
153 | @Override
154 | public Git git() {
155 | return git;
156 | }
157 |
158 | @Override
159 | public String getLocation() {
160 | return repo;
161 | }
162 | };
163 | }
164 |
165 | private class AddRepoDialog extends ToolbarDialog {
166 | private EditText location, url, username, password;
167 |
168 | AddRepoDialog() {
169 | super(HomeActivity.this);
170 | }
171 |
172 | @Override
173 | protected int getLayoutResource() {
174 | return R.layout.add_repo;
175 | }
176 |
177 | @Override
178 | protected void onInitView() {
179 | setTitle(R.string.add_repo);
180 |
181 | location = $(this, R.id.local_path);
182 | url = $(this, R.id.clone_url);
183 | username = $(this, R.id.username);
184 | password = $(this, R.id.password);
185 | }
186 |
187 | @Override
188 | protected void onConfirm() {
189 | String location = this.location.getText().toString();
190 |
191 | String msg = RepoManager.checkRepo(location);
192 | if (msg != null) {
193 | Toast.makeText(HomeActivity.this, msg, Toast.LENGTH_SHORT).show();
194 | } else {
195 | String url = this.url.getText().toString();
196 | String username = this.username.getText().toString().trim();
197 | String password = this.password.getText().toString().trim();
198 |
199 | RepoManager.getInstance().addRepo(location, url, username, password);
200 |
201 | dismiss();
202 | reload();
203 | }
204 | }
205 | }
206 |
207 | private class CommitterDialog extends ToolbarDialog {
208 | EditText name, email;
209 |
210 | CommitterDialog() {
211 | super(HomeActivity.this);
212 | }
213 |
214 | @Override
215 | protected int getLayoutResource() {
216 | return R.layout.set_committer;
217 | }
218 |
219 | @Override
220 | protected void onInitView() {
221 | setTitle(R.string.committer);
222 |
223 | name = $(this, R.id.committer_name);
224 | email = $(this, R.id.committer_email);
225 |
226 | name.setText(RepoManager.getInstance().getCommitterName());
227 | email.setText(RepoManager.getInstance().getCommitterEmail());
228 | }
229 |
230 | @Override
231 | protected void onConfirm() {
232 | RepoManager.getInstance().setCommitterIdentity(name.getText().toString().trim(), email.getText().toString().trim());
233 | dismiss();
234 | }
235 |
236 | }
237 |
238 | private class CloneTask extends GitTask {
239 | String currentMsg = "";
240 | int currentWork = 0;
241 | int currentCompleted = 0;
242 | String url = "";
243 |
244 | CloneTask(String repo, String url) {
245 | super(HomeActivity.this, buildGitProvider(repo));
246 | this.url = url;
247 | }
248 |
249 | @Override
250 | protected void doGitTask(GitProvider provider, String... params) throws GitAPIException, RuntimeException {
251 | provider.git().cloneRepository()
252 | .setDirectory(new File(provider.getLocation()))
253 | .setURI(url)
254 | .setBare(false)
255 | .setCredentialsProvider(new UsernamePasswordCredentialsProvider(params[0], params[1]))
256 | .setCloneSubmodules(true)
257 | .setCloneAllBranches(true)
258 | .setProgressMonitor(new ProgressMonitor() {
259 | @Override
260 | public void start(int p1) {
261 | }
262 |
263 | @Override
264 | public void beginTask(String msg, int work) {
265 | currentMsg = msg;
266 | currentWork = work;
267 | currentCompleted = 0;
268 |
269 | publishProgress(currentMsg + "(" + "0/" + work + ")");
270 | }
271 |
272 | @Override
273 | public void update(int completed) {
274 | currentCompleted += completed;
275 | publishProgress(currentMsg + "(" + currentCompleted + "/" + currentWork + ")");
276 | }
277 |
278 | @Override
279 | public void endTask() {
280 | }
281 |
282 | @Override
283 | public boolean isCancelled() {
284 | return false;
285 | }
286 |
287 |
288 | })
289 | .call();
290 | }
291 |
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/widget/SlidingTabStrip.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.widget;
2 |
3 | import android.R;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.view.View;
11 | import android.view.ViewParent;
12 | import android.widget.LinearLayout;
13 | import android.widget.TextView;
14 |
15 | public class SlidingTabStrip extends LinearLayout {
16 |
17 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0;
18 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
19 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 2;
20 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
21 |
22 | private final int mBottomBorderThickness;
23 | private final Paint mBottomBorderPaint;
24 |
25 | private final int mSelectedIndicatorThickness;
26 | private final Paint mSelectedIndicatorPaint;
27 |
28 | private final int mDefaultBottomBorderColor;
29 |
30 | private int mSelectedPosition;
31 | private float mSelectionOffset;
32 |
33 | private SlidingTabLayout.TabColorizer mCustomTabColorizer;
34 | private final SimpleTabColorizer mDefaultTabColorizer;
35 |
36 | SlidingTabStrip(Context context) {
37 | this(context, null);
38 | }
39 |
40 | SlidingTabStrip(Context context, AttributeSet attrs) {
41 | super(context, attrs);
42 | setWillNotDraw(false);
43 |
44 | final float density = getResources().getDisplayMetrics().density;
45 |
46 | TypedValue outValue = new TypedValue();
47 | context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
48 | final int themeForegroundColor = outValue.data;
49 | context.getTheme().resolveAttribute(net.typeblog.git.R.attr.colorPrimary, outValue, true);
50 | int colorPrimary = outValue.data;
51 |
52 | mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
53 | DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
54 |
55 | mDefaultTabColorizer = new SimpleTabColorizer(colorPrimary);
56 | mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
57 |
58 | mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
59 | mBottomBorderPaint = new Paint();
60 | mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
61 |
62 | mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
63 | mSelectedIndicatorPaint = new Paint();
64 | }
65 |
66 | void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
67 | mCustomTabColorizer = customTabColorizer;
68 | invalidate();
69 | }
70 |
71 | void setSelectedIndicatorColors(int... colors) {
72 | // Make sure that the custom colorizer is removed
73 | mCustomTabColorizer = null;
74 | mDefaultTabColorizer.setIndicatorColors(colors);
75 | invalidate();
76 | }
77 |
78 | void onViewPagerPageChanged(int position, float positionOffset) {
79 | mSelectedPosition = position;
80 | mSelectionOffset = positionOffset;
81 |
82 | // Title colors changes when page scrolled
83 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
84 | ? mCustomTabColorizer
85 | : mDefaultTabColorizer;
86 |
87 | int id = getSlidingTabLayout().getTextViewId();
88 |
89 | View selected = getChildAt(mSelectedPosition);
90 | View selectedTitle = id == 0 ? selected : selected.findViewById(id);
91 |
92 | int selectedColor = tabColorizer.getSelectedTitleColor(mSelectedPosition);
93 | int normalColor = tabColorizer.getNormalTitleColor(mSelectedPosition);
94 |
95 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
96 | View next = getChildAt(mSelectedPosition + 1);
97 | View nextTitle = id == 0 ? next : next.findViewById(id);
98 |
99 | // Set the gradient title colors
100 | int nextSelectedColor = tabColorizer.getSelectedTitleColor(mSelectedPosition + 1);
101 | int nextNormalColor = tabColorizer.getNormalTitleColor(mSelectedPosition + 1);
102 |
103 | int selectedBlend = blendColors(selectedColor, normalColor, 1.0f - mSelectionOffset);
104 | int nextBlend = blendColors(nextSelectedColor, nextNormalColor, mSelectionOffset);
105 |
106 | if (selectedTitle instanceof TextView)
107 | ((TextView) selectedTitle).setTextColor(selectedBlend);
108 |
109 | if (nextTitle instanceof TextView)
110 | ((TextView) nextTitle).setTextColor(nextBlend);
111 |
112 | } else if (mSelectionOffset == 0f) {
113 | if (selectedTitle instanceof TextView)
114 | ((TextView) selectedTitle).setTextColor(selectedColor);
115 | }
116 |
117 | invalidate();
118 | }
119 |
120 | void updateTitleViews() {
121 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
122 | ? mCustomTabColorizer
123 | : mDefaultTabColorizer;
124 |
125 | int id = getSlidingTabLayout().getTextViewId();
126 |
127 | for (int i = 0; i < getChildCount(); i++) {
128 | View v = getChildAt(i);
129 | View child = id == 0 ? v : v.findViewById(id);
130 |
131 | int color;
132 | if (mSelectedPosition != i)
133 | color = tabColorizer.getNormalTitleColor(i);
134 | else
135 | color = tabColorizer.getSelectedTitleColor(i);
136 |
137 | if (child instanceof TextView) {
138 | ((TextView) v).setTextColor(color);
139 | }
140 | }
141 |
142 | invalidate();
143 | }
144 |
145 | SlidingTabLayout getSlidingTabLayout() {
146 | ViewParent parent = getParent();
147 |
148 | if (parent instanceof SlidingTabLayout) {
149 | return (SlidingTabLayout) parent;
150 | } else if (parent == null) {
151 | return null;
152 | } else {
153 | throw new RuntimeException("The parent of a SlidingTabStrip must be a SlidingTabLayout");
154 | }
155 | }
156 |
157 | @Override
158 | protected void onDraw(Canvas canvas) {
159 | final int height = getHeight();
160 | final int childCount = getChildCount();
161 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
162 | ? mCustomTabColorizer
163 | : mDefaultTabColorizer;
164 |
165 | // Thick colored underline below the current selection
166 | if (childCount > 0) {
167 | View selectedTitle = getChildAt(mSelectedPosition);
168 | int left = selectedTitle.getLeft();
169 | int right = selectedTitle.getRight();
170 | int color = tabColorizer.getIndicatorColor(mSelectedPosition);
171 |
172 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
173 | int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
174 | if (color != nextColor) {
175 | color = blendColors(nextColor, color, mSelectionOffset);
176 | }
177 |
178 | // Draw the selection partway between the tabs
179 | View nextTitle = getChildAt(mSelectedPosition + 1);
180 | left = (int) (mSelectionOffset * nextTitle.getLeft() +
181 | (1.0f - mSelectionOffset) * left);
182 | right = (int) (mSelectionOffset * nextTitle.getRight() +
183 | (1.0f - mSelectionOffset) * right);
184 | }
185 |
186 | mSelectedIndicatorPaint.setColor(color);
187 |
188 | canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
189 | height, mSelectedIndicatorPaint);
190 | }
191 |
192 | // Thin underline along the entire bottom edge
193 | canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
194 | }
195 |
196 | /**
197 | * Set the alpha value of the {@code color} to be the given {@code alpha} value.
198 | */
199 | private static int setColorAlpha(int color, byte alpha) {
200 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
201 | }
202 |
203 | /**
204 | * Blend {@code color1} and {@code color2} using the given ratio.
205 | *
206 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
207 | * 0.0 will return {@code color2}.
208 | */
209 | private static int blendColors(int color1, int color2, float ratio) {
210 | final float inverseRation = 1f - ratio;
211 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
212 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
213 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
214 | return Color.rgb((int) r, (int) g, (int) b);
215 | }
216 |
217 | public static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
218 | private int[] mIndicatorColors;
219 | private int mColorPrimary;
220 |
221 | public SimpleTabColorizer(int colorPrimary) {
222 | mColorPrimary = colorPrimary;
223 | }
224 |
225 | @Override
226 | public int getIndicatorColor(int position) {
227 | return mIndicatorColors[position % mIndicatorColors.length];
228 | }
229 |
230 |
231 | @Override
232 | public int getSelectedTitleColor(int position) {
233 | return 0;
234 | }
235 |
236 | @Override
237 | public int getNormalTitleColor(int position) {
238 | return blendColors(getSelectedTitleColor(position), mColorPrimary, 0.6f);
239 | }
240 |
241 | void setIndicatorColors(int... colors) {
242 | mIndicatorColors = colors;
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/app/src/main/java/net/typeblog/git/widget/SlidingTabLayout.java:
--------------------------------------------------------------------------------
1 | package net.typeblog.git.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.Typeface;
5 | import android.graphics.drawable.Drawable;
6 | import android.os.Build;
7 | import android.support.v4.view.PagerAdapter;
8 | import android.support.v4.view.ViewPager;
9 | import android.util.AttributeSet;
10 | import android.util.SparseArray;
11 | import android.util.TypedValue;
12 | import android.view.Gravity;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.HorizontalScrollView;
17 | import android.widget.ImageView;
18 | import android.widget.LinearLayout;
19 | import android.widget.TextView;
20 |
21 | public class SlidingTabLayout extends HorizontalScrollView {
22 |
23 | public interface TabColorizer {
24 | /**
25 | * @return return the color of the indicator used when {@code position} is selected.
26 | */
27 | int getIndicatorColor(int position);
28 |
29 | int getSelectedTitleColor(int position);
30 | int getNormalTitleColor(int position);
31 | }
32 |
33 | private static final int TITLE_OFFSET_DIPS = 24;
34 | private static final int TAB_VIEW_PADDING_DIPS = 16;
35 | private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
36 |
37 | private int mTitleOffset;
38 |
39 | private int mTabViewLayoutId;
40 | private int mTabViewTextViewId;
41 | private boolean mDistributeEvenly;
42 | private boolean mTabStripPopulated = false;
43 |
44 | private ViewPager mViewPager;
45 | private SparseArray mContentDescriptions = new SparseArray();
46 | private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
47 |
48 | private final SlidingTabStrip mTabStrip;
49 |
50 | public SlidingTabLayout(Context context) {
51 | this(context, null);
52 | }
53 |
54 | public SlidingTabLayout(Context context, AttributeSet attrs) {
55 | this(context, attrs, 0);
56 | }
57 |
58 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
59 | super(context, attrs, defStyle);
60 |
61 | // Disable the Scroll Bar
62 | setHorizontalScrollBarEnabled(false);
63 | // Make sure that the Tab Strips fills this View
64 | setFillViewport(true);
65 |
66 | mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
67 |
68 | mTabStrip = new SlidingTabStrip(context);
69 | addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
70 | }
71 |
72 | public void setCustomTabColorizer(TabColorizer tabColorizer) {
73 | mTabStrip.setCustomTabColorizer(tabColorizer);
74 | }
75 |
76 | // Call this when contents in TabColorizer changed.
77 | public void notifyIndicatorColorChanged() {
78 | mTabStrip.updateTitleViews();
79 | }
80 |
81 | public void setDistributeEvenly(boolean distributeEvenly) {
82 | mDistributeEvenly = distributeEvenly;
83 | }
84 |
85 | public void setSelectedIndicatorColors(int... colors) {
86 | mTabStrip.setSelectedIndicatorColors(colors);
87 | }
88 |
89 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
90 | mViewPagerPageChangeListener = listener;
91 | }
92 |
93 | public void setCustomTabView(int layoutResId, int textViewId) {
94 | mTabViewLayoutId = layoutResId;
95 | mTabViewTextViewId = textViewId;
96 | }
97 |
98 | public void setViewPager(ViewPager viewPager) {
99 | mTabStrip.removeAllViews();
100 |
101 | mViewPager = viewPager;
102 | if (viewPager != null) {
103 | viewPager.setOnPageChangeListener(new InternalViewPagerListener());
104 | populateTabStrip();
105 | }
106 | }
107 |
108 | public void setViewPager(ViewPager viewPager, SlidingTabLayout layout) {
109 | mTabStrip.removeAllViews();
110 |
111 | mViewPager = viewPager;
112 | if (layout != null) {
113 | layout.setOnPageChangeListener(new InternalViewPagerListener());
114 | populateTabStrip();
115 | }
116 | }
117 |
118 | /**
119 | * Create a default view to be used for tabs. This is called if a custom tab view is not set via
120 | * {@link #setCustomTabView(int, int)}.
121 | */
122 | protected View createDefaultTabView(Context context) {
123 | View v;
124 | TextView textView = new TextView(context);
125 | textView.setGravity(Gravity.CENTER);
126 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
127 | textView.setTypeface(Typeface.DEFAULT_BOLD);
128 | textView.setAllCaps(true);
129 | textView.setLayoutParams(new LinearLayout.LayoutParams(
130 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
131 |
132 | v = textView;
133 |
134 | TypedValue outValue = new TypedValue();
135 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
136 | outValue, true);
137 | v.setBackgroundResource(outValue.resourceId);
138 |
139 | int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
140 | v.setPadding(padding, padding, padding, padding);
141 |
142 | return v;
143 | }
144 |
145 | private void populateTabStrip() {
146 | final PagerAdapter adapter = mViewPager.getAdapter();
147 | final View.OnClickListener tabClickListener = new TabClickListener();
148 |
149 | for (int i = 0; i < adapter.getCount(); i++) {
150 | View tabView = null;
151 | TextView tabTitleView = null;
152 | ImageView tabIconView = null;
153 |
154 | if (mTabViewLayoutId != 0) {
155 | // If there is a custom tab view layout id set, try and inflate it
156 | tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
157 | false);
158 | tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
159 | }
160 |
161 | if (tabView == null) {
162 | tabView = createDefaultTabView(getContext());
163 | }
164 |
165 | if (tabTitleView == null && TextView.class.isInstance(tabView)) {
166 | tabTitleView = (TextView) tabView;
167 | }
168 |
169 | if (mDistributeEvenly) {
170 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams();
171 | lp.width = 0;
172 | lp.weight = 1;
173 | }
174 |
175 | if (tabTitleView != null)
176 | tabTitleView.setText(adapter.getPageTitle(i));
177 |
178 | tabView.setOnClickListener(tabClickListener);
179 | String desc = mContentDescriptions.get(i, null);
180 | if (desc != null) {
181 | tabView.setContentDescription(desc);
182 | }
183 |
184 | mTabStrip.addView(tabView);
185 | if (i == mViewPager.getCurrentItem()) {
186 | tabView.setSelected(true);
187 | }
188 | }
189 |
190 | mTabStripPopulated = true;
191 | }
192 |
193 | public void setContentDescription(int i, String desc) {
194 | mContentDescriptions.put(i, desc);
195 | }
196 |
197 | @Override
198 | protected void onAttachedToWindow() {
199 | super.onAttachedToWindow();
200 |
201 | if (mViewPager != null) {
202 | scrollToTab(mViewPager.getCurrentItem(), 0);
203 | }
204 | }
205 |
206 | int getTextViewId() {
207 | return mTabViewTextViewId;
208 | }
209 |
210 | private void scrollToTab(int tabIndex, int positionOffset) {
211 | final int tabStripChildCount = mTabStrip.getChildCount();
212 | if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
213 | return;
214 | }
215 |
216 | View selectedChild = mTabStrip.getChildAt(tabIndex);
217 | if (selectedChild != null) {
218 | int targetScrollX = selectedChild.getLeft() + positionOffset;
219 |
220 | if (tabIndex > 0 || positionOffset > 0) {
221 | // If we're not at the first child and are mid-scroll, make sure we obey the offset
222 | targetScrollX -= mTitleOffset;
223 | }
224 |
225 | scrollTo(targetScrollX, 0);
226 | }
227 | }
228 |
229 | private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
230 | private int mScrollState;
231 |
232 | @Override
233 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
234 | int tabStripChildCount = mTabStrip.getChildCount();
235 | if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
236 | return;
237 | }
238 |
239 | mTabStrip.onViewPagerPageChanged(position, positionOffset);
240 |
241 | View selectedTitle = mTabStrip.getChildAt(position);
242 | int extraOffset = (selectedTitle != null)
243 | ? (int) (positionOffset * selectedTitle.getWidth())
244 | : 0;
245 | scrollToTab(position, extraOffset);
246 |
247 | if (mViewPagerPageChangeListener != null) {
248 | mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
249 | positionOffsetPixels);
250 | }
251 | }
252 |
253 | @Override
254 | public void onPageScrollStateChanged(int state) {
255 | mScrollState = state;
256 |
257 | if (mViewPagerPageChangeListener != null) {
258 | mViewPagerPageChangeListener.onPageScrollStateChanged(state);
259 | }
260 | }
261 |
262 | @Override
263 | public void onPageSelected(int position) {
264 | if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
265 | mTabStrip.onViewPagerPageChanged(position, 0f);
266 | scrollToTab(position, 0);
267 | }
268 | for (int i = 0; i < mTabStrip.getChildCount(); i++) {
269 | mTabStrip.getChildAt(i).setSelected(position == i);
270 | }
271 | if (mViewPagerPageChangeListener != null) {
272 | mViewPagerPageChangeListener.onPageSelected(position);
273 | }
274 | }
275 | }
276 |
277 | private class TabClickListener implements View.OnClickListener {
278 | @Override
279 | public void onClick(View v) {
280 | for (int i = 0; i < mTabStrip.getChildCount(); i++) {
281 | if (v == mTabStrip.getChildAt(i)) {
282 | mViewPager.setCurrentItem(i);
283 | return;
284 | }
285 | }
286 | }
287 | }
288 |
289 | }
290 |
--------------------------------------------------------------------------------
]