Refresh notification list. Add is_read flag and display unread count.

This commit is contained in:
Akinwale Ariwodola 2020-07-24 06:17:53 +01:00
parent 9af6b74934
commit 185526eb29
9 changed files with 328 additions and 16 deletions

View file

@ -37,6 +37,7 @@ import java.util.List;
import java.util.Map;
public class LbrynetMessagingService extends FirebaseMessagingService {
public static final String ACTION_NOTIFICATION_RECEIVED = "io.lbry.browser.Broadcast.NotificationReceived";
private static final String TAG = "LbrynetMessagingService";
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL";
@ -59,10 +60,6 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
String title = payload.get("title");
String body = payload.get("body");
String name = payload.get("name"); // notification name
String contentTitle = payload.get("content_title");
String channelUrl = payload.get("channel_url");
//String publishTime = payload.get("publish_time");
String publishTime = null;
if (type != null && getEnabledTypes().indexOf(type) > -1 && body != null && body.trim().length() > 0) {
// only log the receive event for valid notifications received
@ -72,7 +69,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
firebaseAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_RECEIVE, bundle);
}
sendNotification(title, body, type, url, name, contentTitle, channelUrl, publishTime);
sendNotification(title, body, type, url, name);
}
// persist the notification data
@ -85,6 +82,14 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
lnotification.setTargetUrl(url);
lnotification.setTimestamp(new Date());
DatabaseHelper.createNotification(lnotification, db);
// send a broadcast
Intent intent = new Intent(ACTION_NOTIFICATION_RECEIVED);
intent.putExtra("title", title);
intent.putExtra("body", body);
intent.putExtra("url", url);
intent.putExtra("timestamp", lnotification.getTimestamp().getTime());
sendBroadcast(intent);
} catch (Exception ex) {
// don't fail if any error occurs while saving a notification
Log.e(TAG, "could not save notification", ex);
@ -119,8 +124,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
*
* @param messageBody FCM message body received.
*/
private void sendNotification(String title, String messageBody, String type, String url, String name,
String contentTitle, String channelUrl, String publishTime) {
private void sendNotification(String title, String messageBody, String type, String url, String name) {
//Intent intent = new Intent(this, MainActivity.class);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (url == null) {
@ -142,8 +146,8 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setColor(ContextCompat.getColor(this, R.color.lbryGreen))
.setSmallIcon(R.drawable.ic_lbry)
.setContentTitle(HtmlCompat.fromHtml(messageBody, HtmlCompat.FROM_HTML_MODE_LEGACY))
.setContentText(HtmlCompat.fromHtml(messageBody, HtmlCompat.FROM_HTML_MODE_LEGACY))
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);

View file

@ -114,6 +114,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import io.lbry.browser.adapter.NavigationMenuAdapter;
import io.lbry.browser.adapter.NotificationListAdapter;
import io.lbry.browser.adapter.UrlSuggestionListAdapter;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.dialog.ContentScopeDialogFragment;
@ -136,6 +137,7 @@ import io.lbry.browser.model.Tag;
import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.WalletBalance;
import io.lbry.browser.model.WalletSync;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.model.lbryinc.Reward;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.GenericTaskHandler;
@ -223,6 +225,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Getter
private String firebaseMessagingToken;
private NotificationListAdapter notificationListAdapter;
private Map<String, Fragment> openNavFragments;
private static final Map<Class, Integer> fragmentClassNavIdMap = new HashMap<>();
static {
@ -439,6 +443,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// setup uri bar
setupUriBar();
initNotificationsPage();
loadUnreadNotificationsCount();
// other
pendingSyncSetQueue = new ArrayList<>();
@ -519,6 +524,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Override
public void onClick(View view) {
if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) {
hideNotifications();
openFileUrl(nowPlayingClaimUrl);
}
}
@ -728,8 +734,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
private void openSelectedMenuItem() {
hideNotifications();
switch (selectedMenuItemId) {
// TODO: reverse map lookup for class?
case NavMenuItem.ID_ITEM_FOLLOWING:
openFragment(FollowingFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING);
break;
@ -1648,6 +1655,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public void initNotificationsPage() {
findViewById(R.id.notification_list_empty_container).setVisibility(View.VISIBLE);
RecyclerView notificationsList = findViewById(R.id.notifications_list);
LinearLayoutManager llm = new LinearLayoutManager(this);
notificationsList.setLayoutManager(llm);
}
public void initPlaybackNotification() {
@ -2020,6 +2031,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
intentFilter.addAction(ACTION_OPEN_REWARDS_PAGE);
intentFilter.addAction(ACTION_PUBLISH_SUCCESSFUL);
intentFilter.addAction(ACTION_SAVE_SHARED_USER_STATE);
intentFilter.addAction(LbrynetMessagingService.ACTION_NOTIFICATION_RECEIVED);
requestsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -2040,6 +2052,25 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
saveSharedUserState();
} else if (ACTION_PUBLISH_SUCCESSFUL.equalsIgnoreCase(action)) {
openPublishesOnSuccessfulPublish();
} else if (LbrynetMessagingService.ACTION_NOTIFICATION_RECEIVED.equalsIgnoreCase(action)) {
handleNotificationReceived(intent);
}
}
private void handleNotificationReceived(Intent intent) {
loadUnreadNotificationsCount();
if (notificationListAdapter != null) {
LbryNotification lnotification = new LbryNotification();
lnotification.setTitle(intent.getStringExtra("title"));
lnotification.setDescription(intent.getStringExtra("body"));
lnotification.setTargetUrl(intent.getStringExtra("url"));
lnotification.setTimestamp(new Date(intent.getLongExtra("timestamp", System.currentTimeMillis())));
// show at the top
notificationListAdapter.insertNotification(lnotification, 0);
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
} else {
loadNotifications();
}
}
@ -2088,12 +2119,35 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public void showNotifications() {
clearWunderbarFocus(findViewById(R.id.wunderbar));
findViewById(R.id.notifications_container).setVisibility(View.VISIBLE);
((ImageView) findViewById(R.id.notifications_toggle_icon)).setColorFilter(ContextCompat.getColor(this, R.color.lbryGreen));
if (notificationListAdapter == null) {
loadNotifications();
}
markNotificationsRead();
}
public void hideNotifications() {
((ImageView) findViewById(R.id.notifications_toggle_icon)).setColorFilter(ContextCompat.getColor(this, R.color.actionBarForeground));
findViewById(R.id.notifications_container).setVisibility(View.GONE);
}
private void markNotificationsRead() {
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
DatabaseHelper.markNotificationsRead(db);
} catch (Exception ex) {
// pass
}
return null;
}
protected void onPostExecute(Void result) {
loadUnreadNotificationsCount();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onBackPressed() {
if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) {
@ -3064,6 +3118,72 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void displayUnreadNotificationCount(int count) {
String text = count > 99 ? "99+" : String.valueOf(count);
TextView badge = findViewById(R.id.notifications_badge_count);
badge.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
badge.setText(text);
}
private void loadUnreadNotificationsCount() {
(new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
try {
SQLiteDatabase db = dbHelper.getReadableDatabase();
return DatabaseHelper.getUnreadNotificationsCount(db);
} catch (Exception ex) {
return 0;
}
}
protected void onPostExecute(Integer count) {
displayUnreadNotificationCount(count);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void loadNotifications() {
(new AsyncTask<Void, Void, List<LbryNotification>>() {
protected void onPreExecute() {
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
findViewById(R.id.notifications_progress).setVisibility(View.VISIBLE);
}
@Override
protected List<LbryNotification> doInBackground(Void... params) {
List<LbryNotification> notifications = new ArrayList<>();
try {
SQLiteDatabase db = dbHelper.getReadableDatabase();
notifications = DatabaseHelper.getNotifications(db);
} catch (Exception ex) {
// pass
}
return notifications;
}
protected void onPostExecute(List<LbryNotification> notifications) {
findViewById(R.id.notification_list_empty_container).setVisibility(notifications.size() == 0 ? View.VISIBLE : View.GONE);
findViewById(R.id.notifications_progress).setVisibility(View.GONE);
notificationListAdapter = new NotificationListAdapter(notifications, MainActivity.this);
notificationListAdapter.setClickListener(new NotificationListAdapter.NotificationClickListener() {
@Override
public void onNotificationClicked(LbryNotification notification) {
LbryUri target = LbryUri.tryParse(notification.getTargetUrl());
if (target != null) {
hideNotifications();
if (target.isChannel()) {
openChannelUrl(target.toString());
} else {
openFileUrl(target.toString());
}
}
}
});
((RecyclerView) findViewById(R.id.notifications_list)).setAdapter(notificationListAdapter);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void checkSyncedWallet() {
String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE);
// Just check if the current user has a synced wallet, no need to do anything else here

View file

@ -0,0 +1,103 @@
package io.lbry.browser.adapter;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import io.lbry.browser.R;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.utils.Helper;
import lombok.Getter;
import lombok.Setter;
public class NotificationListAdapter extends RecyclerView.Adapter<NotificationListAdapter.ViewHolder> {
private Context context;
private List<LbryNotification> items;
@Setter
private NotificationClickListener clickListener;
@Getter
@Setter
private int customizeMode;
public NotificationListAdapter(List<LbryNotification> notifications, Context context) {
this.context = context;
this.items = new ArrayList<>(notifications);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
protected TextView titleView;
protected TextView bodyView;
protected TextView timeView;
public ViewHolder(View v) {
super(v);
titleView = v.findViewById(R.id.notification_title);
bodyView = v.findViewById(R.id.notification_body);
timeView = v.findViewById(R.id.notification_time);
}
}
public int getItemCount() {
return items != null ? items.size() : 0;
}
public void insertNotification(LbryNotification notification, int index) {
if (!items.contains(notification)) {
items.add(index, notification);
}
notifyDataSetChanged();
}
public void addNotification(LbryNotification notification) {
if (!items.contains(notification)) {
items.add(notification);
}
notifyDataSetChanged();
}
public void addTags(List<LbryNotification> notifications) {
for (LbryNotification notification : notifications) {
if (!items.contains(notification)) {
items.add(notification);
}
}
notifyDataSetChanged();
}
@Override
public NotificationListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.list_item_notification, root, false);
return new NotificationListAdapter.ViewHolder(v);
}
@Override
public void onBindViewHolder(NotificationListAdapter.ViewHolder vh, int position) {
LbryNotification notification = items.get(position);
vh.titleView.setText(notification.getTitle());
vh.titleView.setVisibility(!Helper.isNullOrEmpty(notification.getTitle()) ? View.VISIBLE : View.GONE);
vh.bodyView.setText(notification.getDescription());
vh.timeView.setText(DateUtils.getRelativeTimeSpanString(
notification.getTimestamp().getTime(),
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (clickListener != null) {
clickListener.onNotificationClicked(notification);
}
}
});
}
public interface NotificationClickListener {
void onNotificationClicked(LbryNotification notification);
}
}

View file

@ -54,6 +54,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
", description TEXT" +
", thumbnail_url TEXT" +
", target_url TEXT" +
", is_read INTEGER DEFAULT 0 NOT NULL" +
", timestamp TEXT NOT NULL)",
};
private static final String[] SQL_CREATE_INDEXES = {
@ -77,6 +78,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
", description TEXT" +
", thumbnail_url TEXT" +
", target_url TEXT" +
", is_read INTEGER DEFAULT 0 NOT NULL" +
", timestamp TEXT NOT NULL)",
"CREATE INDEX idx_notification_timestamp ON notifications (timestamp)"
};
@ -93,6 +95,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final String SQL_INSERT_NOTIFICATION = "INSERT INTO notifications (title, description, target_url, timestamp) VALUES (?, ?, ?, ?)";
private static final String SQL_GET_NOTIFICATIONS = "SELECT id, title, description, target_url, timestamp FROM notifications ORDER BY timestamp DESC LIMIT 500";
private static final String SQL_GET_UNREAD_NOTIFICATIONS_COUNT = "SELECT COUNT(id) FROM notifications WHERE is_read <> 1";
private static final String SQL_MARK_NOTIFICATIONS_READ = "UPDATE notifications SET is_read = 1 WHERE is_read = 0";
private static final String SQL_INSERT_VIEW_HISTORY =
"REPLACE INTO view_history (url, claim_id, claim_name, cost, currency, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
@ -302,10 +306,27 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// invalid timestamp (which shouldn't happen). Skip this item
continue;
}
notifications.add(notification);
}
} finally {
Helper.closeCursor(cursor);
}
return notifications;
}
public static int getUnreadNotificationsCount(SQLiteDatabase db) {
int count = 0;
Cursor cursor = null;
try {
cursor = db.rawQuery(SQL_GET_UNREAD_NOTIFICATIONS_COUNT, null);
if (cursor.moveToNext()) {
count = cursor.getInt(0);
}
} finally {
Helper.closeCursor(cursor);
}
return count;
}
public static void markNotificationsRead(SQLiteDatabase db) {
db.execSQL(SQL_MARK_NOTIFICATIONS_READ);
}
}

View file

@ -11,5 +11,6 @@ public class LbryNotification {
private String description;
private String thumbnailUrl;
private String targetUrl;
private boolean read;
private Date timestamp;
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/red" />
<corners android:radius="16dp" />
</shape>

View file

@ -28,17 +28,18 @@
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="24dp">
android:layout_height="wrap_content">
<EditText
android:id="@+id/wunderbar"
android:background="@android:color/transparent"
android:layout_centerVertical="true"
android:drawableLeft="@drawable/ic_search"
android:drawablePadding="8dp"
android:drawableTint="@color/actionBarForeground"
android:fontFamily="@font/inter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="24dp"
android:hint="@string/uri_placeholder"
android:imeOptions="actionGo"
android:inputType="textNoSuggestions"
@ -53,6 +54,7 @@
android:background="?attr/selectableItemBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="24dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="gone">
@ -63,19 +65,38 @@
android:tint="@color/actionBarForeground" />
</LinearLayout>
<LinearLayout
<RelativeLayout
android:id="@+id/wunderbar_notifications"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/notifications_toggle_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_notifications"
android:tint="@color/actionBarForeground" />
</LinearLayout>
<TextView
android:id="@+id/notifications_badge_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_notification_badge"
android:fontFamily="@font/inter"
android:gravity="center_horizontal"
android:minWidth="12dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:layout_marginTop="12dp"
android:layout_marginLeft="24dp"
android:textColor="@color/white"
android:textSize="10sp"
android:visibility="invisible"
/>
</RelativeLayout>
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>

View file

@ -41,6 +41,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<ProgressBar
android:id="@+id/notifications_progress"
android:layout_centerInParent="true"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/notification_list_empty_container"
android:orientation="vertical"

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:fontFamily="@font/inter"
android:textStyle="bold"
android:textSize="13sp" />
<TextView
android:id="@+id/notification_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:fontFamily="@font/inter"
android:textSize="14sp" />
<TextView
android:id="@+id/notification_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textSize="11sp" />
</LinearLayout>