diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 231becca..6a490eb2 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -855,7 +855,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt binding.toolbarSearch.setOnSearchClickListener(v -> binding.tabLayout.setVisibility(View.VISIBLE)); //For receiving data from other activities LocalBroadcastManager.getInstance(BaseMainActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); - if (emojis == null || !emojis.containsKey(BaseMainActivity.currentInstance)) { + if (emojis == null || !emojis.containsKey(BaseMainActivity.currentInstance) || emojis.get(BaseMainActivity.currentInstance) == null) { new Thread(() -> { try { emojis.put(currentInstance, new EmojiInstance(BaseMainActivity.this).getEmojiList(BaseMainActivity.currentInstance)); diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/PleromaAPI.java b/app/src/main/java/app/fedilab/android/client/endpoints/PleromaAPI.java new file mode 100644 index 00000000..d1309bb2 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/endpoints/PleromaAPI.java @@ -0,0 +1,40 @@ +package app.fedilab.android.client.endpoints; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import retrofit2.Call; +import retrofit2.http.DELETE; +import retrofit2.http.Header; +import retrofit2.http.PUT; +import retrofit2.http.Path; + +public interface PleromaAPI { + + + @PUT("pleroma/statuses/{id}/reactions/{name}") + Call addReaction( + @Header("Authorization") String app_token, + @Path("id") String id, + @Path("name") String name + ); + + @DELETE("pleroma/statuses/{id}/reactions/{name}") + Call removeReaction( + @Header("Authorization") String app_token, + @Path("id") String id, + @Path("name") String name + ); + +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Pleroma.java b/app/src/main/java/app/fedilab/android/client/entities/api/Pleroma.java new file mode 100644 index 00000000..bee1912b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Pleroma.java @@ -0,0 +1,25 @@ +package app.fedilab.android.client.entities.api; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.List; + +public class Pleroma implements Serializable { + @SerializedName("emoji_reactions") + public List emoji_reactions; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Reaction.java b/app/src/main/java/app/fedilab/android/client/entities/api/Reaction.java index 39635524..028f93ef 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Reaction.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Reaction.java @@ -16,7 +16,9 @@ package app.fedilab.android.client.entities.api; import com.google.gson.annotations.SerializedName; -public class Reaction { +import java.io.Serializable; + +public class Reaction implements Serializable { @SerializedName("name") public String name; @SerializedName("count") diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java index b75125e1..144eea3e 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java @@ -89,6 +89,8 @@ public class Status implements Serializable, Cloneable { public Card card; @SerializedName("poll") public Poll poll; + @SerializedName("pleroma") + public Pleroma pleroma; public Attachment art_attachment; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java index 6a6d9fad..abf0c05f 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java @@ -76,17 +76,18 @@ public class AnnouncementAdapter extends RecyclerView.Adapter 0) { ReactionAdapter reactionAdapter = new ReactionAdapter(announcement.id, announcement.reactions); - holder.binding.reactionsView.setAdapter(reactionAdapter); + holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter); LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); - holder.binding.reactionsView.setLayoutManager(layoutManager); + holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager); } else { - holder.binding.reactionsView.setAdapter(null); + holder.binding.layoutReactions.reactionsView.setAdapter(null); } holder.binding.content.setText( announcement.getSpanContent(context, @@ -108,11 +109,11 @@ public class AnnouncementAdapter extends RecyclerView.Adapter { + holder.binding.layoutReactions.statusEmoji.setOnClickListener(v -> { EmojiManager.install(new EmojiOneProvider()); - final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.statusEmoji).setOnEmojiPopupDismissListener(() -> { + final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.layoutReactions.statusEmoji).setOnEmojiPopupDismissListener(() -> { InputMethodManager imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(holder.binding.statusEmoji.getWindowToken(), 0); + imm.hideSoftInputFromWindow(holder.binding.layoutReactions.statusEmoji.getWindowToken(), 0); }).setOnEmojiClickListener((emoji, imageView) -> { String emojiStr = imageView.getUnicode(); boolean alreadyAdded = false; @@ -142,10 +143,10 @@ public class AnnouncementAdapter extends RecyclerView.Adapter { + holder.binding.layoutReactions.statusAddCustomEmoji.setOnClickListener(v -> { final AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle()); int paddingPixel = 15; float density = context.getResources().getDisplayMetrics().density; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/EmojiAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/EmojiAdapter.java index 9f849b1f..7a3819ca 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/EmojiAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/EmojiAdapter.java @@ -42,7 +42,7 @@ public class EmojiAdapter extends BaseAdapter { } public int getCount() { - return emojiList.size(); + return emojiList == null ? 0 : emojiList.size(); } public Emoji getItem(int position) { diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index d0f2cc4c..628f39d8 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -15,6 +15,8 @@ package app.fedilab.android.ui.drawer; * see . */ +import static android.content.Context.INPUT_METHOD_SERVICE; +import static app.fedilab.android.BaseMainActivity.emojis; import static app.fedilab.android.BaseMainActivity.regex_home; import static app.fedilab.android.BaseMainActivity.regex_local; import static app.fedilab.android.BaseMainActivity.regex_public; @@ -46,7 +48,9 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; +import android.widget.GridView; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RelativeLayout; @@ -66,6 +70,7 @@ import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; @@ -78,6 +83,9 @@ import com.github.stom79.mytransl.client.HttpsConnectionException; import com.github.stom79.mytransl.client.Results; import com.github.stom79.mytransl.translate.Params; import com.github.stom79.mytransl.translate.Translate; +import com.vanniktech.emoji.EmojiManager; +import com.vanniktech.emoji.EmojiPopup; +import com.vanniktech.emoji.one.EmojiOneProvider; import com.varunest.sparkbutton.SparkButton; import java.lang.ref.WeakReference; @@ -93,6 +101,7 @@ import app.fedilab.android.R; import app.fedilab.android.activities.ComposeActivity; import app.fedilab.android.activities.ContextActivity; import app.fedilab.android.activities.CustomSharingActivity; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MediaActivity; import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.activities.ReportActivity; @@ -100,7 +109,9 @@ import app.fedilab.android.activities.StatusInfoActivity; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.Notification; import app.fedilab.android.client.entities.api.Poll; +import app.fedilab.android.client.entities.api.Reaction; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.app.Account; import app.fedilab.android.client.entities.app.StatusCache; import app.fedilab.android.client.entities.app.StatusDraft; import app.fedilab.android.client.entities.app.Timeline; @@ -124,6 +135,7 @@ import app.fedilab.android.ui.fragment.timeline.FragmentMastodonContext; import app.fedilab.android.viewmodel.mastodon.AccountsVM; import app.fedilab.android.viewmodel.mastodon.SearchVM; import app.fedilab.android.viewmodel.mastodon.StatusesVM; +import app.fedilab.android.viewmodel.pleroma.ActionsVM; import es.dmoral.toasty.Toasty; import jp.wasabeef.glide.transformations.BlurTransformation; @@ -313,6 +325,116 @@ public class StatusAdapter extends RecyclerView.Adapter boolean fullAttachement = sharedpreferences.getBoolean(context.getString(R.string.SET_FULL_PREVIEW), false); boolean displayBookmark = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_BOOKMARK), false); + if (MainActivity.currentAccount != null && MainActivity.currentAccount.api == Account.API.PLEROMA) { + holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE); + if (status.pleroma != null && status.pleroma.emoji_reactions != null && status.pleroma.emoji_reactions.size() > 0) { + ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.pleroma.emoji_reactions); + holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter); + LinearLayoutManager layoutManager + = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); + holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager); + } else { + holder.binding.layoutReactions.reactionsView.setAdapter(null); + } + holder.binding.layoutReactions.statusEmoji.setOnClickListener(v -> { + EmojiManager.install(new EmojiOneProvider()); + final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.layoutReactions.statusEmoji).setOnEmojiPopupDismissListener(() -> { + InputMethodManager imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(holder.binding.layoutReactions.statusEmoji.getWindowToken(), 0); + }).setOnEmojiClickListener((emoji, imageView) -> { + String emojiStr = imageView.getUnicode(); + boolean alreadyAdded = false; + for (Reaction reaction : status.pleroma.emoji_reactions) { + if (reaction.name.compareTo(emojiStr) == 0) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.pleroma.emoji_reactions.remove(reaction); + } + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + break; + } + } + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + status.pleroma.emoji_reactions.add(0, reaction); + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + } + ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); + if (alreadyAdded) { + actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } + }) + .build(holder.binding.layoutReactions.fakeEdittext); + emojiPopup.toggle(); + }); + holder.binding.layoutReactions.statusAddCustomEmoji.setOnClickListener(v -> { + + final AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle()); + int paddingPixel = 15; + float density = context.getResources().getDisplayMetrics().density; + int paddingDp = (int) (paddingPixel * density); + builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builder.setTitle(R.string.insert_emoji); + AlertDialog alertDialogEmoji = null; + if (emojis != null && emojis.size() > 0 && emojis.get(BaseMainActivity.currentInstance) != null) { + GridView gridView = new GridView(context); + gridView.setAdapter(new EmojiAdapter(emojis.get(BaseMainActivity.currentInstance))); + gridView.setNumColumns(5); + AlertDialog finalAlertDialogEmoji = alertDialogEmoji; + gridView.setOnItemClickListener((parent, view, index, id) -> { + String emojiStr = emojis.get(BaseMainActivity.currentInstance).get(index).shortcode; + String url = emojis.get(BaseMainActivity.currentInstance).get(index).url; + String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url; + boolean alreadyAdded = false; + for (Reaction reaction : status.pleroma.emoji_reactions) { + if (reaction.name.compareTo(emojiStr) == 0) { + alreadyAdded = true; + reaction.count = (reaction.count - 1); + if (reaction.count == 0) { + status.pleroma.emoji_reactions.remove(reaction); + } + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + break; + } + } + if (!alreadyAdded) { + Reaction reaction = new Reaction(); + reaction.me = true; + reaction.count = 1; + reaction.name = emojiStr; + reaction.url = url; + reaction.static_url = static_url; + status.pleroma.emoji_reactions.add(0, reaction); + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + } + ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); + if (alreadyAdded) { + actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } else { + actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); + } + if (finalAlertDialogEmoji != null) { + finalAlertDialogEmoji.dismiss(); + } + }); + gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp); + builder.setView(gridView); + } else { + TextView textView = new TextView(context); + textView.setText(context.getString(R.string.no_emoji)); + textView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp); + builder.setView(textView); + } + alertDialogEmoji = builder.show(); + }); + } + int truncate_toots_size = sharedpreferences.getInt(context.getString(R.string.SET_TRUNCATE_TOOTS_SIZE), 0); // boolean display_video_preview = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_VIDEO_PREVIEWS), true); // boolean isModerator = sharedpreferences.getBoolean(Helper.PREF_IS_MODERATOR, false); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index c540f269..53fd1aae 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -174,9 +174,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } public void scrollToTop() { - binding.swipeContainer.setRefreshing(true); - flagLoading = false; - route(DIRECTION.SCROLL_TOP, true); + if (binding != null) { + binding.swipeContainer.setRefreshing(true); + flagLoading = false; + route(DIRECTION.SCROLL_TOP, true); + } } public View onCreateView(@NonNull LayoutInflater inflater, diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AnnouncementsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AnnouncementsVM.java index 743cd32a..06eccbfd 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AnnouncementsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AnnouncementsVM.java @@ -23,9 +23,6 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import java.util.List; import java.util.concurrent.TimeUnit; @@ -54,7 +51,6 @@ public class AnnouncementsVM extends AndroidViewModel { } private MastodonAnnouncementsService init(@NonNull String instance) { - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://" + instance + "/api/v1/") .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) diff --git a/app/src/main/java/app/fedilab/android/viewmodel/pleroma/ActionsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/pleroma/ActionsVM.java new file mode 100644 index 00000000..c57b52ea --- /dev/null +++ b/app/src/main/java/app/fedilab/android/viewmodel/pleroma/ActionsVM.java @@ -0,0 +1,101 @@ +package app.fedilab.android.viewmodel.pleroma; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.MutableLiveData; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import app.fedilab.android.client.endpoints.PleromaAPI; +import app.fedilab.android.client.entities.api.Announcement; +import app.fedilab.android.helper.Helper; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ActionsVM extends AndroidViewModel { + + final OkHttpClient okHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .connectTimeout(60, TimeUnit.SECONDS) + .callTimeout(60, TimeUnit.SECONDS) + .proxy(Helper.getProxy(getApplication().getApplicationContext())) + .build(); + private MutableLiveData announcementMutableLiveData; + private MutableLiveData> announcementListMutableLiveData; + + public ActionsVM(@NonNull Application application) { + super(application); + } + + private PleromaAPI init(@NonNull String instance) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance + "/api/v1/") + .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) + .client(okHttpClient) + .build(); + return retrofit.create(PleromaAPI.class); + } + + /** + * React to an announcement with an emoji. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @param id Local ID of an announcement + * @param name Unicode emoji, or shortcode of custom emoji + */ + public void addReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) { + PleromaAPI pleromaAPI = init(instance); + new Thread(() -> { + Call addReactionCall = pleromaAPI.addReaction(token, id, name); + if (addReactionCall != null) { + try { + addReactionCall.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + /** + * Undo a react emoji to an announcement. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @param id Local ID of an announcement + * @param name Unicode emoji, or shortcode of custom emoji + */ + public void removeReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) { + PleromaAPI pleromaAPI = init(instance); + new Thread(() -> { + Call removeReactionCall = pleromaAPI.removeReaction(token, id, name); + if (removeReactionCall != null) { + try { + removeReactionCall.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/app/src/main/res/layout/drawer_announcement.xml b/app/src/main/res/layout/drawer_announcement.xml index fdb2d27e..c0c40807 100644 --- a/app/src/main/res/layout/drawer_announcement.xml +++ b/app/src/main/res/layout/drawer_announcement.xml @@ -59,64 +59,9 @@ tools:maxLines="10" tools:text="@tools:sample/lorem/random" /> - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/drawer_status.xml b/app/src/main/res/layout/drawer_status.xml index c73a3c89..b6caece7 100644 --- a/app/src/main/res/layout/drawer_status.xml +++ b/app/src/main/res/layout/drawer_status.xml @@ -601,6 +601,12 @@ + + diff --git a/app/src/main/res/layout/layout_reactions.xml b/app/src/main/res/layout/layout_reactions.xml new file mode 100644 index 00000000..46e4b1a0 --- /dev/null +++ b/app/src/main/res/layout/layout_reactions.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file