From ade7b797f15e888114b017e68035bf9b44be72a9 Mon Sep 17 00:00:00 2001
From: opyale <opyale@noreply.codeberg.org>
Date: Sun, 8 Nov 2020 19:58:47 +0100
Subject: [PATCH] Add Issue/Comment Reactions (#557)

Minor performance improvements.

Merge branch 'master' of https://codeberg.org/gitnex/GitNex into issue-reactions

Improving color of selected elements.

First, fully working implementation of reactions.

Merge branch 'master' of https://codeberg.org/gitnex/GitNex into issue-reactions

 Conflicts:
	app/src/main/res/layout/bottom_sheet_issue_comments.xml
	app/src/main/res/layout/list_issue_comments.xml

(Hopefully) fixing merge issues.

Merge branch 'master' of https://codeberg.org/gitnex/GitNex into issue-reactions

 Conflicts:
	app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java
	app/src/main/res/layout/activity_issue_detail.xml
	app/src/main/res/layout/bottom_sheet_issue_comments.xml
	app/src/main/res/layout/bottom_sheet_single_issue.xml
	app/src/main/res/values/colors.xml

Moving reactions below time frame on comments.

Merge branch 'master' into layout-reactions

Add IssueReactions

Merge remote-tracking branch 'origin/layout-reactions' into layout-reactions

Merge branch 'master' of https://gitea.com/gitnex/GitNex into layout-reactions

Merge branch 'master' into layout-reactions

Applying to pulls and issues.

Merge branch 'master' of https://gitea.com/gitnex/GitNex into layout-reactions

Providing external layouts.

Some improvements.

Adding comment emote indications.

Adding circle around emotes.

Adding some padding.

First tests.

Co-authored-by: opyale <opyale@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: 6543 <6543@noreply.gitea.io>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/557
Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
---
 .idea/codeStyles/Project.xml                  |  15 +-
 README.md                                     |   1 +
 app/build.gradle                              |  10 +-
 .../activities/IssueDetailActivity.java       |  22 +-
 .../gitnex/adapters/FilesDiffAdapter.java     |  22 +-
 .../gitnex/adapters/IssueCommentsAdapter.java |  53 +++-
 .../BottomSheetSingleIssueFragment.java       |  25 ++
 .../java/org/mian/gitnex/helpers/AppUtil.java |  12 +
 .../org/mian/gitnex/helpers/ChangeLog.java    |  17 +-
 .../mian/gitnex/interfaces/ApiInterface.java  |  85 ++++++-
 .../org/mian/gitnex/models/APISettings.java   |  34 +++
 .../gitnex/models/AttachmentSettings.java     |  34 +++
 .../gitnex/models/CreateStatusOption.java     |  36 +++
 .../org/mian/gitnex/models/IssueReaction.java |  71 ++++++
 .../mian/gitnex/models/MarkdownOption.java    |  42 ++++
 .../mian/gitnex/models/MultiSelectModel.java  |  42 ----
 .../gitnex/models/RepositorySettings.java     |  22 ++
 .../java/org/mian/gitnex/models/Status.java   |  56 +++++
 .../org/mian/gitnex/models/UISettings.java    |  16 ++
 .../org/mian/gitnex/models/UserHeatmap.java   |  12 +
 .../{helpers => views}/DiffTextView.java      |   2 +-
 .../org/mian/gitnex/views/ReactionList.java   | 140 +++++++++++
 .../mian/gitnex/views/ReactionSpinner.java    | 238 ++++++++++++++++++
 .../main/res/layout/activity_issue_detail.xml |   8 +
 .../layout/bottom_sheet_issue_comments.xml    |  15 +-
 .../res/layout/bottom_sheet_single_issue.xml  |   7 +
 .../main/res/layout/layout_reaction_badge.xml |  24 ++
 .../res/layout/layout_reaction_button.xml     |  22 ++
 .../main/res/layout/list_issue_comments.xml   |   7 +
 app/src/main/res/values-v23/themes.xml        |   2 +
 app/src/main/res/values/attrs.xml             |   1 +
 app/src/main/res/values/colors.xml            |   5 +-
 app/src/main/res/values/themes.xml            |   3 +
 33 files changed, 1000 insertions(+), 101 deletions(-)
 create mode 100644 app/src/main/java/org/mian/gitnex/models/APISettings.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/AttachmentSettings.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/CreateStatusOption.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/IssueReaction.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/MarkdownOption.java
 delete mode 100644 app/src/main/java/org/mian/gitnex/models/MultiSelectModel.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/RepositorySettings.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/Status.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/UISettings.java
 create mode 100644 app/src/main/java/org/mian/gitnex/models/UserHeatmap.java
 rename app/src/main/java/org/mian/gitnex/{helpers => views}/DiffTextView.java (97%)
 create mode 100644 app/src/main/java/org/mian/gitnex/views/ReactionList.java
 create mode 100644 app/src/main/java/org/mian/gitnex/views/ReactionSpinner.java
 create mode 100644 app/src/main/res/layout/layout_reaction_badge.xml
 create mode 100644 app/src/main/res/layout/layout_reaction_button.xml

diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index ffa16517..40c66e59 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -19,9 +19,18 @@
     <JetCodeStyleSettings>
       <option name="PACKAGES_TO_USE_STAR_IMPORTS">
         <value>
-          <package name="java.util" withSubpackages="false" static="false" />
-          <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
-          <package name="io.ktor" withSubpackages="true" static="false" />
+          <package name="java.util" alias="false" withSubpackages="false" />
+          <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
+          <package name="io.ktor" alias="false" withSubpackages="true" />
+        </value>
+      </option>
+      <option name="PACKAGES_IMPORT_LAYOUT">
+        <value>
+          <package name="" alias="false" withSubpackages="true" />
+          <package name="java" alias="false" withSubpackages="true" />
+          <package name="javax" alias="false" withSubpackages="true" />
+          <package name="kotlin" alias="false" withSubpackages="true" />
+          <package name="" alias="true" withSubpackages="true" />
         </value>
       </option>
     </JetCodeStyleSettings>
diff --git a/README.md b/README.md
index a03a0cb7..f46bdafa 100644
--- a/README.md
+++ b/README.md
@@ -89,5 +89,6 @@ Thanks to all the open source libraries, contributors and donators.
 - Ge0rg/memorizingTrustManager
 - Dimezis/blurView
 - Mikaelhg/urlbuilder
+- emoji-java
 
 [Follow me on Fediverse - mastodon.social/@mmarif](https://mastodon.social/@mmarif)
diff --git a/app/build.gradle b/app/build.gradle
index a570c5f0..59b2c58b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -38,8 +38,10 @@ android {
         abortOnError false
     }
     compileOptions {
-        targetCompatibility = "8"
-        sourceCompatibility = "8"
+        coreLibraryDesugaringEnabled true
+
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
     }
     defaultConfig{
         vectorDrawables.useSupportLibrary = true
@@ -60,7 +62,7 @@ dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
     implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
     implementation 'com.google.android.material:material:1.3.0-alpha03'
-    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
     implementation "androidx.legacy:legacy-support-v4:1.0.0"
     implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
     testImplementation 'junit:junit:4.13.1'
@@ -108,5 +110,7 @@ dependencies {
     implementation "androidx.work:work-runtime:$work_version"
     implementation "com.eightbitlab:blurview:1.6.4"
     implementation "io.mikael:urlbuilder:2.0.9"
+    implementation 'com.vdurmont:emoji-java:5.1.1'
+    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.0'
 
 }
diff --git a/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java
index f7bb36ec..41c80252 100644
--- a/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java
+++ b/app/src/main/java/org/mian/gitnex/activities/IssueDetailActivity.java
@@ -59,6 +59,7 @@ import org.mian.gitnex.models.Labels;
 import org.mian.gitnex.models.UpdateIssueAssignees;
 import org.mian.gitnex.models.WatchInfo;
 import org.mian.gitnex.viewmodels.IssueCommentsViewModel;
+import org.mian.gitnex.views.ReactionList;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -208,6 +209,10 @@ public class IssueDetailActivity extends BaseActivity implements LabelsListAdapt
 
 		switch(text) {
 
+			case "onResume":
+				onResume();
+				break;
+
 			case "showLabels":
 				showLabels();
 				break;
@@ -519,7 +524,13 @@ public class IssueDetailActivity extends BaseActivity implements LabelsListAdapt
 					viewBinding.divider.setVisibility(View.VISIBLE);
 				}
 
-				adapter = new IssueCommentsAdapter(ctx, issueCommentsMain, getSupportFragmentManager(), this::onResume);
+				Bundle bundle = new Bundle();
+				bundle.putString("repoOwner", repoOwner);
+				bundle.putString("repoName", repoName);
+				bundle.putInt("issueNumber", issueIndex);
+
+				adapter = new IssueCommentsAdapter(ctx, bundle, issueCommentsMain, getSupportFragmentManager(), this::onResume);
+
 				viewBinding.recyclerView.setAdapter(adapter);
 
 			});
@@ -718,6 +729,15 @@ public class IssueDetailActivity extends BaseActivity implements LabelsListAdapt
 							.setOnClickListener(new ClickListener(TimeHelper.customDateFormatForToastDateFormat(singleIssue.getCreated_at()), ctx));
 					}
 
