diff --git a/app/build.gradle b/app/build.gradle index afcaf7452..2fcb2c6fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -68,13 +68,15 @@ android { 'src/main/res/layouts/mastodon', 'src/main/res/layouts/peertube', 'src/main/res/layouts', - 'src/main/layout', + 'src/main/res/drawables/mastodon', 'src/main/res/drawables/peertube', 'src/main/res/drawables', + 'src/main/res/menus/mastodon', 'src/main/res/menus/peertube', 'src/main/res/menus', + 'src/main/res' ] } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 699f5e66d..399f8770b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -196,6 +196,9 @@ + diff --git a/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java b/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java new file mode 100644 index 000000000..5af65c456 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java @@ -0,0 +1,131 @@ +package app.fedilab.android.mastodon.activities; +/* Copyright 2023 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 static app.fedilab.android.BaseMainActivity.currentAccount; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.TypedValue; +import android.view.MenuItem; + +import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.preference.PreferenceManager; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.databinding.ActivityDirectMessageBinding; +import app.fedilab.android.mastodon.client.entities.api.Status; +import app.fedilab.android.mastodon.client.entities.app.StatusCache; +import app.fedilab.android.mastodon.exception.DBException; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.helper.MastodonHelper; +import app.fedilab.android.mastodon.ui.drawer.StatusAdapter; +import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonDirectMessage; +import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM; + +public class DirectMessageActivity extends BaseActivity implements FragmentMastodonDirectMessage.FirstMessage { + + public static boolean expand; + public static boolean displayCW; + + Fragment currentFragment; + private Status firstMessage; + private String remote_instance; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ActivityDirectMessageBinding binding = ActivityDirectMessageBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + setSupportActionBar(binding.toolbar); + ActionBar actionBar = getSupportActionBar(); + //Remove title + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + } + binding.title.setText(R.string.context_conversation); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); + float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); + binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + } + Bundle b = getIntent().getExtras(); + displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false); + Status focusedStatus = null; // or other values + if (b != null) { + focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS); + remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null); + } + if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) { + finish(); + return; + } + MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_STATUS, focusedStatus); + bundle.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance); + FragmentMastodonDirectMessage FragmentMastodonDirectMessage = new FragmentMastodonDirectMessage(); + FragmentMastodonDirectMessage.firstMessage = this; + currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, FragmentMastodonDirectMessage, bundle, null, null); + StatusesVM timelinesVM = new ViewModelProvider(DirectMessageActivity.this).get(StatusesVM.class); + timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(DirectMessageActivity.this, status -> { + if (status != null) { + StatusCache statusCache = new StatusCache(); + statusCache.instance = BaseMainActivity.currentInstance; + statusCache.user_id = BaseMainActivity.currentUserID; + statusCache.status = status; + statusCache.status_id = status.id; + //Update cache + new Thread(() -> { + try { + new StatusCache(getApplication()).updateIfExists(statusCache); + Handler mainHandler = new Handler(Looper.getMainLooper()); + //Update UI + Runnable myRunnable = () -> StatusAdapter.sendAction(DirectMessageActivity.this, Helper.ARG_STATUS_ACTION, status, null); + mainHandler.post(myRunnable); + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); + } + }); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return true; + } + + @Override + public void get(Status status) { + firstMessage = status; + invalidateOptionsMenu(); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java index 01e1e76d1..3192d2863 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java @@ -47,6 +47,7 @@ import app.fedilab.android.R; import app.fedilab.android.databinding.DrawerConversationBinding; import app.fedilab.android.databinding.ThumbnailBinding; import app.fedilab.android.mastodon.activities.ContextActivity; +import app.fedilab.android.mastodon.activities.DirectMessageActivity; import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Conversation; @@ -210,15 +211,26 @@ public class ConversationAdapter extends RecyclerView.Adapter { - Intent intent = new Intent(context, ContextActivity.class); + Intent intent; + if (chatMode) { + intent = new Intent(context, DirectMessageActivity.class); + } else { + intent = new Intent(context, ContextActivity.class); + } intent.putExtra(Helper.ARG_STATUS, conversation.last_status); context.startActivity(intent); }); holder.binding.attachmentsListContainer.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_UP) { - Intent intent = new Intent(context, ContextActivity.class); + Intent intent; + if (chatMode) { + intent = new Intent(context, DirectMessageActivity.class); + } else { + intent = new Intent(context, ContextActivity.class); + } intent.putExtra(Helper.ARG_STATUS, conversation.last_status); context.startActivity(intent); } diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java new file mode 100644 index 000000000..9e21a106a --- /dev/null +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java @@ -0,0 +1,106 @@ +package app.fedilab.android.mastodon.ui.drawer; +/* Copyright 2023 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.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.WeakReference; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.databinding.DrawerStatusChatBinding; +import app.fedilab.android.mastodon.client.entities.api.Status; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.helper.MastodonHelper; +import app.fedilab.android.mastodon.helper.ThemeHelper; + +public class StatusDirectMessageAdapter extends RecyclerView.Adapter { + + private final List statusList; + private Context context; + private RecyclerView mRecyclerView; + + public StatusDirectMessageAdapter(List data) { + this.statusList = data; + } + + @NotNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) { + context = parent.getContext(); + DrawerStatusChatBinding itemBinding = DrawerStatusChatBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new StatusChatViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + + StatusChatViewHolder holder = (StatusChatViewHolder) viewHolder; + Status status = statusList.get(position); + + holder.binding.messageContent.setText( + status.getSpanContent(context, + new WeakReference<>(holder.binding.messageContent), + () -> mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))), + TextView.BufferType.SPANNABLE); + + MastodonHelper.loadPPMastodon(holder.binding.userPp, status.account); + holder.binding.date.setText(Helper.longDateToString(status.created_at)); + //Owner account + int textColor; + if (status.account.id.equals(MainActivity.currentUserID)) { + holder.binding.mainContainer.setBackgroundResource(R.drawable.bubble_right_tail); + textColor = R.attr.colorOnPrimary; + } else { + holder.binding.mainContainer.setBackgroundResource(R.drawable.bubble_left_tail); + textColor = R.attr.colorOnSecondary; + } + holder.binding.date.setTextColor(ThemeHelper.getAttColor(context, textColor)); + holder.binding.messageContent.setTextColor(ThemeHelper.getAttColor(context, textColor)); + holder.binding.userName.setTextColor(ThemeHelper.getAttColor(context, textColor)); + } + + @Override + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + + mRecyclerView = recyclerView; + } + + + @Override + public int getItemCount() { + return statusList.size(); + } + + public static class StatusChatViewHolder extends RecyclerView.ViewHolder { + DrawerStatusChatBinding binding; + + StatusChatViewHolder(DrawerStatusChatBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonDirectMessage.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonDirectMessage.java new file mode 100644 index 000000000..c984f68e9 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonDirectMessage.java @@ -0,0 +1,143 @@ +package app.fedilab.android.mastodon.ui.fragment.timeline; +/* Copyright 2023 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.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.databinding.FragmentPaginationBinding; +import app.fedilab.android.mastodon.client.entities.api.Context; +import app.fedilab.android.mastodon.client.entities.api.Status; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.ui.drawer.StatusDirectMessageAdapter; +import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM; + + +public class FragmentMastodonDirectMessage extends Fragment { + + + public FirstMessage firstMessage; + private FragmentPaginationBinding binding; + private StatusesVM statusesVM; + private List statuses; + private StatusDirectMessageAdapter statusDirectMessageAdapter; + //Handle actions that can be done in other fragments + private Status focusedStatus; + private Status firstStatus; + private boolean pullToRefresh; + private String user_token, user_instance; + + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + + focusedStatus = null; + pullToRefresh = false; + if (getArguments() != null) { + focusedStatus = (Status) getArguments().getSerializable(Helper.ARG_STATUS); + } + user_instance = MainActivity.currentInstance; + user_token = MainActivity.currentToken; + + if (focusedStatus == null) { + getChildFragmentManager().beginTransaction().remove(this).commit(); + } + binding = FragmentPaginationBinding.inflate(inflater, container, false); + statusesVM = new ViewModelProvider(FragmentMastodonDirectMessage.this).get(StatusesVM.class); + binding.recyclerView.setNestedScrollingEnabled(true); + this.statuses = new ArrayList<>(); + this.statuses.add(focusedStatus); + statusDirectMessageAdapter = new StatusDirectMessageAdapter(this.statuses); + binding.swipeContainer.setRefreshing(false); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(statusDirectMessageAdapter); + binding.swipeContainer.setOnRefreshListener(() -> { + if (this.statuses.size() > 0) { + binding.swipeContainer.setRefreshing(true); + pullToRefresh = true; + statusesVM.getContext(user_instance, user_token, focusedStatus.id) + .observe(getViewLifecycleOwner(), this::initializeContextView); + } + }); + if (focusedStatus != null) { + statusesVM.getContext(user_instance, user_token, focusedStatus.id) + .observe(getViewLifecycleOwner(), this::initializeContextView); + } + return binding.getRoot(); + } + + + /** + * Intialize the common view for the context + * + * @param context {@link Context} + */ + private void initializeContextView(final Context context) { + + if (context == null) { + Helper.sendToastMessage(requireActivity(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error)); + return; + } + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + if (pullToRefresh) { + pullToRefresh = false; + int size = this.statuses.size(); + statuses.clear(); + statusDirectMessageAdapter.notifyItemRangeRemoved(0, size); + statuses.add(focusedStatus); + } + if (context.ancestors.size() > 0) { + firstStatus = context.ancestors.get(0); + } else { + firstStatus = statuses.get(0); + } + if (firstMessage != null) { + firstMessage.get(firstStatus); + } + + int statusPosition = context.ancestors.size(); + //Build the array of statuses + statuses.addAll(0, context.ancestors); + statusDirectMessageAdapter.notifyItemRangeInserted(0, statusPosition); + statuses.addAll(statusPosition + 1, context.descendants); + statusDirectMessageAdapter.notifyItemRangeInserted(statusPosition + 1, context.descendants.size()); + if (binding.recyclerView.getItemDecorationCount() > 0) { + for (int i = 0; i < binding.recyclerView.getItemDecorationCount(); i++) { + binding.recyclerView.removeItemDecorationAt(i); + } + } + binding.swipeContainer.setRefreshing(false); + binding.recyclerView.scrollToPosition(statusPosition); + } + + public interface FirstMessage { + void get(Status status); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawables/mastodon/drawable/browser_calls_blocked.xml b/app/src/main/res/drawables/mastodon/drawable/browser_calls_blocked.xml deleted file mode 100644 index 783a0a05b..000000000 --- a/app/src/main/res/drawables/mastodon/drawable/browser_calls_blocked.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawables/mastodon/drawable/bubble_left.xml b/app/src/main/res/drawables/mastodon/drawable/bubble_left.xml index 9ad4629d4..125d8c6bb 100644 --- a/app/src/main/res/drawables/mastodon/drawable/bubble_left.xml +++ b/app/src/main/res/drawables/mastodon/drawable/bubble_left.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/res/drawables/mastodon/drawable/bubble_left_tail.xml b/app/src/main/res/drawables/mastodon/drawable/bubble_left_tail.xml index 6a1b0f51b..7f93abfd1 100644 --- a/app/src/main/res/drawables/mastodon/drawable/bubble_left_tail.xml +++ b/app/src/main/res/drawables/mastodon/drawable/bubble_left_tail.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/res/drawables/mastodon/drawable/bubble_right_tail.xml b/app/src/main/res/drawables/mastodon/drawable/bubble_right_tail.xml index 29fa08207..fa68f846a 100644 --- a/app/src/main/res/drawables/mastodon/drawable/bubble_right_tail.xml +++ b/app/src/main/res/drawables/mastodon/drawable/bubble_right_tail.xml @@ -1,4 +1,3 @@ - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml b/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml index 8926bbaf0..935b0b4a7 100644 --- a/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml +++ b/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml @@ -2,6 +2,7 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/menus/mastodon/menu/main_webview.xml b/app/src/main/res/menus/mastodon/menu/main_webview.xml index abc6b2473..d9977f825 100644 --- a/app/src/main/res/menus/mastodon/menu/main_webview.xml +++ b/app/src/main/res/menus/mastodon/menu/main_webview.xml @@ -1,14 +1,9 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44ae065a1..b0fc7f0fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1029,6 +1029,8 @@ SET_DYNAMICCOLOR SET_CARDVIEW SET_CUSTOMIZE_LIGHT_COLORS + SET_CHAT_FOR_CONVERSATION + SET_CUSTOMIZE_LIGHT_COLORS_ACTION SET_CUSTOMIZE_DARK_COLORS SET_CUSTOMIZE_DARK_COLORS_ACTION