Notifications (#554)
Cleanup Extending and improving notifications Using new icons instead Lowering polling delay to one minute and other improvements Fixing minor issues Simplifying progress layout Fixing bugs and other improvements Adding translations Notifications Co-authored-by: opyale <opyale@noreply.gitea.io> Co-authored-by: 6543 <6543@noreply.codeberg.org> Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/554 Reviewed-by: 6543 <6543@noreply.codeberg.org> Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package org.mian.gitnex.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.helpers.TinyDB;
|
||||
|
||||
/**
|
||||
* Author opyale
|
||||
*/
|
||||
|
||||
public class BottomSheetNotificationsFilterFragment extends BottomSheetDialogFragment {
|
||||
|
||||
private TinyDB tinyDB;
|
||||
private OnDismissedListener onDismissedListener;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
|
||||
this.tinyDB = new TinyDB(context);
|
||||
super.onAttach(context);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
View view = inflater.inflate(R.layout.bottom_sheet_notifications_filter, container, false);
|
||||
|
||||
TextView readNotifications = view.findViewById(R.id.readNotifications);
|
||||
TextView unreadNotifications = view.findViewById(R.id.unreadNotifications);
|
||||
|
||||
readNotifications.setOnClickListener(v1 -> {
|
||||
|
||||
tinyDB.putString("notificationsFilterState", "read");
|
||||
dismiss();
|
||||
|
||||
});
|
||||
|
||||
unreadNotifications.setOnClickListener(v12 -> {
|
||||
|
||||
tinyDB.putString("notificationsFilterState", "unread");
|
||||
dismiss();
|
||||
|
||||
});
|
||||
|
||||
return view;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
|
||||
if(onDismissedListener != null) {
|
||||
|
||||
onDismissedListener.onDismissed();
|
||||
}
|
||||
|
||||
super.dismiss();
|
||||
|
||||
}
|
||||
|
||||
public void setOnDismissedListener(OnDismissedListener onDismissedListener) {
|
||||
|
||||
this.onDismissedListener = onDismissedListener;
|
||||
}
|
||||
|
||||
public interface OnDismissedListener {
|
||||
|
||||
void onDismissed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.mian.gitnex.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.actions.NotificationsActions;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.models.NotificationThread;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Author opyale
|
||||
*/
|
||||
|
||||
public class BottomSheetNotificationsFragment extends BottomSheetDialogFragment {
|
||||
|
||||
private Context context;
|
||||
private NotificationThread notificationThread;
|
||||
private OnOptionSelectedListener onOptionSelectedListener;
|
||||
|
||||
public void onAttach(Context context, NotificationThread notificationThread, OnOptionSelectedListener onOptionSelectedListener) {
|
||||
|
||||
this.context = context;
|
||||
this.notificationThread = notificationThread;
|
||||
this.onOptionSelectedListener = onOptionSelectedListener;
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.bottom_sheet_notifications, container, false);
|
||||
|
||||
TextView markRead = v.findViewById(R.id.markRead);
|
||||
TextView markUnread = v.findViewById(R.id.markUnread);
|
||||
TextView markPinned = v.findViewById(R.id.markPinned);
|
||||
|
||||
NotificationsActions notificationsActions = new NotificationsActions(context);
|
||||
Activity activity = Objects.requireNonNull(getActivity());
|
||||
|
||||
if(notificationThread.isPinned()) {
|
||||
|
||||
AppUtil.setMultiVisibility(View.GONE, markUnread, markPinned);
|
||||
} else if(notificationThread.isUnread()) {
|
||||
|
||||
markUnread.setVisibility(View.GONE);
|
||||
} else {
|
||||
|
||||
markRead.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
markPinned.setOnClickListener(v12 -> {
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
notificationsActions.setNotificationStatus(notificationThread, NotificationsActions.NotificationStatus.PINNED);
|
||||
activity.runOnUiThread(() -> onOptionSelectedListener.onSelected());
|
||||
|
||||
}
|
||||
catch(Exception e) {
|
||||
|
||||
activity.runOnUiThread(() -> Toasty.error(context, getString(R.string.genericError)));
|
||||
Log.e("onError", e.toString());
|
||||
|
||||
} finally {
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
});
|
||||
|
||||
markRead.setOnClickListener(v1 -> {
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
notificationsActions.setNotificationStatus(notificationThread, NotificationsActions.NotificationStatus.READ);
|
||||
activity.runOnUiThread(() -> onOptionSelectedListener.onSelected());
|
||||
|
||||
}
|
||||
catch(Exception e) {
|
||||
|
||||
activity.runOnUiThread(() -> Toasty.error(context, getString(R.string.genericError)));
|
||||
Log.e("onError", e.toString());
|
||||
|
||||
} finally {
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
});
|
||||
|
||||
markUnread.setOnClickListener(v13 -> {
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
notificationsActions.setNotificationStatus(notificationThread, NotificationsActions.NotificationStatus.UNREAD);
|
||||
activity.runOnUiThread(() -> onOptionSelectedListener.onSelected());
|
||||
|
||||
}
|
||||
catch(Exception e) {
|
||||
|
||||
activity.runOnUiThread(() -> Toasty.error(context, getString(R.string.genericError)));
|
||||
Log.e("onError", e.toString());
|
||||
|
||||
} finally {
|
||||
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
});
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
public interface OnOptionSelectedListener {
|
||||
|
||||
void onSelected();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment {
|
||||
TextView subscribeIssue = v.findViewById(R.id.subscribeIssue);
|
||||
TextView unsubscribeIssue = v.findViewById(R.id.unsubscribeIssue);
|
||||
|
||||
if(tinyDB.getString("issueType").equals("pr")) {
|
||||
if(tinyDB.getString("issueType").equalsIgnoreCase("Pull")) {
|
||||
|
||||
editIssue.setText(R.string.editPrText);
|
||||
copyIssueUrl.setText(R.string.copyPrUrlText);
|
||||
@@ -199,7 +199,7 @@ public class BottomSheetSingleIssueFragment extends BottomSheetDialogFragment {
|
||||
|
||||
});
|
||||
|
||||
if(tinyDB.getString("issueType").equals("issue")) {
|
||||
if(tinyDB.getString("issueType").equalsIgnoreCase("Issue")) {
|
||||
|
||||
if(tinyDB.getString("issueState").equals("open")) { // close issue
|
||||
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
package org.mian.gitnex.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.actions.NotificationsActions;
|
||||
import org.mian.gitnex.activities.IssueDetailActivity;
|
||||
import org.mian.gitnex.adapters.NotificationsAdapter;
|
||||
import org.mian.gitnex.clients.RetrofitClient;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.InfiniteScrollListener;
|
||||
import org.mian.gitnex.helpers.SnackBar;
|
||||
import org.mian.gitnex.helpers.StaticGlobalVariables;
|
||||
import org.mian.gitnex.helpers.TinyDB;
|
||||
import org.mian.gitnex.models.NotificationThread;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* Author opyale
|
||||
*/
|
||||
|
||||
public class NotificationsFragment extends Fragment implements NotificationsAdapter.OnNotificationClickedListener, NotificationsAdapter.OnMoreClickedListener, BottomSheetNotificationsFragment.OnOptionSelectedListener {
|
||||
|
||||
private List<NotificationThread> notificationThreads;
|
||||
private NotificationsAdapter notificationsAdapter;
|
||||
private NotificationsActions notificationsActions;
|
||||
|
||||
private ImageView markAllAsRead;
|
||||
private ProgressBar progressBar;
|
||||
private RelativeLayout mainLayout;
|
||||
private ProgressBar loadingMoreView;
|
||||
private TextView noDataNotifications;
|
||||
private SwipeRefreshLayout pullToRefresh;
|
||||
|
||||
private Activity activity;
|
||||
private Context context;
|
||||
private TinyDB tinyDB;
|
||||
private Menu menu;
|
||||
|
||||
private int pageCurrentIndex = 1;
|
||||
private int pageResultLimit;
|
||||
private String currentFilterMode = "unread";
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
activity = Objects.requireNonNull(getActivity());
|
||||
context = getContext();
|
||||
tinyDB = new TinyDB(context);
|
||||
|
||||
pageResultLimit = StaticGlobalVariables.getCurrentResultLimit(context);
|
||||
tinyDB.putString("notificationsFilterState", currentFilterMode);
|
||||
|
||||
mainLayout = v.findViewById(R.id.mainLayout);
|
||||
markAllAsRead = v.findViewById(R.id.markAllAsRead);
|
||||
noDataNotifications = v.findViewById(R.id.noDataNotifications);
|
||||
loadingMoreView = v.findViewById(R.id.loadingMoreView);
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
|
||||
notificationThreads = new ArrayList<>();
|
||||
notificationsActions = new NotificationsActions(context);
|
||||
notificationsAdapter = new NotificationsAdapter(context, notificationThreads, this, this);
|
||||
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
|
||||
|
||||
RecyclerView recyclerView = v.findViewById(R.id.notifications);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(linearLayoutManager);
|
||||
recyclerView.setAdapter(notificationsAdapter);
|
||||
recyclerView.addOnScrollListener(new InfiniteScrollListener(pageResultLimit, linearLayoutManager) {
|
||||
|
||||
@Override
|
||||
public void onScrolledToEnd(int firstVisibleItemPosition) {
|
||||
|
||||
pageCurrentIndex++;
|
||||
loadNotifications(true);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
|
||||
if(currentFilterMode.equalsIgnoreCase("unread")) {
|
||||
|
||||
if(dy > 0 && markAllAsRead.isShown()) {
|
||||
|
||||
markAllAsRead.setVisibility(View.GONE);
|
||||
} else if(dy < 0) {
|
||||
|
||||
markAllAsRead.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
markAllAsRead.setOnClickListener(v1 -> {
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
if(notificationsActions.setAllNotificationsRead(new Date())) {
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
|
||||
SnackBar.info(context, mainLayout, getString(R.string.markedNotificationsAsRead));
|
||||
loadNotifications(true);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(IOException e) {
|
||||
|
||||
activity.runOnUiThread(() -> SnackBar.error(context, mainLayout, getString(R.string.genericError)));
|
||||
Log.e("onError", e.toString());
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
});
|
||||
|
||||
pullToRefresh = v.findViewById(R.id.pullToRefresh);
|
||||
pullToRefresh.setOnRefreshListener(() -> {
|
||||
|
||||
pageCurrentIndex = 1;
|
||||
loadNotifications(false);
|
||||
|
||||
});
|
||||
|
||||
loadNotifications(false);
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
private void loadNotifications(boolean append) {
|
||||
|
||||
noDataNotifications.setVisibility(View.GONE);
|
||||
|
||||
if(pageCurrentIndex == 1 || !append) {
|
||||
|
||||
notificationThreads.clear();
|
||||
notificationsAdapter.notifyDataSetChanged();
|
||||
pullToRefresh.setRefreshing(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
} else {
|
||||
|
||||
loadingMoreView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
String instanceUrl = tinyDB.getString("instanceUrl");
|
||||
String loginUid = tinyDB.getString("loginUid");
|
||||
String instanceToken = "token " + tinyDB.getString(loginUid + "-token");
|
||||
|
||||
String[] filter = tinyDB.getString("notificationsFilterState").equals("read") ?
|
||||
new String[]{"pinned", "read"} :
|
||||
new String[]{"pinned", "unread"};
|
||||
|
||||
Call<List<NotificationThread>> call = RetrofitClient.getInstance(instanceUrl, context)
|
||||
.getApiInterface()
|
||||
.getNotificationThreads(instanceToken, false, filter,
|
||||
StaticGlobalVariables.defaultOldestTimestamp, "",
|
||||
pageCurrentIndex, pageResultLimit);
|
||||
|
||||
call.enqueue(new Callback<List<NotificationThread>>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<List<NotificationThread>> call, @NonNull Response<List<NotificationThread>> response) {
|
||||
|
||||
if(response.code() == 200) {
|
||||
|
||||
assert response.body() != null;
|
||||
|
||||
if(!append) {
|
||||
|
||||
notificationThreads.clear();
|
||||
}
|
||||
|
||||
notificationThreads.addAll(response.body());
|
||||
notificationsAdapter.notifyDataSetChanged();
|
||||
|
||||
} else {
|
||||
|
||||
Log.e("onError", String.valueOf(response.code()));
|
||||
}
|
||||
|
||||
onCleanup();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<List<NotificationThread>> call, @NonNull Throwable t) {
|
||||
|
||||
Log.e("onError", t.toString());
|
||||
onCleanup();
|
||||
|
||||
}
|
||||
|
||||
private void onCleanup() {
|
||||
|
||||
AppUtil.setMultiVisibility(View.GONE, loadingMoreView, progressBar);
|
||||
pullToRefresh.setRefreshing(false);
|
||||
|
||||
if(notificationThreads.isEmpty()) {
|
||||
|
||||
noDataNotifications.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void changeFilterMode() {
|
||||
|
||||
int filterIcon = currentFilterMode.equalsIgnoreCase("read") ?
|
||||
R.drawable.ic_filter_closed :
|
||||
R.drawable.ic_filter;
|
||||
|
||||
menu.getItem(0).setIcon(filterIcon);
|
||||
|
||||
if(currentFilterMode.equalsIgnoreCase("read")) {
|
||||
|
||||
markAllAsRead.setVisibility(View.GONE);
|
||||
} else {
|
||||
|
||||
markAllAsRead.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
|
||||
this.menu = menu;
|
||||
|
||||
inflater.inflate(R.menu.filter_menu_notifications, menu);
|
||||
|
||||
currentFilterMode = tinyDB.getString("notificationsFilterState");
|
||||
changeFilterMode();
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
|
||||
if(item.getItemId() == R.id.filterNotifications) {
|
||||
|
||||
BottomSheetNotificationsFilterFragment bottomSheetNotificationsFilterFragment = new BottomSheetNotificationsFilterFragment();
|
||||
bottomSheetNotificationsFilterFragment.show(getChildFragmentManager(), "notificationsFilterBottomSheet");
|
||||
bottomSheetNotificationsFilterFragment.setOnDismissedListener(() -> {
|
||||
|
||||
pageCurrentIndex = 1;
|
||||
currentFilterMode = tinyDB.getString("notificationsFilterState");
|
||||
|
||||
changeFilterMode();
|
||||
loadNotifications(false);
|
||||
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationClicked(NotificationThread notificationThread) {
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
if(notificationThread.isUnread()) {
|
||||
|
||||
notificationsActions.setNotificationStatus(notificationThread, NotificationsActions.NotificationStatus.READ);
|
||||
activity.runOnUiThread(() -> loadNotifications(false));
|
||||
|
||||
}
|
||||
} catch(IOException ignored) {}
|
||||
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
if(StringUtils.containsAny(notificationThread.getSubject().getType().toLowerCase(), "pull", "issue")) {
|
||||
|
||||
Intent intent = new Intent(context, IssueDetailActivity.class);
|
||||
String issueUrl = notificationThread.getSubject().getUrl();
|
||||
|
||||
tinyDB.putString("issueNumber", issueUrl.substring(issueUrl.lastIndexOf("/") + 1));
|
||||
tinyDB.putString("issueType", notificationThread.getSubject().getType());
|
||||
tinyDB.putString("repoFullName", notificationThread.getRepository().getFullname());
|
||||
|
||||
startActivity(intent);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoreClicked(NotificationThread notificationThread) {
|
||||
|
||||
BottomSheetNotificationsFragment bottomSheetNotificationsFragment = new BottomSheetNotificationsFragment();
|
||||
bottomSheetNotificationsFragment.onAttach(context, notificationThread, this);
|
||||
bottomSheetNotificationsFragment.show(getChildFragmentManager(), "notificationsBottomSheet");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelected() {
|
||||
|
||||
pageCurrentIndex = 1;
|
||||
loadNotifications(false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user