+					Bundle bundle = new Bundle();
+					bundle.putString("repoOwner", repoOwner);
+					bundle.putString("repoName", repoName);
+					bundle.putInt("issueId", singleIssue.getNumber());
+
+					ReactionList reactionList = new ReactionList(ctx, bundle);
+					viewBinding.commentReactionBadges.removeAllViews();
+					viewBinding.commentReactionBadges.addView(reactionList);
+
 					if(singleIssue.getMilestone() != null) {
 
 						viewBinding.issueMilestone.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java
index 2b7811c8..86bec71c 100644
--- a/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java
+++ b/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java
@@ -15,8 +15,9 @@ import android.widget.TextView;
 import androidx.fragment.app.FragmentManager;
 import org.mian.gitnex.R;
 import org.mian.gitnex.fragments.BottomSheetReplyFragment;
-import org.mian.gitnex.helpers.DiffTextView;
+import org.mian.gitnex.helpers.AppUtil;
 import org.mian.gitnex.models.FileDiffView;
+import org.mian.gitnex.views.DiffTextView;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentSkipListMap;
@@ -48,11 +49,11 @@ public class FilesDiffAdapter extends BaseAdapter {
 
 		selectedViews = new ConcurrentSkipListMap<>();
 
-		COLOR_ADDED = getColorFromAttribute(R.attr.diffAddedColor);
-		COLOR_REMOVED = getColorFromAttribute(R.attr.diffRemovedColor);
-		COLOR_NORMAL = getColorFromAttribute(R.attr.primaryBackgroundColor);
-		COLOR_SELECTED = getColorFromAttribute(R.attr.diffSelectedColor);
-		COLOR_FONT = getColorFromAttribute(R.attr.inputTextColor);
+		COLOR_ADDED = AppUtil.getColorFromAttribute(context, R.attr.diffAddedColor);
+		COLOR_REMOVED = AppUtil.getColorFromAttribute(context, R.attr.diffRemovedColor);
+		COLOR_NORMAL = AppUtil.getColorFromAttribute(context, R.attr.primaryBackgroundColor);
+		COLOR_SELECTED = AppUtil.getColorFromAttribute(context, R.attr.diffSelectedColor);
+		COLOR_FONT = AppUtil.getColorFromAttribute(context, R.attr.inputTextColor);
 
 	}
 
@@ -256,13 +257,4 @@ public class FilesDiffAdapter extends BaseAdapter {
 
 	}
 
-	private int getColorFromAttribute(int resid) {
-
-		TypedValue typedValue = new TypedValue();
-		context.getTheme().resolveAttribute(resid, typedValue, true);
-
-		return typedValue.data;
-
-	}
-
 }
diff --git a/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java
index a60230c6..8d78c7db 100644
--- a/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java
+++ b/app/src/main/java/org/mian/gitnex/adapters/IssueCommentsAdapter.java
@@ -10,6 +10,7 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 import androidx.annotation.NonNull;
 import androidx.fragment.app.FragmentManager;
@@ -29,6 +30,8 @@ import org.mian.gitnex.helpers.TimeHelper;
 import org.mian.gitnex.helpers.TinyDB;
 import org.mian.gitnex.helpers.Toasty;
 import org.mian.gitnex.models.IssueComments;
