Commit 636fe2ae authored by M M Arif's avatar M M Arif

Merge branch '14-issue-comments' into 'develop'

Resolve "[New] Comment on issue and show its comments"

Closes #14

See merge request mmarif4u/gitnex!13
parents 5c5d24cc c386cc26
......@@ -43,6 +43,7 @@ Option 2 - Open terminal(Linux) and cd to the project dir. Run `./gradlew build`
11- Login/Logout
12- Issue comments with rendered markdown support
13- Emoji support
14- Comment on issues
More to come...
## Screenshots:
......
......@@ -6,8 +6,8 @@ android {
applicationId "org.mian.gitnex"
minSdkVersion 21
targetSdkVersion 28
versionCode 6
versionName "pre-alpha4"
versionCode 7
versionName "pre-alpha5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
......@@ -16,6 +16,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
//checkReleaseBuilds false
abortOnError false
}
}
dependencies {
......@@ -40,7 +44,7 @@ dependencies {
implementation 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
implementation "ru.noties:markwon:2.0.1"
implementation "ru.noties:markwon-image-loader:2.0.1"
implementation 'com.vdurmont:emoji-java:4.0.0'
implementation "com.vdurmont:emoji-java:4.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
......
......@@ -12,7 +12,8 @@
android:roundIcon="@mipmap/app_logo_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activities.IssueDetailActivity"></activity>
<activity android:name=".activities.ReplyToIssueActivity"></activity>
<activity android:name=".activities.IssueDetailActivity" />
<activity
android:name=".activities.RepoDetailActivity"
android:label="@string/title_activity_repo_detail"
......
......@@ -15,6 +15,7 @@ import retrofit2.Response;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.il.AsyncDrawableLoader;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
......@@ -22,9 +23,12 @@ import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.squareup.picasso.Picasso;
......@@ -64,6 +68,8 @@ public class IssueDetailActivity extends AppCompatActivity {
private TextView issueCreatedTime;
private HorizontalScrollView labelsScrollView;
private HorizontalScrollView assigneesScrollView;
private ImageView replyToIssue;
private ScrollView scrollViewComments;
@Override
protected void onCreate(Bundle savedInstanceState) {
......@@ -71,7 +77,7 @@ public class IssueDetailActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_issue_detail);
TinyDB tinyDb = new TinyDB(getApplicationContext());
final TinyDB tinyDb = new TinyDB(getApplicationContext());
final String instanceUrl = tinyDb.getString("instanceUrl");
final String loginUid = tinyDb.getString("loginUid");
......@@ -94,6 +100,8 @@ public class IssueDetailActivity extends AppCompatActivity {
issueCreatedTime = findViewById(R.id.issueCreatedTime);
labelsScrollView = findViewById(R.id.labelsScrollView);
assigneesScrollView = findViewById(R.id.assigneesScrollView);
replyToIssue = findViewById(R.id.replyToIssue);
scrollViewComments = findViewById(R.id.scrollViewComments);
toolbarTitle.setText(repoName);
initCloseListener();
......@@ -101,6 +109,7 @@ public class IssueDetailActivity extends AppCompatActivity {
mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setNestedScrollingEnabled(false);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(),
......@@ -120,11 +129,62 @@ public class IssueDetailActivity extends AppCompatActivity {
}
});
replyToIssue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tinyDb.putString("issueTitle", issueTitle.getText().toString());
Intent intent = new Intent(view.getContext(), ReplyToIssueActivity.class);
startActivity(intent);
}
});
scrollViewComments.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
int scrollX = scrollViewComments.getScrollX(); // horizontal
int scrollY = scrollViewComments.getScrollY(); // vertical
if(scrollY > 500) {
replyToIssue.setVisibility(View.GONE);
}
else {
replyToIssue.setVisibility(View.VISIBLE);
}
}
});
getSingleIssue(instanceUrl, instanceToken, repoOwner, repoName, issueIndex);
fetchDataAsync(instanceUrl, instanceToken, repoOwner, repoName, issueIndex);
}
@Override
public void onResume() {
super.onResume();
final 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");
final int issueIndex = Integer.parseInt(tinyDb.getString("issueNumber"));
if(tinyDb.getBoolean("commentPosted")) {
scrollViewComments.post(new Runnable() {
@Override
public void run() {
IssueCommentsViewModel.loadIssueComments(instanceUrl, instanceToken, repoOwner, repoName, issueIndex);
scrollViewComments.fullScroll(ScrollView.FOCUS_DOWN);
tinyDb.putBoolean("commentPosted", false);
}
});
}
}
private void fetchDataAsync(String instanceUrl, String instanceToken, String owner, String repo, int index) {
IssueCommentsViewModel issueCommentsModel = ViewModelProviders.of(this).get(IssueCommentsViewModel.class);
......@@ -169,6 +229,8 @@ public class IssueDetailActivity extends AppCompatActivity {
final CharSequence bodyWithMD = Markwon.markdown(mdConfiguration, EmojiParser.parseToUnicode(singleIssue.getBody()));
issueDescription.setText(bodyWithMD);
RelativeLayout.LayoutParams paramsDesc = (RelativeLayout.LayoutParams)issueDescription.getLayoutParams();
LinearLayout assigneesLayout = findViewById(R.id.frameAssignees);
LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(80, 80);
params1.setMargins(15, 0, 0, 0);
......@@ -244,9 +306,27 @@ public class IssueDetailActivity extends AppCompatActivity {
}
else {
issueDueDate.setVisibility(View.GONE);
}
if((singleIssue.getDue_date() == null && singleIssue.getMilestone() == null) && singleIssue.getAssignees() != null) {
paramsDesc.setMargins(0, 35, 0, 0);
issueDescription.setLayoutParams(paramsDesc);
}
else if(singleIssue.getDue_date() == null && singleIssue.getMilestone() == null) {
paramsDesc.setMargins(0, 55, 0, 0);
issueDescription.setLayoutParams(paramsDesc);
}
else if(singleIssue.getAssignees() == null) {
paramsDesc.setMargins(0, 35, 0, 0);
issueDescription.setLayoutParams(paramsDesc);
}
else {
paramsDesc.setMargins(0, 15, 0, 0);
issueDescription.setLayoutParams(paramsDesc);
}
switch (timeFormat) {
case "pretty": {
PrettyTime prettyTime = new PrettyTime(new Locale(locale));
......
package org.mian.gitnex.activities;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.mian.gitnex.R;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.models.Issues;
import org.mian.gitnex.util.TinyDB;
public class ReplyToIssueActivity extends AppCompatActivity {
public ImageView closeActivity;
private View.OnClickListener onClickListener;
private EditText newReplyToIssue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reply_to_issue);
TinyDB tinyDb = new TinyDB(getApplicationContext());
closeActivity = findViewById(R.id.close);
newReplyToIssue = findViewById(R.id.newReplyToIssue);
TextView toolbar_title = findViewById(R.id.toolbar_title);
if(!tinyDb.getString("issueTitle").isEmpty()) {
toolbar_title.setText(tinyDb.getString("issueTitle"));
}
initCloseListener();
closeActivity.setOnClickListener(onClickListener);
Button replyButton = findViewById(R.id.replyButton);
replyButton.setOnClickListener(replyToIssue);
}
private void initCloseListener() {
onClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
};
}
private View.OnClickListener replyToIssue = new View.OnClickListener() {
public void onClick(View v) {
processNewCommentReply();
}
};
private void processNewCommentReply() {
String newReplyDT = newReplyToIssue.getText().toString();
if(newReplyDT.equals("")) {
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
(ViewGroup) findViewById(R.id.custom_toast_container));
TextView text = layout.findViewById(R.id.toastText);
text.setText(R.string.commentEmptyError);
Toast toast = new Toast(getApplicationContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
else {
replyComment(newReplyDT);
}
}
private void replyComment(String newReplyDT) {
final 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];
final int issueIndex = Integer.parseInt(tinyDb.getString("issueNumber"));
Issues issueComment = new Issues(newReplyDT);
Call<Issues> call = RetrofitClient
.getInstance(instanceUrl)
.getApiInterface()
.replyCommentToIssue(instanceToken, repoOwner, repoName, issueIndex, issueComment);
call.enqueue(new Callback<Issues>() {
@Override
public void onResponse(@NonNull Call<Issues> call, @NonNull retrofit2.Response<Issues> response) {
if(response.isSuccessful()) {
if(response.code() == 201) {
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
(ViewGroup) findViewById(R.id.custom_toast_container));
TextView text = layout.findViewById(R.id.toastText);
text.setText(R.string.commentSuccess);
Toast toast = new Toast(getApplicationContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
tinyDb.putBoolean("commentPosted", true);
finish();
}
}
else {
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
(ViewGroup) findViewById(R.id.custom_toast_container));
TextView text = layout.findViewById(R.id.toastText);
text.setText(R.string.commentError);
Toast toast = new Toast(getApplicationContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
}
@Override
public void onFailure(@NonNull Call<Issues> call, @NonNull Throwable t) {
Log.e("onFailure", t.toString());
}
});
}
}
......@@ -64,4 +64,7 @@ public interface ApiInterface {
@GET("repos/{owner}/{repo}/issues/{index}/comments") // get issue comments
Call<List<IssueComments>> getIssueComments(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex);
@POST("repos/{owner}/{repo}/issues/{index}/comments") // reply to issue
Call<Issues> replyCommentToIssue(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex, @Body Issues jsonStr);
}
......@@ -24,6 +24,10 @@ public class Issues {
private assigneeObject assignee;
private List<assigneesObject> assignees;
public Issues(String body) {
this.body = body;
}
public class userObject {
private int id;
......
<vector android:height="24dp" android:tint="#CFCFCF"
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
......
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/colorPrimary">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
tools:ignore="UnusedAttribute">
<ImageView
android:id="@+id/close"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="15dp"
android:layout_marginLeft="15dp"
android:gravity="center_vertical"
android:contentDescription="@string/close"
android:src="@drawable/ic_close" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textStyle="bold"
android:textColor="@color/white"
android:maxLines="1"
android:textSize="18sp" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/backgroundColor">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:paddingBottom="30dp"
android:orientation="vertical">
<EditText
android:id="@+id/newReplyToIssue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/shape_inputs"
android:maxLines="12"
android:minLines="10"
tools:ignore="Autofill"
android:labelFor="@+id/newReplyToIssue"
android:scrollbars="vertical"
android:gravity="top|start"
android:textSize="16sp"
android:textColor="@color/colorWhite"
android:textColorHint="@color/colorWhite"
android:inputType="textMultiLine"
android:textColorHighlight="@color/colorWhite"/>
<Button
android:id="@+id/replyButton"
android:gravity="center"
android:layout_gravity="end"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="@string/commentButtonText"
android:background="@drawable/shape_buttons"
android:textColor="@color/btnTextColor" />
</LinearLayout>
</ScrollView>
</LinearLayout>
......@@ -136,6 +136,10 @@
<string name="dueDate">Due %1$s</string>
<string name="createdTime">Created %1$s</string>
<string name="assignedTo">Assigned to : %1$s</string>
<string name="commentButtonText">Comment</string>
<string name="commentEmptyError">Please write your comment!</string>
<string name="commentSuccess">Comment posted</string>
<string name="commentError">Something went wrong, please try again</string>
<string name="generalImgContentText">IMG</string>
<string name="generalPageTitle">GitNex</string>
......
......@@ -146,6 +146,10 @@
<string name="dueDate">Due %1$s</string>
<string name="createdTime">Created %1$s</string>
<string name="assignedTo">Assigned to : %1$s</string>
<string name="commentButtonText">Comment</string>
<string name="commentEmptyError">Please write your comment!</string>
<string name="commentSuccess">Comment posted</string>
<string name="commentError">Something went wrong, please try again</string>
<string name="generalImgContentText">Avatar</string>
<string name="generalPageTitle">GitNex</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment