diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b2337c4..036e310e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,8 +11,11 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> + @@ -61,8 +64,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/java/org/mian/gitnex/activities/NewFileActivity.java b/app/src/main/java/org/mian/gitnex/activities/NewFileActivity.java new file mode 100644 index 00000000..0fd31d3a --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/activities/NewFileActivity.java @@ -0,0 +1,335 @@ +package org.mian.gitnex.activities; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import com.google.gson.JsonElement; +import org.mian.gitnex.R; +import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.helpers.AlertDialogs; +import org.mian.gitnex.helpers.Authorization; +import org.mian.gitnex.helpers.Toasty; +import org.mian.gitnex.models.Branches; +import org.mian.gitnex.models.NewFile; +import org.mian.gitnex.util.AppUtil; +import org.mian.gitnex.util.TinyDB; +import java.util.ArrayList; +import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; + +/** + * Author M M Arif + */ + +public class NewFileActivity extends AppCompatActivity { + + public ImageView closeActivity; + private View.OnClickListener onClickListener; + private Button newFileCreate; + + private EditText newFileName; + private EditText newFileContent; + private EditText newFileBranchName; + private EditText newFileCommitMessage; + private Spinner newFileBranchesSpinner; + final Context ctx = this; + + List branchesList = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_file); + + boolean connToInternet = AppUtil.haveNetworkConnection(getApplicationContext()); + + TinyDB tinyDb = new TinyDB(getApplicationContext()); + final String instanceUrl = tinyDb.getString("instanceUrl"); + final String loginUid = tinyDb.getString("loginUid"); + String repoFullName = tinyDb.getString("repoFullName"); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + + closeActivity = findViewById(R.id.close); + newFileName = findViewById(R.id.newFileName); + newFileContent = findViewById(R.id.newFileContent); + newFileBranchName = findViewById(R.id.newFileBranchName); + newFileCommitMessage = findViewById(R.id.newFileCommitMessage); + + initCloseListener(); + closeActivity.setOnClickListener(onClickListener); + + newFileCreate = findViewById(R.id.newFileCreate); + + initCloseListener(); + closeActivity.setOnClickListener(onClickListener); + + newFileBranchesSpinner = findViewById(R.id.newFileBranchesSpinner); + newFileBranchesSpinner.getBackground().setColorFilter(getResources().getColor(R.color.white), PorterDuff.Mode.SRC_ATOP); + getBranches(instanceUrl, instanceToken, repoOwner, repoName, loginUid); + + newFileBranchesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() + { + public void onItemSelected(AdapterView arg0, + View arg1, int arg2, long arg3) + { + Branches bModelValue = (Branches) newFileBranchesSpinner.getSelectedItem(); + Log.i("bModelSelected", bModelValue.toString()); + + if(bModelValue.toString().equals("No branch")) { + newFileBranchName.setEnabled(true); + } + else { + newFileBranchName.setEnabled(false); + newFileBranchName.setText(""); + } + + } + + public void onNothingSelected(AdapterView arg0) {} + }); + + disableProcessButton(); + + if(!connToInternet) { + + newFileCreate.setEnabled(false); + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius( 8 ); + shape.setColor(getResources().getColor(R.color.hintColor)); + newFileCreate.setBackground(shape); + + } else { + + newFileCreate.setOnClickListener(createFileListener); + + } + + } + + private View.OnClickListener createFileListener = new View.OnClickListener() { + public void onClick(View v) { + processNewFile(); + } + }; + + private void processNewFile() { + + boolean connToInternet = AppUtil.haveNetworkConnection(getApplicationContext()); + AppUtil appUtil = new AppUtil(); + TinyDB tinyDb = new TinyDB(getApplicationContext()); + final String instanceUrl = tinyDb.getString("instanceUrl"); + final String loginUid = tinyDb.getString("loginUid"); + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + String repoFullName = tinyDb.getString("repoFullName"); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + + String newFileName_ = newFileName.getText().toString(); + String newFileContent_ = newFileContent.getText().toString(); + String newFileBranchName_ = newFileBranchName.getText().toString(); + String newFileCommitMessage_ = newFileCommitMessage.getText().toString(); + + Branches currentBranch = (Branches) newFileBranchesSpinner.getSelectedItem(); + + if(!connToInternet) { + + Toasty.info(getApplicationContext(), getResources().getString(R.string.checkNetConnection)); + return; + + } + + if(newFileName_.equals("") || newFileContent_.equals("") || newFileCommitMessage_.equals("")) { + + Toasty.info(getApplicationContext(), getString(R.string.newFileRequiredFields)); + return; + + } + + if(currentBranch.toString().equals("No branch")) { + + if(newFileBranchName_.equals("")) { + Toasty.info(getApplicationContext(), getString(R.string.newFileRequiredFieldNewBranchName)); + return; + } + else { + if(!appUtil.checkStringsWithDash(newFileBranchName_)) { + + Toasty.info(getApplicationContext(), getString(R.string.newFileInvalidBranchName)); + return; + + } + } + + } + + if(appUtil.charactersLength(newFileCommitMessage_) > 255) { + + Toasty.info(getApplicationContext(), getString(R.string.newFileCommitMessageError)); + + } + else { + + disableProcessButton(); + createNewFile(instanceUrl, Authorization.returnAuthentication(getApplicationContext(), loginUid, instanceToken), repoOwner, repoName, newFileName_, appUtil.encodeBase64(newFileContent_), newFileBranchName_, newFileCommitMessage_, currentBranch.toString()); + + } + + } + + private void createNewFile(final String instanceUrl, final String token, String repoOwner, String repoName, String fileName, String fileContent, String fileBranchName, String fileCommitMessage, String currentBranch) { + + NewFile createNewFileJsonStr; + if(currentBranch.equals("No branch")) { + createNewFileJsonStr = new NewFile("", fileContent, fileCommitMessage, fileBranchName); + } + else { + createNewFileJsonStr = new NewFile(currentBranch, fileContent, fileCommitMessage, ""); + } + + Call call = RetrofitClient + .getInstance(instanceUrl) + .getApiInterface() + .createNewFile(token, repoOwner, repoName, fileName, createNewFileJsonStr); + + call.enqueue(new Callback() { + + @Override + public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { + + if(response.code() == 201) { + + enableProcessButton(); + Toasty.info(getApplicationContext(), getString(R.string.newFileSuccessMessage)); + finish(); + + } + else if(response.code() == 401) { + + enableProcessButton(); + AlertDialogs.authorizationTokenRevokedDialog(ctx, getResources().getString(R.string.alertDialogTokenRevokedTitle), + getResources().getString(R.string.alertDialogTokenRevokedMessage), + getResources().getString(R.string.alertDialogTokenRevokedCopyNegativeButton), + getResources().getString(R.string.alertDialogTokenRevokedCopyPositiveButton)); + + } + else { + + if(response.code() == 404) { + enableProcessButton(); + Toasty.info(getApplicationContext(), getString(R.string.apiNotFound)); + } + else { + enableProcessButton(); + Toasty.info(getApplicationContext(), getString(R.string.orgCreatedError)); + } + + } + + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e("onFailure", t.toString()); + enableProcessButton(); + } + }); + + } + + private void getBranches(String instanceUrl, String instanceToken, String repoOwner, String repoName, String loginUid) { + + Call> call = RetrofitClient + .getInstance(instanceUrl) + .getApiInterface() + .getBranches(Authorization.returnAuthentication(getApplicationContext(), loginUid, instanceToken), repoOwner, repoName); + + call.enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull retrofit2.Response> response) { + + if(response.isSuccessful()) { + if(response.code() == 200) { + + List branchesList_ = response.body(); + + branchesList.add(new Branches("No branch")); + assert branchesList_ != null; + if(branchesList_.size() > 0) { + for (int i = 0; i < branchesList_.size(); i++) { + + Branches data = new Branches( + branchesList_.get(i).getName() + ); + branchesList.add(data); + + } + } + + ArrayAdapter adapter = new ArrayAdapter<>(getApplicationContext(), + R.layout.spinner_item, branchesList); + + adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); + newFileBranchesSpinner.setAdapter(adapter); + enableProcessButton(); + + } + } + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + Log.e("onFailure", t.toString()); + } + }); + + } + + private void initCloseListener() { + onClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }; + } + + private void disableProcessButton() { + + newFileCreate.setEnabled(false); + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius( 8 ); + shape.setColor(getResources().getColor(R.color.hintColor)); + newFileCreate.setBackground(shape); + + } + + private void enableProcessButton() { + + newFileCreate.setEnabled(true); + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius( 8 ); + shape.setColor(getResources().getColor(R.color.btnBackground)); + newFileCreate.setBackground(shape); + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java index 02a227d3..0e845ef9 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -108,7 +108,6 @@ public class RepoDetailActivity extends AppCompatActivity implements RepoBottomS } } - @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); @@ -165,6 +164,9 @@ public class RepoDetailActivity extends AppCompatActivity implements RepoBottomS Intent i = new Intent(Intent.ACTION_VIEW, url); startActivity(i); break; + case "newFile": + startActivity(new Intent(RepoDetailActivity.this, NewFileActivity.class)); + break; } } diff --git a/app/src/main/java/org/mian/gitnex/activities/SponsorsActivity.java b/app/src/main/java/org/mian/gitnex/activities/SponsorsActivity.java index cb058a2f..8db47f6c 100644 --- a/app/src/main/java/org/mian/gitnex/activities/SponsorsActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/SponsorsActivity.java @@ -2,8 +2,10 @@ package org.mian.gitnex.activities; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; +import android.text.method.LinkMovementMethod; import android.view.View; import android.widget.ImageView; +import android.widget.TextView; import org.mian.gitnex.R; /** @@ -20,6 +22,9 @@ public class SponsorsActivity extends AppCompatActivity { setContentView(R.layout.activity_sponsors); ImageView closeActivity = findViewById(R.id.close); + TextView liberaPaySponsorsThomas = findViewById(R.id.liberaPaySponsorsThomas); + + liberaPaySponsorsThomas.setMovementMethod(LinkMovementMethod.getInstance()); initCloseListener(); closeActivity.setOnClickListener(onClickListener); diff --git a/app/src/main/java/org/mian/gitnex/adapters/BranchesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/BranchesAdapter.java index 2534aa65..d9b11557 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/BranchesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/BranchesAdapter.java @@ -1,12 +1,13 @@ package org.mian.gitnex.adapters; import android.content.Context; +import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.mian.gitnex.R; -import org.mian.gitnex.helpers.UrlHelper; import org.mian.gitnex.models.Branches; import org.mian.gitnex.util.TinyDB; import java.util.List; @@ -66,7 +67,9 @@ public class BranchesAdapter extends RecyclerView.Adapter" + mCtx.getResources().getString(R.string.commitLinkBranchesTab) + " ")); + holder.branchCommitHash.setMovementMethod(LinkMovementMethod.getInstance()); } diff --git a/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java index 51f9742c..e7d57a28 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ReleasesAdapter.java @@ -2,6 +2,8 @@ package org.mian.gitnex.adapters; import android.content.Context; import android.graphics.Color; +import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -139,8 +141,14 @@ public class ReleasesAdapter extends RecyclerView.Adapter" + mCtx.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + " ")); + holder.releaseZipDownload.setMovementMethod(LinkMovementMethod.getInstance()); + + holder.releaseTarDownload.setText( + Html.fromHtml("" + mCtx.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + " ")); + holder.releaseTarDownload.setMovementMethod(LinkMovementMethod.getInstance()); } diff --git a/app/src/main/java/org/mian/gitnex/fragments/RepoBottomSheetFragment.java b/app/src/main/java/org/mian/gitnex/fragments/RepoBottomSheetFragment.java index efa9ab81..c2866e1f 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/RepoBottomSheetFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/RepoBottomSheetFragment.java @@ -30,6 +30,7 @@ public class RepoBottomSheetFragment extends BottomSheetDialogFragment { TextView addCollaborator = v.findViewById(R.id.addCollaborator); TextView createRelease = v.findViewById(R.id.createRelease); TextView openWebRepo = v.findViewById(R.id.openWebRepo); + TextView newFile = v.findViewById(R.id.newFile); createLabel.setOnClickListener(new View.OnClickListener() { @Override @@ -79,6 +80,14 @@ public class RepoBottomSheetFragment extends BottomSheetDialogFragment { } }); + newFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + bmListener.onButtonClicked("newFile"); + dismiss(); + } + }); + return v; } diff --git a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java index 9fbbf13e..c78b196b 100644 --- a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java +++ b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java @@ -3,6 +3,7 @@ package org.mian.gitnex.interfaces; import com.google.gson.JsonElement; import org.mian.gitnex.models.AddEmail; import org.mian.gitnex.models.Branches; +import org.mian.gitnex.models.NewFile; import org.mian.gitnex.models.UpdateIssueAssignee; import org.mian.gitnex.models.UpdateIssueState; import org.mian.gitnex.models.Collaborators; @@ -210,4 +211,7 @@ public interface ApiInterface { @GET("repos/{owner}/{repo}/subscribers") // get all repo watchers Call> getRepoWatchers(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName); + + @POST("repos/{owner}/{repo}/contents/{file}") // create new file + Call createNewFile(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("file") String fileName, @Body NewFile jsonStr); } \ No newline at end of file diff --git a/app/src/main/java/org/mian/gitnex/models/NewFile.java b/app/src/main/java/org/mian/gitnex/models/NewFile.java new file mode 100644 index 00000000..64012eab --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/models/NewFile.java @@ -0,0 +1,99 @@ +package org.mian.gitnex.models; + +/** + * Author M M Arif + */ + +public class NewFile { + + private String branch; + private String content; + private String message; + private String new_branch; + + private authorObject author; + private committerObject committer; + + public String getBranch() { + return branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public String getContents() { + return content; + } + + public void setContents(String contents) { + this.content = contents; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getNew_branch() { + return new_branch; + } + + public void setNew_branch(String new_branch) { + this.new_branch = new_branch; + } + + public class authorObject { + + private String email; + private String name; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public class committerObject { + + private String email; + private String name; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public NewFile(String branch, String content, String message, String new_branch) { + this.branch = branch; + this.content = content; + this.message = message; + this.new_branch = new_branch; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mian/gitnex/util/AppUtil.java b/app/src/main/java/org/mian/gitnex/util/AppUtil.java index 9681077e..e9f86255 100644 --- a/app/src/main/java/org/mian/gitnex/util/AppUtil.java +++ b/app/src/main/java/org/mian/gitnex/util/AppUtil.java @@ -7,9 +7,11 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.util.Base64; import android.util.DisplayMetrics; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.Calendar; import java.util.Locale; @@ -77,6 +79,10 @@ public class AppUtil { return str.matches("^[\\w.-]+$"); } + public Boolean checkStringsWithDash(String str) { // [a-zA-Z0-9-_. ] + return str.matches("^[\\w-]+$"); + } + public Boolean checkIntegers(String str) { return str.matches("\\d+"); } @@ -180,4 +186,28 @@ public class AppUtil { } + public String encodeBase64(String str) { + + String base64Str = str; + if(!str.equals("")) { + byte[] data = str.getBytes(StandardCharsets.UTF_8); + base64Str = Base64.encodeToString(data, Base64.DEFAULT); + } + + return base64Str; + + } + + public String decodeBase64(String str) { + + String base64Str = str; + if(!str.equals("")) { + byte[] data = Base64.decode(base64Str, Base64.DEFAULT); + base64Str = new String(data, StandardCharsets.UTF_8); + } + + return base64Str; + + } + } diff --git a/app/src/main/res/drawable/ic_file.xml b/app/src/main/res/drawable/ic_file.xml new file mode 100644 index 00000000..a431a5e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_file.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 5a4a7c8e..15c882c4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -61,7 +61,6 @@ android:padding="10dp" android:textSize="14sp" tools:ignore="Autofill" - android:labelFor="@+id/instance_url" android:background="@drawable/shape_inputs" android:drawableStart="@drawable/ic_link_24dp" android:drawablePadding="10dp" @@ -79,7 +78,6 @@ android:padding="10dp" android:textSize="14sp" tools:ignore="Autofill" - android:labelFor="@+id/login_uid" android:background="@drawable/shape_inputs" android:drawableStart="@drawable/ic_person_24dp" android:drawablePadding="10dp" @@ -97,7 +95,6 @@ android:padding="10dp" android:textSize="14sp" tools:ignore="Autofill" - android:labelFor="@+id/login_passwd" android:background="@drawable/shape_inputs" android:drawableStart="@drawable/ic_lock_24dp" android:drawablePadding="10dp" @@ -115,7 +112,6 @@ android:padding="10dp" android:textSize="14sp" tools:ignore="Autofill" - android:labelFor="@+id/otpCode" android:background="@drawable/shape_inputs" android:drawableStart="@drawable/ic_otp" android:drawablePadding="10dp" diff --git a/app/src/main/res/layout/activity_new_file.xml b/app/src/main/res/layout/activity_new_file.xml new file mode 100644 index 00000000..97c65f48 --- /dev/null +++ b/app/src/main/res/layout/activity_new_file.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +