├── settings.gradle ├── .gitignore └── app ├── src └── main │ ├── res │ ├── drawable-hdpi │ │ ├── ic_launcher.png │ │ └── ic_add_white_24dp.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_add_white_24dp.png │ ├── drawable-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_add_white_24dp.png │ ├── layout │ │ ├── frame.xml │ │ ├── list.xml │ │ ├── text.xml │ │ ├── repo.xml │ │ ├── track_remote.xml │ │ ├── file_item.xml │ │ ├── branch_create.xml │ │ ├── main.xml │ │ ├── revert.xml │ │ ├── repo_item.xml │ │ ├── toolbar.xml │ │ ├── item_with_check_multiline.xml │ │ ├── item_with_radio.xml │ │ ├── add_remote.xml │ │ ├── set_committer.xml │ │ ├── commit.xml │ │ ├── commit_item.xml │ │ ├── push.xml │ │ └── add_repo.xml │ ├── values │ │ ├── styleable.xml │ │ ├── colors.xml │ │ ├── styles.xml │ │ └── strings.xml │ └── menu │ │ ├── home.xml │ │ ├── dialog.xml │ │ ├── action_mode_file.xml │ │ ├── action_mode_remote_branch.xml │ │ ├── action_mode_tags.xml │ │ ├── action_mode_commits.xml │ │ ├── action_mode_branch.xml │ │ └── repo.xml │ ├── java │ └── net │ │ └── typeblog │ │ └── git │ │ ├── support │ │ ├── GitProvider.java │ │ ├── GlobalContext.java │ │ ├── Utility.java │ │ └── RepoManager.java │ │ ├── PGitApplication.java │ │ ├── adapters │ │ ├── StringArrayAdapter.java │ │ ├── RefAdapter.java │ │ ├── FileAdapter.java │ │ ├── RepoAdapter.java │ │ ├── BaseListAdapter.java │ │ └── CommitAdapter.java │ │ ├── dialogs │ │ ├── BranchCreateDialog.java │ │ ├── TagCreateDialog.java │ │ ├── GitPushDialog.java │ │ ├── RemoteCheckoutDialog.java │ │ ├── GitPullDialog.java │ │ ├── RemoteAddDialog.java │ │ ├── BaseRefCreateDialog.java │ │ ├── ToolbarDialog.java │ │ ├── GitRevertDialog.java │ │ ├── RemoteTrackDialog.java │ │ ├── GitCommitDialog.java │ │ └── BasePullPushDialog.java │ │ ├── fragments │ │ ├── RemoteListFragment.java │ │ ├── RemoteBranchListFragment.java │ │ ├── BaseTextFragment.java │ │ ├── TagListFragment.java │ │ ├── GitStatusFragment.java │ │ ├── BranchListFragment.java │ │ ├── FileListFragment.java │ │ ├── BaseListFragment.java │ │ └── CommitListFragment.java │ │ ├── widget │ │ ├── CheckableRelativeLayout.java │ │ ├── FABImageView.java │ │ ├── CheckableWrapperRelativeLayout.java │ │ ├── SlidingTabStrip.java │ │ └── SlidingTabLayout.java │ │ ├── tasks │ │ └── GitTask.java │ │ └── activities │ │ ├── ToolbarActivity.java │ │ ├── RepoActivity.java │ │ └── HomeActivity.java │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | obj/ 4 | libs/*/*.so 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterCxy/PGit/HEAD/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/layout/frame.xml: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styleable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/support/GitProvider.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.support; 2 | 3 | import org.eclipse.jgit.api.Git; 4 | 5 | public interface GitProvider 6 | { 7 | public Git git(); 8 | public String getLocation(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/dialog.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_mode_file.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_mode_remote_branch.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/PGitApplication.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git; 2 | 3 | import android.app.Application; 4 | 5 | import net.typeblog.git.support.GlobalContext; 6 | 7 | public class PGitApplication extends Application 8 | { 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | GlobalContext.set(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #BBBBBB 5 | #009688 6 | #00796b 7 | #ffc107 8 | #33FFFFFF 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/support/GlobalContext.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.support; 2 | 3 | import android.content.Context; 4 | 5 | public class GlobalContext 6 | { 7 | private static Context mContext; 8 | 9 | public static void set(Context context) { 10 | mContext = context.getApplicationContext(); 11 | } 12 | 13 | public static Context get() { 14 | return mContext; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_mode_tags.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/text.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/repo.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_mode_commits.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/track_remote.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/file_item.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_mode_branch.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/StringArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import java.util.List; 7 | 8 | import net.typeblog.git.R; 9 | import static net.typeblog.git.support.Utility.*; 10 | 11 | public class StringArrayAdapter extends BaseListAdapter 12 | { 13 | public StringArrayAdapter(List list) { 14 | super(list); 15 | } 16 | 17 | @Override 18 | protected int getLayoutResource() { 19 | return R.layout.file_item; 20 | } 21 | 22 | @Override 23 | protected void bindView(View v, String item) { 24 | TextView text = $(v, R.id.file_name); 25 | text.setText(item); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/RefAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import org.eclipse.jgit.lib.Ref; 7 | 8 | import java.util.List; 9 | 10 | import net.typeblog.git.R; 11 | import static net.typeblog.git.support.Utility.*; 12 | 13 | public class RefAdapter extends BaseListAdapter 14 | { 15 | public RefAdapter(List list) { 16 | super(list); 17 | } 18 | 19 | @Override 20 | protected int getLayoutResource() { 21 | // TODO: Do not share the same layout with FileAdapter 22 | return R.layout.file_item; 23 | } 24 | 25 | @Override 26 | protected void bindView(View v, Ref item) { 27 | TextView t = $(v, R.id.file_name); 28 | t.setText(item.getName()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/BranchCreateDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | 5 | import org.eclipse.jgit.api.errors.GitAPIException; 6 | 7 | import net.typeblog.git.R; 8 | import net.typeblog.git.support.GitProvider; 9 | import static net.typeblog.git.support.Utility.*; 10 | 11 | public class BranchCreateDialog extends BaseRefCreateDialog 12 | { 13 | public BranchCreateDialog(Context context, GitProvider provider) { 14 | super(context, provider); 15 | } 16 | 17 | @Override 18 | protected void doCreate(String name) throws GitAPIException, RuntimeException { 19 | mProvider.git().branchCreate() 20 | .setName(name) 21 | .call(); 22 | } 23 | 24 | @Override 25 | protected int getTitle() { 26 | return R.string.git_branch_create; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/branch_create.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/TagCreateDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | 5 | import org.eclipse.jgit.api.errors.GitAPIException; 6 | 7 | import net.typeblog.git.R; 8 | import net.typeblog.git.support.GitProvider; 9 | import net.typeblog.git.support.RepoManager; 10 | 11 | public class TagCreateDialog extends BaseRefCreateDialog 12 | { 13 | public TagCreateDialog(Context context, GitProvider provider) { 14 | super(context, provider); 15 | } 16 | 17 | @Override 18 | protected int getTitle() { 19 | return R.string.git_tag_create; 20 | } 21 | 22 | @Override 23 | protected void doCreate(String name) throws GitAPIException, RuntimeException { 24 | mProvider.git().tag() 25 | .setName(name) 26 | .setTagger(RepoManager.getInstance().getCommitterIdent()) 27 | .call(); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 8 | 9 | 14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/revert.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.1.0" 6 | 7 | defaultConfig { 8 | applicationId "net.typeblog.git" 9 | minSdkVersion 14 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'org.eclipse.jgit:org.eclipse.jgit:3.6.+' 24 | //compile 'com.android.support:appcompat-v7:22.1.0' // Included in MaterialEditText 25 | compile 'com.rengwuxian.materialedittext:library:2.1.3' 26 | compile 'com.android.support:support-v13:22.1.0' 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/fragments/RemoteListFragment.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.fragments; 2 | 3 | import android.app.Activity; 4 | 5 | import java.util.List; 6 | 7 | import net.typeblog.git.adapters.StringArrayAdapter; 8 | import net.typeblog.git.support.GitProvider; 9 | 10 | public class RemoteListFragment extends BaseListFragment 11 | { 12 | private GitProvider mProvider; 13 | 14 | @Override 15 | public void onAttach(Activity activity) { 16 | super.onAttach(activity); 17 | mProvider = (GitProvider) activity; 18 | } 19 | 20 | @Override 21 | protected StringArrayAdapter createAdapter() { 22 | return new StringArrayAdapter(mItemList); 23 | } 24 | 25 | @Override 26 | protected void onViewInflated() { 27 | 28 | } 29 | 30 | @Override 31 | protected void doLoad(List list) { 32 | list.addAll(mProvider.git().getRepository().getRemoteNames()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/FileAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import java.io.File; 8 | import java.util.List; 9 | 10 | import net.typeblog.git.R; 11 | import net.typeblog.git.support.GlobalContext; 12 | import static net.typeblog.git.support.Utility.*; 13 | 14 | public class FileAdapter extends BaseListAdapter 15 | { 16 | public FileAdapter(List list) { 17 | super(list); 18 | } 19 | 20 | @Override 21 | protected int getLayoutResource() { 22 | return R.layout.file_item; 23 | } 24 | 25 | @Override 26 | protected void bindView(View v, File f) { 27 | TextView name = $(v, R.id.file_name); 28 | 29 | name.setText(f.getName()); 30 | name.setTextColor(GlobalContext.get().getResources().getColor(f.isDirectory() ? R.color.color_primary_dark : R.color.white)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/menu/repo.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/repo_item.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_with_check_multiline.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_with_radio.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/fragments/RemoteBranchListFragment.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.fragments; 2 | 3 | import org.eclipse.jgit.api.ListBranchCommand; 4 | 5 | import java.util.List; 6 | 7 | import net.typeblog.git.R; 8 | import net.typeblog.git.dialogs.RemoteCheckoutDialog; 9 | 10 | public class RemoteBranchListFragment extends BranchListFragment 11 | { 12 | 13 | @Override 14 | protected boolean shouldEnterActionMode(int pos) { 15 | return true; 16 | } 17 | 18 | @Override 19 | protected int getActionModeMenu() { 20 | return R.menu.action_mode_remote_branch; 21 | } 22 | 23 | @Override 24 | protected boolean onActionModeItemSelected(int id) { 25 | String name = mItemList.get(mList.getCheckedItemPosition()).getName(); 26 | 27 | switch (id) { 28 | case R.id.checkout: 29 | new RemoteCheckoutDialog(getActivity(), mProvider, name).show(); 30 | return true; 31 | default: 32 | return false; 33 | } 34 | } 35 | 36 | @Override 37 | protected boolean multiChoice() { 38 | return false; 39 | } 40 | 41 | @Override 42 | protected ListBranchCommand.ListMode getListMode() { 43 | return ListBranchCommand.ListMode.REMOTE; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/widget/CheckableRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.util.AttributeSet; 6 | import android.widget.Checkable; 7 | import android.widget.RelativeLayout; 8 | 9 | import net.typeblog.git.R; 10 | 11 | public class CheckableRelativeLayout extends RelativeLayout implements Checkable 12 | { 13 | private boolean mIsChecked = false; 14 | 15 | public CheckableRelativeLayout(Context context) { 16 | this(context, null); 17 | } 18 | 19 | public CheckableRelativeLayout(Context context, AttributeSet attrs) { 20 | this(context, attrs, 0); 21 | } 22 | 23 | public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle) { 24 | super(context, attrs, defStyle); 25 | } 26 | 27 | @Override 28 | public void setChecked(boolean checked) { 29 | setBackgroundDrawable(checked ? new ColorDrawable(getResources().getColor(R.color.selector_white)) : null); 30 | } 31 | 32 | @Override 33 | public boolean isChecked() { 34 | return mIsChecked; 35 | } 36 | 37 | @Override 38 | public void toggle() { 39 | setChecked(!isChecked()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/GitPushDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | 5 | import org.eclipse.jgit.api.errors.GitAPIException; 6 | import org.eclipse.jgit.lib.ProgressMonitor; 7 | import org.eclipse.jgit.transport.CredentialsProvider; 8 | 9 | import net.typeblog.git.R; 10 | import net.typeblog.git.support.GitProvider; 11 | import net.typeblog.git.support.RepoManager; 12 | import static net.typeblog.git.support.Utility.*; 13 | 14 | public class GitPushDialog extends BasePullPushDialog 15 | { 16 | public GitPushDialog(Context context, GitProvider provider, String ref) { 17 | super(context, provider, ref); 18 | } 19 | 20 | @Override 21 | protected void onInitView() { 22 | setTitle(R.string.git_push); 23 | super.onInitView(); 24 | } 25 | 26 | @Override 27 | protected void doTask(ProgressMonitor monitor, String remote, String ref, CredentialsProvider authorization, boolean force) throws GitAPIException, RuntimeException { 28 | mProvider.git().push() 29 | .setRemote(remote) 30 | .add(ref) 31 | .setCredentialsProvider(authorization) 32 | //.setPushTags() 33 | .setForce(force) 34 | .setProgressMonitor(monitor) 35 | .call(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/RemoteCheckoutDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | 5 | import org.eclipse.jgit.api.CreateBranchCommand; 6 | import org.eclipse.jgit.api.errors.GitAPIException; 7 | 8 | import net.typeblog.git.R; 9 | import net.typeblog.git.support.GitProvider; 10 | import static net.typeblog.git.support.Utility.*; 11 | 12 | public class RemoteCheckoutDialog extends BaseRefCreateDialog 13 | { 14 | private String mName; 15 | 16 | public RemoteCheckoutDialog(Context context, GitProvider provider, String name) { 17 | super(context, provider); 18 | mName = name; 19 | } 20 | 21 | @Override 22 | protected void onInitView() { 23 | super.onInitView(); 24 | 25 | mText.setText(mName.substring(mName.lastIndexOf("/") + 1, mName.length())); 26 | } 27 | 28 | @Override 29 | protected int getTitle() { 30 | return R.string.git_checkout_remote; 31 | } 32 | 33 | @Override 34 | protected void doCreate(String name) throws GitAPIException, RuntimeException { 35 | mProvider.git().checkout() 36 | .setCreateBranch(true) 37 | .setName(name) 38 | .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) 39 | .setStartPoint(mName) 40 | .call(); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/GitPullDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import org.eclipse.jgit.api.errors.GitAPIException; 7 | import org.eclipse.jgit.lib.ProgressMonitor; 8 | import org.eclipse.jgit.transport.CredentialsProvider; 9 | 10 | import java.io.IOException; 11 | 12 | import net.typeblog.git.R; 13 | import net.typeblog.git.support.GitProvider; 14 | import net.typeblog.git.support.RepoManager; 15 | import static net.typeblog.git.support.Utility.*; 16 | 17 | public class GitPullDialog extends BasePullPushDialog 18 | { 19 | public GitPullDialog(Context context, GitProvider provider) { 20 | super(context, provider, null); 21 | } 22 | 23 | @Override 24 | protected void onInitView() { 25 | setTitle(R.string.git_pull); 26 | super.onInitView(); 27 | 28 | // Disable force for pull (for now) 29 | mForce.setVisibility(View.GONE); 30 | } 31 | 32 | @Override 33 | protected void doTask(ProgressMonitor monitor, String remote, String ref, CredentialsProvider authorization, boolean force) throws GitAPIException, RuntimeException { 34 | mProvider.git().pull() 35 | .setRemote(remote) 36 | .setCredentialsProvider(authorization) 37 | .setProgressMonitor(monitor) 38 | .call(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/add_remote.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 22 | 23 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/fragments/BaseTextFragment.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.fragments; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import net.typeblog.git.R; 13 | import static net.typeblog.git.support.Utility.*; 14 | 15 | public abstract class BaseTextFragment extends Fragment 16 | { 17 | protected TextView mText; 18 | 19 | protected abstract String doLoad(); 20 | 21 | @Override 22 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 23 | View v = inflater.inflate(R.layout.text, container, false); 24 | 25 | mText = $(v, R.id.text); 26 | 27 | reload(); 28 | 29 | return v; 30 | } 31 | 32 | public void reload() { 33 | new LoaderTask().execute(); 34 | } 35 | 36 | private class LoaderTask extends AsyncTask { 37 | 38 | @Override 39 | protected String doInBackground(Void... params) { 40 | return doLoad(); 41 | } 42 | 43 | @Override 44 | protected void onPostExecute(String result) { 45 | super.onPostExecute(result); 46 | 47 | mText.setText(result); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/RepoAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import net.typeblog.git.R; 12 | import net.typeblog.git.support.GlobalContext; 13 | import static net.typeblog.git.support.Utility.*; 14 | 15 | public class RepoAdapter extends BaseListAdapter 16 | { 17 | private List mUrlList; 18 | 19 | public RepoAdapter(List repoList, List urlList) { 20 | super(repoList); 21 | mUrlList = urlList; 22 | } 23 | 24 | @Override 25 | public void notifyDataSetChanged() { 26 | 27 | if (mUrlList.size() != mList.size()) { 28 | throw new IllegalStateException("Size not match"); 29 | } 30 | 31 | super.notifyDataSetChanged(); 32 | } 33 | 34 | @Override 35 | protected void bindView(View v, String item) { 36 | TextView name = $(v, R.id.name); 37 | TextView url = $(v, R.id.url); 38 | 39 | name.setText(item); 40 | url.setText(mUrlList.get(mList.indexOf(item))); 41 | 42 | if (url.getText().toString().trim().equals("")) { 43 | url.setText(GlobalContext.get().getString(R.string.nothing)); 44 | } 45 | } 46 | 47 | @Override 48 | protected int getLayoutResource() { 49 | return R.layout.repo_item; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/set_committer.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 23 | 24 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/BaseListAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | 9 | import java.util.List; 10 | 11 | import net.typeblog.git.support.GlobalContext; 12 | 13 | public abstract class BaseListAdapter extends BaseAdapter 14 | { 15 | protected abstract int getLayoutResource(); 16 | protected abstract void bindView(View v, T item); 17 | 18 | protected List mList; 19 | private LayoutInflater mInflater; 20 | 21 | public BaseListAdapter(List list) { 22 | mList = list; 23 | mInflater = (LayoutInflater) GlobalContext.get().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 24 | } 25 | 26 | 27 | @Override 28 | public int getCount() { 29 | return mList.size(); 30 | } 31 | 32 | @Override 33 | public Object getItem(int pos) { 34 | return mList.get(pos); 35 | } 36 | 37 | @Override 38 | public long getItemId(int pos) { 39 | return pos; 40 | } 41 | 42 | @Override 43 | public View getView(int position, View convertView, ViewGroup container) { 44 | if (position >= getCount()) return convertView; 45 | 46 | View v = convertView; 47 | if (v == null) { 48 | v = mInflater.inflate(getLayoutResource(), container, false); 49 | } 50 | 51 | bindView(v, mList.get(position)); 52 | 53 | return v; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/dialogs/RemoteAddDialog.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.dialogs; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | import org.eclipse.jgit.lib.StoredConfig; 7 | 8 | import java.io.IOException; 9 | 10 | import net.typeblog.git.R; 11 | import net.typeblog.git.support.GitProvider; 12 | import static net.typeblog.git.support.Utility.*; 13 | 14 | public class RemoteAddDialog extends ToolbarDialog 15 | { 16 | private GitProvider mProvider; 17 | private TextView mName, mUrl; 18 | 19 | public RemoteAddDialog(Context context, GitProvider provider) { 20 | super(context); 21 | mProvider = provider; 22 | } 23 | 24 | @Override 25 | protected int getLayoutResource() { 26 | return R.layout.add_remote; 27 | } 28 | 29 | @Override 30 | protected void onInitView() { 31 | setTitle(R.string.add_remote); 32 | 33 | mName = $(this, R.id.remote_name); 34 | mUrl = $(this, R.id.remote_url); 35 | } 36 | 37 | @Override 38 | protected void onConfirm() { 39 | StoredConfig conf = mProvider.git().getRepository().getConfig(); 40 | String name = mName.getText().toString().trim(); 41 | 42 | if (name.equals("")) return; 43 | 44 | conf.setString("remote", name, "url", mUrl.getText().toString().trim()); 45 | conf.setString("remote", name, "fetch", "+refs/heads/*:refs/remotes/" + name + "/*"); 46 | try { 47 | conf.save(); 48 | } catch (IOException e) { 49 | 50 | } 51 | dismiss(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/widget/FABImageView.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.ColorStateList; 5 | import android.graphics.Color; 6 | import android.graphics.Outline; 7 | import android.graphics.drawable.Drawable; 8 | import android.graphics.drawable.RippleDrawable; 9 | import android.os.Build; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.ViewOutlineProvider; 13 | import android.widget.ImageView; 14 | 15 | public class FABImageView extends ImageView 16 | { 17 | private Drawable mBackground; 18 | 19 | public FABImageView(Context context) { 20 | this(context, null); 21 | } 22 | 23 | public FABImageView(Context context, AttributeSet attrs) { 24 | this(context, attrs, 0); 25 | } 26 | 27 | public FABImageView(Context context, AttributeSet attrs, int defStyle) { 28 | super(context, attrs, defStyle); 29 | setClickable(true); 30 | 31 | if (Build.VERSION.SDK_INT >= 21) { 32 | setElevation(10.4f); 33 | } 34 | 35 | setOutlineProvider(new ViewOutlineProvider() { 36 | @Override 37 | public void getOutline(View view, Outline outline) { 38 | outline.setOval(0, 0, getWidth(), getHeight()); 39 | } 40 | }); 41 | setClipToOutline(true); 42 | 43 | mBackground = getBackground(); 44 | setBackgroundDrawable(new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{Color.WHITE}), mBackground, null)); 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/net/typeblog/git/adapters/CommitAdapter.java: -------------------------------------------------------------------------------- 1 | package net.typeblog.git.adapters; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import java.text.SimpleDateFormat; 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | import org.eclipse.jgit.lib.ObjectId; 13 | import org.eclipse.jgit.revwalk.RevCommit; 14 | 15 | import net.typeblog.git.R; 16 | import net.typeblog.git.support.GlobalContext; 17 | import static net.typeblog.git.support.Utility.*; 18 | 19 | public class CommitAdapter extends BaseListAdapter 20 | { 21 | public CommitAdapter(List 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 | --------------------------------------------------------------------------------