+import org.mian.gitnex.views.ReactionList;
+import org.mian.gitnex.views.ReactionSpinner;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -42,17 +45,22 @@ import retrofit2.Callback;
 public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdapter.IssueCommentViewHolder> {
 
 	private final Context ctx;
+	private final TinyDB tinyDB;
+	private final Bundle bundle;
 	private final List<IssueComments> issuesComments;
 	private final FragmentManager fragmentManager;
 	private final BottomSheetReplyFragment.OnInteractedListener onInteractedListener;
 
-	public IssueCommentsAdapter(Context ctx, List<IssueComments> issuesCommentsMain, FragmentManager fragmentManager, BottomSheetReplyFragment.OnInteractedListener onInteractedListener) {
+	public IssueCommentsAdapter(Context ctx, Bundle bundle, List<IssueComments> issuesCommentsMain, FragmentManager fragmentManager, BottomSheetReplyFragment.OnInteractedListener onInteractedListener) {
 
 		this.ctx = ctx;
+		this.bundle = bundle;
 		this.issuesComments = issuesCommentsMain;
 		this.fragmentManager = fragmentManager;
 		this.onInteractedListener = onInteractedListener;
 
+		tinyDB = TinyDB.getInstance(ctx);
+
 	}
 
 	class IssueCommentViewHolder extends RecyclerView.ViewHolder {
@@ -63,6 +71,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 		private final TextView author;
 		private final TextView information;
 		private final TextView comment;
+		private final LinearLayout commentReactionBadges;
 
 		private IssueCommentViewHolder(View view) {
 
@@ -73,12 +82,12 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 			information = view.findViewById(R.id.information);
 			ImageView menu = view.findViewById(R.id.menu);
 			comment = view.findViewById(R.id.comment);
+			commentReactionBadges = view.findViewById(R.id.commentReactionBadges);
 
 			menu.setOnClickListener(v -> {
 
 				final Context ctx = v.getContext();
-				final TinyDB tinyDb = TinyDB.getInstance(ctx);
-				final String loginUid = tinyDb.getString("loginUid");
+				final String loginUid = tinyDB.getString("loginUid");
 
 				@SuppressLint("InflateParams") View vw = LayoutInflater.from(ctx).inflate(R.layout.bottom_sheet_issue_comments, null);
 
@@ -102,6 +111,24 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 				dialog.setContentView(vw);
 				dialog.show();
 
+				LinearLayout linearLayout = vw.findViewById(R.id.commentReactionButtons);
+
+				Bundle bundle1 = new Bundle();
+				bundle1.putAll(bundle);
+				bundle1.putInt("commentId", issueComment.getId());
+
+				ReactionSpinner reactionSpinner = new ReactionSpinner(ctx, bundle1);
+				reactionSpinner.setOnInteractedListener(() -> {
+
+					tinyDB.putBoolean("commentEdited", true);
+
+					onInteractedListener.onInteracted();
+					dialog.dismiss();
+
+				});
+
+				linearLayout.addView(reactionSpinner);
+
 				commentMenuEdit.setOnClickListener(v1 -> {
 
 					Bundle bundle = new Bundle();
@@ -125,7 +152,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 					// share issue comment
 					Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
 					sharingIntent.setType("text/plain");
-					String intentHeader = tinyDb.getString("issueNumber") + ctx.getResources().getString(R.string.hash) + "issuecomment-" + issueComment.getId() + " " + tinyDb.getString("issueTitle");
+					String intentHeader = tinyDB.getString("issueNumber") + ctx.getResources().getString(R.string.hash) + "issuecomment-" + issueComment.getId() + " " + tinyDB.getString("issueTitle");
 					sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, intentHeader);
 					sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, commentUrl);
 					ctx.startActivity(Intent.createChooser(sharingIntent, intentHeader));
@@ -155,7 +182,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 					StringBuilder stringBuilder = new StringBuilder();
 					String commenterName = issueComment.getUser().getUsername();
 
-					if(!commenterName.equals(tinyDb.getString("userLogin"))) {
+					if(!commenterName.equals(tinyDB.getString("userLogin"))) {
 
 						stringBuilder.append("@").append(commenterName).append("\n\n");
 					}
@@ -183,7 +210,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 					ClipboardManager clipboard = (ClipboardManager) Objects.requireNonNull(ctx).getSystemService(Context.CLIPBOARD_SERVICE);
 					assert clipboard != null;
 
-					ClipData clip = ClipData.newPlainText("Comment on issue #" + tinyDb.getString("issueNumber"), issueComment.getBody());
+					ClipData clip = ClipData.newPlainText("Comment on issue #" + tinyDB.getString("issueNumber"), issueComment.getBody());
 					clipboard.setPrimaryClip(clip);
 
 					dialog.dismiss();
@@ -214,10 +241,9 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 
 	private void deleteIssueComment(final Context ctx, final int commentId, int position) {
 
-		final TinyDB tinyDb = TinyDB.getInstance(ctx);
-		final String loginUid = tinyDb.getString("loginUid");
-		final String instanceToken = "token " + tinyDb.getString(loginUid + "-token");
-		String[] repoFullName = tinyDb.getString("repoFullName").split("/");
+		final String loginUid = tinyDB.getString("loginUid");
+		final String instanceToken = "token " + tinyDB.getString(loginUid + "-token");
+		String[] repoFullName = tinyDB.getString("repoFullName").split("/");
 
 		if (repoFullName.length != 2) {
 			return;
@@ -307,6 +333,13 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<IssueCommentsAdap
 
 		holder.information.setText(informationBuilder.toString());
 
+		Bundle bundle1 = new Bundle();
+		bundle1.putAll(bundle);
+		bundle1.putInt("commentId", issueComment.getId());
+
+		ReactionList reactionList = new ReactionList(ctx, bundle1);
+		holder.commentReactionBadges.addView(reactionList);
+
 	}
 
 	@Override
diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetSingleIssueFragment.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetSingleIssueFragment.java
index e8a5023c..dcf70c3e 100644
--- a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetSingleIssueFragment.java
+++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetSingleIssueFragment.java
@@ -8,6 +8,7 @@ import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -20,6 +21,7 @@ import org.mian.gitnex.activities.MergePullRequestActivity;
 import org.mian.gitnex.helpers.TinyDB;
 import org.mian.gitnex.helpers.Toasty;
 import org.mian.gitnex.helpers.Version;
+import org.mian.gitnex.views.ReactionSpinner;
 import java.util.Objects;
 
 /**
@@ -51,6 +53,29 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment {
 		TextView subscribeIssue = v.findViewById(R.id.subscribeIssue);
 		TextView unsubscribeIssue = v.findViewById(R.id.unsubscribeIssue);
 
+		LinearLayout linearLayout = v.findViewById(R.id.commentReactionButtons);
+
+		Bundle bundle1 = new Bundle();
+
+		String repoFullName = tinyDB.getString("repoFullName");
+		String[] parts = repoFullName.split("/");
+
+		bundle1.putString("repoOwner", parts[0]);
+		bundle1.putString("repoName", parts[1]);
+		bundle1.putInt("issueId", Integer.parseInt(tinyDB.getString("issueNumber")));
+
+		ReactionSpinner reactionSpinner = new ReactionSpinner(ctx, bundle1);
+		reactionSpinner.setOnInteractedListener(() -> {
+
+			tinyDB.putBoolean("singleIssueUpdate", true);
+
+			bmListener.onButtonClicked("onResume");
+			dismiss();
+
+		});
+
+		linearLayout.addView(reactionSpinner);
+
 		if(tinyDB.getString("issueType").equalsIgnoreCase("Pull")) {
 
 			editIssue.setText(R.string.editPrText);
diff --git a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
index 3558d735..fdfefe85 100644
--- a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
+++ b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java
@@ -9,7 +9,9 @@ import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.util.Base64;
 import android.util.DisplayMetrics;
+import android.util.TypedValue;
 import android.view.View;
+import androidx.annotation.ColorInt;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -131,6 +133,16 @@ public class AppUtil {
 
 	}
 
+	@ColorInt
+	public static int getColorFromAttribute(Context context, int resid) {
+
+		TypedValue typedValue = new TypedValue();
+		context.getTheme().resolveAttribute(resid, typedValue, true);
+
+		return typedValue.data;
+
+	}
+
 	public static String customDateFormat(String customDate) {
 
 		String[] parts = customDate.split("-");
diff --git a/app/src/main/java/org/mian/gitnex/helpers/ChangeLog.java b/app/src/main/java/org/mian/gitnex/helpers/ChangeLog.java
index 6d0f4d9e..bfe5a4cf 100644
--- a/app/src/main/java/org/mian/gitnex/helpers/ChangeLog.java
+++ b/app/src/main/java/org/mian/gitnex/helpers/ChangeLog.java
@@ -1,10 +1,5 @@
 package org.mian.gitnex.helpers;
 
-import java.io.IOException;
-import java.util.Objects;
-import org.mian.gitnex.R;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 import android.app.Activity;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -12,6 +7,11 @@ import android.content.res.XmlResourceParser;
 import android.text.Html;
 import android.util.Log;
 import androidx.appcompat.app.AlertDialog;
+import org.mian.gitnex.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import java.io.IOException;
+import java.util.Objects;
 
 /**
  * Author M M Arif
@@ -90,13 +90,14 @@ public class ChangeLog {
 
 		String changelogMessage = getChangelog(resId, res);
 
-		androidx.appcompat.app.AlertDialog.Builder builder = new AlertDialog.Builder(changelogActivity);
+		AlertDialog.Builder builder = new AlertDialog.Builder(changelogActivity);
+
 		builder.setTitle(R.string.changelogTitle);
 		builder.setMessage(Html.fromHtml("<small>" + changelogMessage + "</small>"));
 		builder.setNeutralButton(R.string.close, null);
 		builder.setCancelable(false);
-		builder.create();
-		builder.show();
+
+		builder.create().show();
 
 	}
 
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 d07f58f7..e7edecb3 100644
--- a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java
+++ b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java
@@ -1,13 +1,16 @@
 package org.mian.gitnex.interfaces;
 
 import com.google.gson.JsonElement;
+import org.mian.gitnex.models.APISettings;
 import org.mian.gitnex.models.AddEmail;
+import org.mian.gitnex.models.AttachmentSettings;
 import org.mian.gitnex.models.Branches;
 import org.mian.gitnex.models.Collaborators;
 import org.mian.gitnex.models.Commits;
 import org.mian.gitnex.models.CreateIssue;
 import org.mian.gitnex.models.CreateLabel;
 import org.mian.gitnex.models.CreatePullRequest;
+import org.mian.gitnex.models.CreateStatusOption;
 import org.mian.gitnex.models.DeleteFile;
 import org.mian.gitnex.models.EditFile;
 import org.mian.gitnex.models.Emails;
@@ -15,8 +18,10 @@ import org.mian.gitnex.models.ExploreRepositories;
 import org.mian.gitnex.models.Files;
 import org.mian.gitnex.models.GiteaVersion;
 import org.mian.gitnex.models.IssueComments;
+import org.mian.gitnex.models.IssueReaction;
 import org.mian.gitnex.models.Issues;
 import org.mian.gitnex.models.Labels;
+import org.mian.gitnex.models.MarkdownOption;
 import org.mian.gitnex.models.MergePullRequest;
 import org.mian.gitnex.models.Milestones;
 import org.mian.gitnex.models.NewFile;
@@ -28,10 +33,14 @@ import org.mian.gitnex.models.OrganizationRepository;
 import org.mian.gitnex.models.Permission;
 import org.mian.gitnex.models.PullRequests;
 import org.mian.gitnex.models.Releases;
+import org.mian.gitnex.models.RepositorySettings;
 import org.mian.gitnex.models.RepositoryTransfer;
+import org.mian.gitnex.models.Status;
 import org.mian.gitnex.models.Teams;
+import org.mian.gitnex.models.UISettings;
 import org.mian.gitnex.models.UpdateIssueAssignees;
 import org.mian.gitnex.models.UpdateIssueState;
+import org.mian.gitnex.models.UserHeatmap;
 import org.mian.gitnex.models.UserInfo;
 import org.mian.gitnex.models.UserOrganizations;
 import org.mian.gitnex.models.UserRepositories;
@@ -70,8 +79,26 @@ public interface ApiInterface {
     @GET("version") // gitea version API
     Call<GiteaVersion> getGiteaVersionWithToken(@Header("Authorization") String token);
 
-    @GET("user") // username, full name, email
-    Call<UserInfo> getUserInfo(@Header("Authorization") String token);
+    @POST("markdown")
+    Call<String> renderMarkdown(@Header("Authorization") String token, @Body MarkdownOption markdownOption);
+
+    @POST("markdown/raw")
+    Call<String> renderRawMarkdown(@Header("Authorization") String token, @Body String body);
+
+    @GET("signing-key.gpg") // Get default signing-key.gpg
+    Call<String> getSigningKey(@Header("Authorization") String token);
+
+	@GET("settings/api") // Get instance's global settings for api
+	Call<APISettings> getAPISettings(@Header("Authorization") String token);
+
+	@GET("settings/attachment") // Get instance's global settings for attachments
+	Call<AttachmentSettings> getAttachmentSettings(@Header("Authorization") String token);
+
+	@GET("settings/repository") // Get instance's global settings for repositories
+	Call<RepositorySettings> getRepositorySettings(@Header("Authorization") String token);
+
+    @GET("settings/ui") // Get instance's global settings for ui
+    Call<UISettings> getUISettings(@Header("Authorization") String token);
 
     @GET("users/{username}/tokens") // get user token
     Call<List<UserTokens>> getUserTokens(@Header("Authorization") String authorization, @Path("username") String loginUid);
@@ -112,6 +139,9 @@ public interface ApiInterface {
     @PUT("repos/{owner}/{repo}/notifications") // Mark notification threads as read, pinned or unread on a specific repo
     Call<ResponseBody> markRepoNotificationThreadsAsRead(@Header("Authorization") String token, @Path("owner") String owner, @Path("repo") String repo, @Query("all") Boolean all, @Query("status-types") String[] statusTypes, @Query("to-status") String toStatus, @Query("last_read_at") String last_read_at);
 
+	@GET("user") // username, full name, email
+	Call<UserInfo> getUserInfo(@Header("Authorization") String token);
+
     @GET("user/orgs") // get user organizations
     Call<List<UserOrganizations>> getUserOrgs(@Header("Authorization") String token);
 
@@ -130,6 +160,24 @@ public interface ApiInterface {
     @POST("user/repos") // create new repository
     Call<OrganizationRepository> createNewUserRepository(@Header("Authorization") String token, @Body OrganizationRepository jsonStr);
 
+	@GET("user/followers") // get user followers
+	Call<List<UserInfo>> getFollowers(@Header("Authorization") String token);
+
+	@GET("user/following") // get following
+	Call<List<UserInfo>> getFollowing(@Header("Authorization") String token);
+
+	@POST("user/emails") // add new email
+	Call<JsonElement> addNewEmail(@Header("Authorization") String token, @Body AddEmail jsonStr);
+
+	@GET("user/emails") // get user emails
+	Call<List<Emails>> getUserEmails(@Header("Authorization") String token);
+
+	@GET("user/starred") // get user starred repositories
+	Call<List<UserRepositories>> getUserStarredRepos(@Header("Authorization") String token, @Query("page") int page, @Query("limit") int limit);
+
+	@GET("users/{username}/heatmap") // Get a user's heatmap
+	Call<List<UserHeatmap>> getUserHeatmap(@Header("Authorization") String token, @Path("username") String username);
+
     @GET("repos/{owner}/{repo}") // get repo information
     Call<UserRepositories> getUserRepository(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName);
 
@@ -193,9 +241,6 @@ public interface ApiInterface {
     @PATCH("repos/{owner}/{repo}/labels/{index}") // update / patch a label
     Call<CreateLabel> patchLabel(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int labelIndex, @Body CreateLabel jsonStr);
 
-    @GET("user/starred") // get user starred repositories
-    Call<List<UserRepositories>> getUserStarredRepos(@Header("Authorization") String token, @Query("page") int page, @Query("limit") int limit);
-
     @GET("orgs/{orgName}/repos") // get repositories by org
     Call<List<UserRepositories>> getReposByOrg(@Header("Authorization") String token, @Path("orgName") String orgName, @Query("page") int page, @Query("limit") int limit);
 
@@ -226,17 +271,23 @@ public interface ApiInterface {
     @PATCH("repos/{owner}/{repo}/issues/comments/{commentId}") // edit a comment
     Call<IssueComments> patchIssueComment(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("commentId") int commentId, @Body IssueComments jsonStr);
 
-    @GET("user/followers") // get user followers
-    Call<List<UserInfo>> getFollowers(@Header("Authorization") String token);
+    @GET("repos/{owner}/{repo}/issues/comments/{commentId}/reactions") // get comment reactions
+    Call<List<IssueReaction>> getIssueCommentReactions(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("commentId") int commentId);
 
-    @GET("user/following") // get following
-    Call<List<UserInfo>> getFollowing(@Header("Authorization") String token);
+    @POST("repos/{owner}/{repo}/issues/comments/{commentId}/reactions") // add reaction to a comment
+    Call<IssueReaction> setIssueCommentReaction(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("commentId") int commentId, @Body IssueReaction jsonStr);
 
-    @POST("user/emails") // add new email
-    Call<JsonElement> addNewEmail(@Header("Authorization") String token, @Body AddEmail jsonStr);
+    @HTTP(method = "DELETE", path = "repos/{owner}/{repo}/issues/comments/{commentId}/reactions", hasBody = true) // delete a reaction of a comment
+    Call<ResponseBody> removeIssueCommentReaction(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("commentId") int commentId, @Body IssueReaction jsonStr);
 
-    @GET("user/emails") // get user emails
-    Call<List<Emails>> getUserEmails(@Header("Authorization") String token);
+	@GET("repos/{owner}/{repo}/issues/{index}/reactions") // get issue reactions
+	Call<List<IssueReaction>> getIssueReactions(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex);
+
+	@POST("repos/{owner}/{repo}/issues/{index}/reactions") // add reaction to an issue
+	Call<IssueReaction> setIssueReaction(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex, @Body IssueReaction jsonStr);
+
+	@HTTP(method = "DELETE", path = "repos/{owner}/{repo}/issues/{index}/reactions", hasBody = true) // delete a reaction of an issue
+	Call<ResponseBody> removeIssueReaction(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex, @Body IssueReaction jsonStr);
 
     @GET("repos/{owner}/{repo}/issues/{index}/labels") // get issue labels
     Call<List<Labels>> getIssueLabels(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int issueIndex);
@@ -363,4 +414,12 @@ public interface ApiInterface {
 
 	@GET("repos/{owner}/{repo}/forks") // get all repo forks
 	Call<List<UserRepositories>> getRepositoryForks(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Query("page") int page, @Query("limit") int limit);
+
+	@POST("repos/{owner}/{repo}/statuses/{sha}") // Create a commit status
+	Call<Status> createCommitStatus(@Header("Authorization") String token, @Path("owner") String owner, @Path("repo") String repo, @Path("sha") String sha, @Body CreateStatusOption createStatusOption);
+
+	@GET("repos/{owner}/{repo}/statuses/{sha}") // Get a commit's statuses
+	Call<List<Status>> getCommitStatuses(@Header("Authorization") String token, @Path("owner") String owner, @Path("repo") String repo, @Query("sort") String sort, @Query("state") String state, @Query("page") int page, @Query("limit") int limit);
+
+
 }
diff --git a/app/src/main/java/org/mian/gitnex/models/APISettings.java b/app/src/main/java/org/mian/gitnex/models/APISettings.java
new file mode 100644
index 00000000..ff633aea
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/APISettings.java
@@ -0,0 +1,34 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class APISettings {
+
+	private int default_git_trees_per_page;
+	private int default_max_blob_size;
+	private int default_paging_num;
+	private int max_response_items;
+
+	public int getDefault_git_trees_per_page() {
+
+		return default_git_trees_per_page;
+	}
+
+	public int getDefault_max_blob_size() {
+
+		return default_max_blob_size;
+	}
+
+	public int getDefault_paging_num() {
+
+		return default_paging_num;
+	}
+
+	public int getMax_response_items() {
+
+		return max_response_items;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/AttachmentSettings.java b/app/src/main/java/org/mian/gitnex/models/AttachmentSettings.java
new file mode 100644
index 00000000..0f2dcef0
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/AttachmentSettings.java
@@ -0,0 +1,34 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class AttachmentSettings {
+
+	private String allowed_types;
+	private boolean enabled;
+	private float max_files;
+	private float max_size;
+
+	public String getAllowed_types() {
+
+		return allowed_types;
+	}
+
+	public boolean isEnabled() {
+
+		return enabled;
+	}
+
+	public float getMax_files() {
+
+		return max_files;
+	}
+
+	public float getMax_size() {
+
+		return max_size;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/CreateStatusOption.java b/app/src/main/java/org/mian/gitnex/models/CreateStatusOption.java
new file mode 100644
index 00000000..74cdc8d8
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/CreateStatusOption.java
@@ -0,0 +1,36 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class CreateStatusOption {
+
+    private String context;
+    private String description;
+    private String statusState;
+    private String target_url;
+
+    public CreateStatusOption(String context, String description, String statusState, String target_url) {
+        this.context = context;
+        this.description = description;
+        this.statusState = statusState;
+        this.target_url = target_url;
+    }
+
+    public String getContext() {
+        return context;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getStatusState() {
+        return statusState;
+    }
+
+    public String getTarget_url() {
+        return target_url;
+    }
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/IssueReaction.java b/app/src/main/java/org/mian/gitnex/models/IssueReaction.java
new file mode 100644
index 00000000..74fcc838
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/IssueReaction.java
@@ -0,0 +1,71 @@
+package org.mian.gitnex.models;
+
+import java.util.Date;
+
+/**
+ * Author 6543
+ */
+
+public class IssueReaction {
+
+    private String content;
+    private userObject user;
+    private Date created_at;
+
+    public IssueReaction(String content) {
+        this.content = content;
+    }
+
+    public static class userObject {
+
+        private int id;
+        private String login;
+        private String full_name;
+        private String email;
+        private String avatar_url;
+        private String language;
+        private String username;
+
+        public int getId() {
+            return id;
+        }
+
+        public String getLogin() {
+            return login;
+        }
+
+        public String getFull_name() {
+            return full_name;
+        }
+
+        public String getEmail() {
+            return email;
+        }
+
+        public String getAvatar_url() {
+            return avatar_url;
+        }
+
+        public String getLanguage() {
+            return language;
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public userObject getUser() {
+        return user;
+    }
+
+    public Date getCreated_at() {
+        return created_at;
+    }
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/MarkdownOption.java b/app/src/main/java/org/mian/gitnex/models/MarkdownOption.java
new file mode 100644
index 00000000..444dea44
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/MarkdownOption.java
@@ -0,0 +1,42 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class MarkdownOption {
+
+	private String Context;
+	private String Mode;
+	private String Text;
+	private boolean Wiki;
+
+	public MarkdownOption(String context, String mode, String text, boolean wiki) {
+
+		Context = context;
+		Mode = mode;
+		Text = text;
+		Wiki = wiki;
+	}
+
+	public String getContext() {
+
+		return Context;
+	}
+
+	public String getMode() {
+
+		return Mode;
+	}
+
+	public String getText() {
+
+		return Text;
+	}
+
+	public boolean isWiki() {
+
+		return Wiki;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/MultiSelectModel.java b/app/src/main/java/org/mian/gitnex/models/MultiSelectModel.java
deleted file mode 100644
index 710a8b35..00000000
--- a/app/src/main/java/org/mian/gitnex/models/MultiSelectModel.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.mian.gitnex.models;
-
-/**
- * Author com.github.abumoallim, modified by M M Arif
- */
-
-public class MultiSelectModel {
-
-    private Integer id;
-    private String name;
-    private Boolean isSelected;
-
-    public MultiSelectModel(Integer id, String name) {
-        this.id = id;
-        this.name = name;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public Boolean getSelected() {
-        return isSelected;
-    }
-
-    public void setSelected(Boolean selected) {
-        isSelected = selected;
-    }
-
-}
diff --git a/app/src/main/java/org/mian/gitnex/models/RepositorySettings.java b/app/src/main/java/org/mian/gitnex/models/RepositorySettings.java
new file mode 100644
index 00000000..0fdf189f
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/RepositorySettings.java
@@ -0,0 +1,22 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class RepositorySettings {
+
+	private boolean http_git_disabled;
+	private boolean mirrors_disabled;
+
+	public boolean isHttp_git_disabled() {
+
+		return http_git_disabled;
+	}
+
+	public boolean isMirrors_disabled() {
+
+		return mirrors_disabled;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/Status.java b/app/src/main/java/org/mian/gitnex/models/Status.java
new file mode 100644
index 00000000..a0012d11
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/Status.java
@@ -0,0 +1,56 @@
+package org.mian.gitnex.models;
+
+import java.util.Date;
+
+/**
+ * Author opyale
+ */
+
+public class Status {
+
+    private String context;
+    private Date created_at;
+    private UserInfo creator;
+    private String description;
+    private int id;
+    private String status;
+    private String target_url;
+    private Date updated_at;
+    private String url;
+
+    public String getContext() {
+        return context;
+    }
+
+    public Date getCreated_at() {
+        return created_at;
+    }
+
+    public UserInfo getCreator() {
+        return creator;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public String getTarget_url() {
+        return target_url;
+    }
+
+    public Date getUpdated_at() {
+        return updated_at;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/UISettings.java b/app/src/main/java/org/mian/gitnex/models/UISettings.java
new file mode 100644
index 00000000..67b6e7ba
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/UISettings.java
@@ -0,0 +1,16 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class UISettings {
+
+	private String[] allowed_reactions;
+
+	public String[] getAllowed_reactions() {
+
+		return allowed_reactions;
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/models/UserHeatmap.java b/app/src/main/java/org/mian/gitnex/models/UserHeatmap.java
new file mode 100644
index 00000000..f0a62c3b
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/models/UserHeatmap.java
@@ -0,0 +1,12 @@
+package org.mian.gitnex.models;
+
+/**
+ * Author opyale
+ */
+
+public class UserHeatmap {
+
+	private long contributions;
+	private long timestamp;
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/helpers/DiffTextView.java b/app/src/main/java/org/mian/gitnex/views/DiffTextView.java
similarity index 97%
rename from app/src/main/java/org/mian/gitnex/helpers/DiffTextView.java
rename to app/src/main/java/org/mian/gitnex/views/DiffTextView.java
index dcb81e7a..2e9503c3 100644
--- a/app/src/main/java/org/mian/gitnex/helpers/DiffTextView.java
+++ b/app/src/main/java/org/mian/gitnex/views/DiffTextView.java
@@ -1,4 +1,4 @@
-package org.mian.gitnex.helpers;
+package org.mian.gitnex.views;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/app/src/main/java/org/mian/gitnex/views/ReactionList.java b/app/src/main/java/org/mian/gitnex/views/ReactionList.java
new file mode 100644
index 00000000..3e13dfcd
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/views/ReactionList.java
@@ -0,0 +1,140 @@
+package org.mian.gitnex.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.cardview.widget.CardView;
+import com.vdurmont.emoji.Emoji;
+import com.vdurmont.emoji.EmojiManager;
+import org.mian.gitnex.R;
+import org.mian.gitnex.clients.RetrofitClient;
+import org.mian.gitnex.helpers.AppUtil;
+import org.mian.gitnex.helpers.Authorization;
+import org.mian.gitnex.helpers.TinyDB;
+import org.mian.gitnex.models.IssueReaction;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import retrofit2.Response;
+
+/**
+ * @author opyale
+ */
+
+@SuppressLint("ViewConstructor")
+public class ReactionList extends HorizontalScrollView {
+
+	private enum ReactionType { COMMENT, ISSUE }
+
+	@SuppressLint("SetTextI18n")
+	public ReactionList(Context context, Bundle bundle) {
+
+		super(context);
+
+		LinearLayout root = new LinearLayout(context);
+
+		LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+		root.setOrientation(LinearLayout.HORIZONTAL);
+		root.setGravity(Gravity.START);
+		root.setLayoutParams(layoutParams);
+
+		addView(root);
+		setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+		TinyDB tinyDB = TinyDB.getInstance(context);
+
+		String loginUid = tinyDB.getString("loginUid");
+		String repoOwner = bundle.getString("repoOwner");
+		String repoName = bundle.getString("repoName");
+
+		int id;
+		ReactionType reactionType;
+
+		if(bundle.containsKey("commentId")) {
+			id = bundle.getInt("commentId");
+			reactionType = ReactionType.COMMENT;
+		} else {
+			id = bundle.getInt("issueId");
+			reactionType = ReactionType.ISSUE;
+		}
+
+		new Thread(() -> {
+
+			try {
+
+				Response<List<IssueReaction>> response = null;
+
+				switch(reactionType) {
+
+					case ISSUE:
+						response = RetrofitClient
+							.getApiInterface(context)
+							.getIssueReactions(Authorization.get(context), repoOwner, repoName, id)
+							.execute();
+						break;
+
+					case COMMENT:
+						response = RetrofitClient
+							.getApiInterface(context)
+							.getIssueCommentReactions(Authorization.get(context), repoOwner, repoName, id)
+							.execute();
+						break;
+
+				}
+
+				Map<String, List<IssueReaction>> sortedReactions = new HashMap<>();
+
+				if(response.isSuccessful() && response.body() != null) {
+
+					for(IssueReaction issueReaction : response.body()) {
+
+						if(sortedReactions.containsKey(issueReaction.getContent())) {
+
+							sortedReactions.get(issueReaction.getContent()).add(issueReaction);
+						} else {
+							List<IssueReaction> issueReactions = new ArrayList<>();
+							issueReactions.add(issueReaction);
+
+							sortedReactions.put(issueReaction.getContent(), issueReactions);
+						}
+					}
+				}
+
+				for(String content : sortedReactions.keySet()) {
+
+					List<IssueReaction> issueReactions = sortedReactions.get(content);
+
+					@SuppressLint("InflateParams") CardView reactionBadge = (CardView) LayoutInflater.from(context)
+						.inflate(R.layout.layout_reaction_badge, this, false);
+
+					for(IssueReaction issueReaction : issueReactions) {
+
+						if(issueReaction.getUser().getLogin().equals(loginUid)) {
+							reactionBadge.setCardBackgroundColor(AppUtil.getColorFromAttribute(context, R.attr.inputSelectedColor));
+							break;
+						}
+					}
+
+					Emoji emoji = EmojiManager.getForAlias(content);
+
+					((TextView) reactionBadge.findViewById(R.id.symbol)).setText(((emoji == null) ? content : emoji.getUnicode()) + " " + issueReactions.size());
+					root.post(() -> root.addView(reactionBadge));
+
+				}
+
+			} catch (IOException ignored) {}
+
+		}).start();
+
+	}
+
+}
diff --git a/app/src/main/java/org/mian/gitnex/views/ReactionSpinner.java b/app/src/main/java/org/mian/gitnex/views/ReactionSpinner.java
new file mode 100644
index 00000000..32da4d05
--- /dev/null
+++ b/app/src/main/java/org/mian/gitnex/views/ReactionSpinner.java
@@ -0,0 +1,238 @@
+package org.mian.gitnex.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.cardview.widget.CardView;
+import com.vdurmont.emoji.Emoji;
+import com.vdurmont.emoji.EmojiManager;
+import org.mian.gitnex.R;
+import org.mian.gitnex.clients.RetrofitClient;
+import org.mian.gitnex.helpers.AppUtil;
+import org.mian.gitnex.helpers.Authorization;
+import org.mian.gitnex.helpers.TinyDB;
+import org.mian.gitnex.models.IssueReaction;
+import org.mian.gitnex.models.UISettings;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import retrofit2.Response;
+
+/**
+ * @author opyale
+ */
+
+@SuppressLint("ViewConstructor")
+public class ReactionSpinner extends HorizontalScrollView {
+
+	private enum ReactionType { COMMENT, ISSUE }
+	private enum ReactionAction { REMOVE, ADD }
+
+	private OnInteractedListener onInteractedListener;
+
+	public ReactionSpinner(Context context, Bundle bundle) {
+
+		super(context);
+
+		LinearLayout root = new LinearLayout(context);
+
+		int dens = AppUtil.getPixelsFromDensity(context, 10);
+
+		LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+		root.setOrientation(LinearLayout.HORIZONTAL);
+		root.setPadding(dens, 0, dens, 0);
+		root.setGravity(Gravity.START);
+		root.setLayoutParams(layoutParams);
+
+		TinyDB tinyDB = TinyDB.getInstance(context);
+
+		String loginUid = tinyDB.getString("loginUid");
+		String repoOwner = bundle.getString("repoOwner");
+		String repoName = bundle.getString("repoName");
+
+		int id;
+		ReactionType reactionType;
+
+		if(bundle.containsKey("commentId")) {
+			id = bundle.getInt("commentId");
+			reactionType = ReactionType.COMMENT;
+		} else {
+			id = bundle.getInt("issueId");
+			reactionType = ReactionType.ISSUE;
+		}
+
+		new Thread(() -> {
+
+			try {
+
+				List<IssueReaction> allReactions = getReactions(repoOwner, repoName, reactionType, id);
+
+				for(String allowedReaction : getAllowedReactions()) {
+
+					@SuppressLint("InflateParams") CardView reactionButton = (CardView) LayoutInflater.from(context)
+						.inflate(R.layout.layout_reaction_button, root, false);
+
+					IssueReaction myReaction = null;
+
+					for(IssueReaction issueReaction : allReactions) {
+
+						if(issueReaction.getContent().equals(allowedReaction) && issueReaction.getUser().getLogin().equals(loginUid)) {
+							myReaction = issueReaction;
+							break;
+						}
+					}
+
+					ReactionAction reactionAction;
+
+					if(myReaction != null) {
+
+						reactionButton.setCardBackgroundColor(AppUtil.getColorFromAttribute(context, R.attr.inputSelectedColor));
+						reactionAction = ReactionAction.REMOVE;
+					} else {
+						reactionAction = ReactionAction.ADD;
+					}
+
+					reactionButton.setOnClickListener(v -> new Thread(() -> {
+
+						try {
+							if(react(repoOwner, repoName, reactionType, reactionAction, new IssueReaction(allowedReaction), id)) {
+								v.post(() -> onInteractedListener.onInteracted());
+							}
+						} catch(IOException ignored) {}
+
+					}).start());
+
+					Emoji emoji = EmojiManager.getForAlias(allowedReaction);
+
+					((TextView) reactionButton.findViewById(R.id.symbol)).setText((emoji == null) ? allowedReaction : emoji.getUnicode());
+					root.post(() -> root.addView(reactionButton));
+
+				}
+
+			} catch(IOException ignored) {}
+
+		}).start();
+
+		setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+		addView(root);
+
+	}
+
+	private boolean react(String repoOwner, String repoName, ReactionType reactionType, ReactionAction reactionAction, IssueReaction issueReaction, int id) throws IOException {
+
+		Response<?> response = null;
+
+		switch(reactionType) {
+
+			case ISSUE:
+				switch(reactionAction) {
+
+					case ADD:
+						response = RetrofitClient
+							.getApiInterface(getContext())
+							.setIssueReaction(Authorization.get(getContext()), repoOwner, repoName, id, issueReaction)
+							.execute();
+						break;
+
+
+					case REMOVE:
+						response = RetrofitClient
+							.getApiInterface(getContext())
+							.removeIssueReaction(Authorization.get(getContext()), repoOwner, repoName, id, issueReaction)
+							.execute();
+						break;
+
+				}
+				break;
+
+			case COMMENT:
+				switch(reactionAction) {
+
+					case ADD:
+						response = RetrofitClient
+							.getApiInterface(getContext())
+							.setIssueCommentReaction(Authorization.get(getContext()), repoOwner, repoName, id, issueReaction)
+							.execute();
+						break;
+
+
+					case REMOVE:
+						response = RetrofitClient
+							.getApiInterface(getContext())
+							.removeIssueCommentReaction(Authorization.get(getContext()), repoOwner, repoName, id, issueReaction)
+							.execute();
+						break;
+
+				}
+				break;
+
+		}
+
+		return response.isSuccessful();
+
+	}
+
+	private List<IssueReaction> getReactions(String repoOwner, String repoName, ReactionType reactionType, int id) throws IOException {
+
+		Response<List<IssueReaction>> response = null;
+
+		switch(reactionType) {
+
+			case ISSUE:
+				response = RetrofitClient
+					.getApiInterface(getContext())
+					.getIssueReactions(Authorization.get(getContext()), repoOwner, repoName, id)
+					.execute();
+				break;
+
+			case COMMENT:
+				response = RetrofitClient
+					.getApiInterface(getContext())
+					.getIssueCommentReactions(Authorization.get(getContext()), repoOwner, repoName, id)
+					.execute();
+				break;
+
+		}
+
+		if(response.isSuccessful() && response.body() != null)
+			return response.body();
+		else
+			return Collections.emptyList();
+
+	}
+
+	private List<String> getAllowedReactions() throws IOException {
+
+		List<String> allowedReactions = new ArrayList<>();
+
+		Response<UISettings> response = RetrofitClient
+			.getApiInterface(getContext())
+			.getUISettings(Authorization.get(getContext()))
+			.execute();
+
+		if(response.isSuccessful() && response.body() != null) {
+			allowedReactions.addAll(Arrays.asList(response.body().getAllowed_reactions()));
+		} else {
+			allowedReactions.addAll(Arrays.asList("+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes"));
+		}
+
+		return allowedReactions;
+
+	}
+
+	public void setOnInteractedListener(OnInteractedListener onInteractedListener) {
+		this.onInteractedListener = onInteractedListener;
+	}
+
+	public interface OnInteractedListener { void onInteracted(); }
+
+}
diff --git a/app/src/main/res/layout/activity_issue_detail.xml b/app/src/main/res/layout/activity_issue_detail.xml
index 0d642551..650d8b18 100644
--- a/app/src/main/res/layout/activity_issue_detail.xml
+++ b/app/src/main/res/layout/activity_issue_detail.xml
@@ -225,6 +225,14 @@
 
                         </RelativeLayout>
 
+                        <LinearLayout
+                            android:id="@+id/commentReactionBadges"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/issueTimeFrame"
+                            android:layout_marginTop="10dp"
+                            android:orientation="horizontal" />
+
                     </RelativeLayout>
 
                     <View
diff --git a/app/src/main/res/layout/bottom_sheet_issue_comments.xml b/app/src/main/res/layout/bottom_sheet_issue_comments.xml
index a51e12b8..c7ad7a76 100644
--- a/app/src/main/res/layout/bottom_sheet_issue_comments.xml
+++ b/app/src/main/res/layout/bottom_sheet_issue_comments.xml
@@ -4,10 +4,10 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="?attr/primaryBackgroundColor"
     android:orientation="vertical"
     android:paddingTop="6dp"
-    android:paddingBottom="12dp"
-    android:background="?attr/primaryBackgroundColor">
+    android:paddingBottom="12dp">
 
     <androidx.core.widget.NestedScrollView
         android:layout_width="match_parent"
@@ -15,8 +15,15 @@
 
         <LinearLayout
             android:layout_width="match_parent"
-            android:orientation="vertical"
-            android:layout_height="wrap_content">
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:id="@+id/commentReactionButtons"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:orientation="horizontal" />
 
             <TextView
                 android:id="@+id/commentMenuEdit"
diff --git a/app/src/main/res/layout/bottom_sheet_single_issue.xml b/app/src/main/res/layout/bottom_sheet_single_issue.xml
index 84bf3c31..7d414109 100644
--- a/app/src/main/res/layout/bottom_sheet_single_issue.xml
+++ b/app/src/main/res/layout/bottom_sheet_single_issue.xml
@@ -17,6 +17,13 @@
             android:orientation="vertical"
             android:layout_height="wrap_content">
 
+            <LinearLayout
+                android:id="@+id/commentReactionButtons"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:orientation="horizontal" />
+
             <TextView
                 android:id="@+id/openFilesDiff"
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/layout_reaction_badge.xml b/app/src/main/res/layout/layout_reaction_badge.xml
new file mode 100644
index 00000000..9378d6f6
--- /dev/null
+++ b/app/src/main/res/layout/layout_reaction_badge.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginEnd="5dp"
+    app:cardBackgroundColor="?attr/inputBackgroundColor"
+    app:cardCornerRadius="10dp"
+    app:cardElevation="0dp">
+
+    <TextView
+        android:id="@+id/symbol"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="8dp"
+        android:paddingTop="3dp"
+        android:paddingRight="8dp"
+        android:paddingBottom="3dp"
+        android:textColor="?attr/primaryTextColor"
+        android:textSize="15sp"
+        tools:text="👍" />
+
+</androidx.cardview.widget.CardView>
diff --git a/app/src/main/res/layout/layout_reaction_button.xml b/app/src/main/res/layout/layout_reaction_button.xml
new file mode 100644
index 00000000..3fe143a9
--- /dev/null
+++ b/app/src/main/res/layout/layout_reaction_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginEnd="5dp"
+    app:cardBackgroundColor="?attr/inputBackgroundColor"
+    app:cardCornerRadius="25sp"
+    app:cardElevation="0dp"
+    app:contentPadding="10dp">
+
+    <TextView
+        android:id="@+id/symbol"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textColor="?attr/primaryTextColor"
+        android:textSize="20sp"
+        tools:text="👍" />
+
+</androidx.cardview.widget.CardView>
diff --git a/app/src/main/res/layout/list_issue_comments.xml b/app/src/main/res/layout/list_issue_comments.xml
index c27ba22a..07525a85 100644
--- a/app/src/main/res/layout/list_issue_comments.xml
+++ b/app/src/main/res/layout/list_issue_comments.xml
@@ -71,4 +71,11 @@
         android:textIsSelectable="true"
         android:textSize="14sp" />
 
+    <LinearLayout
+        android:id="@+id/commentReactionBadges"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dp"
+        android:orientation="horizontal" />
+
 </LinearLayout>
diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml
index a669193b..bd16df27 100644
--- a/app/src/main/res/values-v23/themes.xml
+++ b/app/src/main/res/values-v23/themes.xml
@@ -17,6 +17,7 @@
         <item name="primaryTextColor">@color/lightThemeTextColor</item>
         <item name="primaryBackgroundColor">@color/lightThemeBackground</item>
         <item name="inputBackgroundColor">@color/lightThemeInputBackground</item>
+        <item name="inputSelectedColor">@color/lightThemInputSelected</item>
         <item name="inputTextColor">@color/lightThemeInputTextColor</item>
         <item name="checkboxStyle">@style/AppThemeLightCheckBoxStyle</item>
         <item name="selectedTextColor">@color/darkGreen</item>
@@ -57,6 +58,7 @@
         <item name="primaryTextColor">@color/retroThemeTextColor</item>
         <item name="primaryBackgroundColor">@color/retroThemeBackground</item>
         <item name="inputBackgroundColor">@color/retroThemeInputBackground</item>
+        <item name="inputSelectedColor">@color/retroThemeInputSelected</item>
         <item name="inputTextColor">@color/retroThemeInputTextColor</item>
         <item name="checkboxStyle">@style/AppThemeRetroCheckBoxStyle</item>
         <item name="selectedTextColor">@color/retroThemeColorPrimary</item>
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 268fa6c6..9817fbe9 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -6,6 +6,7 @@
     <attr name="primaryTextColor" format="reference"/>
     <attr name="primaryBackgroundColor" format="reference" />
     <attr name="inputBackgroundColor" format="reference" />
+    <attr name="inputSelectedColor" format="reference" />
     <attr name="hintColor" format="reference" />
     <attr name="inputTextColor" format="reference" />
     <attr name="selectedTextColor" format="reference" />
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 04a2448f..b024bc12 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -12,6 +12,7 @@
     <color name="btnBackground">#009486</color>
     <color name="btnTextColor">#ffffff</color>
     <color name="inputBackground">#1d1d1d</color>
+    <color name="inputSelected">#3A3A3A</color>
     <color name="toastBackground">#1d1d1d</color>
     <color name="releasePre">#f2711c</color>
     <color name="releaseStable">@color/btnBackground</color>
@@ -35,7 +36,8 @@
     <color name="lightThemeDiffSelectedColor">#e0e0e0</color>
     <color name="lightThemeTextColor">#646565</color>
     <color name="lightThemeBackground">#f9f9f9</color>
-    <color name="lightThemeInputBackground">#f2f2f2</color>
+    <color name="lightThemeInputBackground">#EFEFEF</color>
+    <color name="lightThemInputSelected">#C9CCCC</color>
     <color name="lightThemeInputTextColor">#212121</color>
     <color name="lightThemeDividerColor">#dbdbdb</color>
 
@@ -45,6 +47,7 @@
     <color name="retroThemeTextColor">#212f3c</color>
     <color name="retroThemeBackground">#fcfcfc</color>
     <color name="retroThemeInputBackground">#f2f2f2</color>
+    <color name="retroThemeInputSelected">#D3D3D3</color>
     <color name="retroThemeInputTextColor">#6200EE</color>
     <color name="retroThemeDividerColor">#dbdbdb</color>
     <color name="retroThemeColorPrimary">#6200EE</color>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 44401d8b..0ae51044 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -17,6 +17,7 @@
         <item name="primaryTextColor">@color/colorWhite</item>
         <item name="primaryBackgroundColor">@color/colorPrimary</item>
         <item name="inputBackgroundColor">@color/inputBackground</item>
+        <item name="inputSelectedColor">@color/inputSelected</item>
         <item name="inputTextColor">@color/colorWhite</item>
         <item name="checkboxStyle">@style/AppThemeCheckBoxStyle</item>
         <item name="selectedTextColor">@color/darkGreen</item>
@@ -56,6 +57,7 @@
         <item name="primaryTextColor">@color/lightThemeTextColor</item>
         <item name="primaryBackgroundColor">@color/lightThemeBackground</item>
         <item name="inputBackgroundColor">@color/lightThemeInputBackground</item>
+        <item name="inputSelectedColor">@color/lightThemInputSelected</item>
         <item name="inputTextColor">@color/lightThemeInputTextColor</item>
         <item name="checkboxStyle">@style/AppThemeLightCheckBoxStyle</item>
         <item name="selectedTextColor">@color/darkGreen</item>
@@ -95,6 +97,7 @@
         <item name="primaryTextColor">@color/retroThemeTextColor</item>
         <item name="primaryBackgroundColor">@color/retroThemeBackground</item>
         <item name="inputBackgroundColor">@color/retroThemeInputBackground</item>
+        <item name="inputSelectedColor">@color/retroThemeInputSelected</item>
         <item name="inputTextColor">@color/retroThemeInputTextColor</item>
         <item name="checkboxStyle">@style/AppThemeRetroCheckBoxStyle</item>
         <item name="selectedTextColor">@color/retroThemeColorPrimary</item>
