check is_seen flag. display comment author thumbnails if present. #979

Merged
akinwale merged 1 commit from is-seen into master 2020-08-20 14:30:28 +02:00
9 changed files with 237 additions and 30 deletions

View file

@ -160,6 +160,7 @@ import io.lbry.browser.tasks.LighthouseAutoCompleteTask;
import io.lbry.browser.tasks.MergeSubscriptionsTask;
import io.lbry.browser.tasks.claim.ResolveTask;
import io.lbry.browser.tasks.lbryinc.NotificationListTask;
import io.lbry.browser.tasks.lbryinc.NotificationUpdateTask;
import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask;
import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler;
import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask;
@ -2127,19 +2128,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
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 {
loadRemoteNotifications();
}
loadRemoteNotifications(false);
}
private void handleAuthTokenGenerated(Intent intent) {
@ -2188,11 +2177,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
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) {
loadRemoteNotifications();
}
if (notificationListAdapter == null || notificationListAdapter.getItemCount() == 0) {
loadRemoteNotifications(true);
} else {
markNotificationsRead();
}
}
public void hideNotifications() {
((ImageView) findViewById(R.id.notifications_toggle_icon)).setColorFilter(ContextCompat.getColor(this, R.color.actionBarForeground));
@ -2200,6 +2190,21 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
private void markNotificationsRead() {
List<LbryNotification> all = notificationListAdapter != null ? notificationListAdapter.getItems() : null;
if (all != null) {
List<Long> unreadIds = new ArrayList<>();
for (LbryNotification notification : all) {
if (!notification.isRead() && notification.getRemoteId() > 0) {
unreadIds.add(notification.getRemoteId());
}
}
if (unreadIds.size() > 0) {
NotificationUpdateTask task = new NotificationUpdateTask(unreadIds, true);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
try {
@ -3224,12 +3229,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void loadRemoteNotifications() {
private void loadRemoteNotifications(boolean markRead) {
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
NotificationListTask task = new NotificationListTask(this, findViewById(R.id.notifications_progress), new NotificationListTask.ListNotificationsHandler() {
@Override
public void onSuccess(List<LbryNotification> notifications) {
loadLocalNotifications();
loadUnreadNotificationsCount();
if (markRead && findViewById(R.id.notifications_container).getVisibility() == View.VISIBLE) {
markNotificationsRead();
}
}
@Override
@ -3262,10 +3271,26 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
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);
if (notificationListAdapter == null) {
notificationListAdapter = new NotificationListAdapter(notifications, MainActivity.this);
} else {
notificationListAdapter.addNotifications(notifications);
}
resolveCommentAuthors(notificationListAdapter.getAuthorUrls());
notificationListAdapter.setClickListener(new NotificationListAdapter.NotificationClickListener() {
@Override
public void onNotificationClicked(LbryNotification notification) {
// set as seen and read
NotificationUpdateTask task = new NotificationUpdateTask(Arrays.asList(notification.getRemoteId()), true, true, true);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
markNotificationReadAndSeen(notification.getId());
notification.setSeen(true);
if (notificationListAdapter != null) {
notificationListAdapter.notifyDataSetChanged();
}
LbryUri target = LbryUri.tryParse(notification.getTargetUrl());
if (target != null) {
if (target.isChannel()) {
@ -3283,6 +3308,44 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void markNotificationReadAndSeen(long notificationId) {
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
if (dbHelper != null) {
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
DatabaseHelper.markNotificationReadAndSeen(notificationId, db);
} catch (Exception ex) {
// pass
}
}
return null;
}
protected void onPostExecute() {
loadUnreadNotificationsCount();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void resolveCommentAuthors(List<String> urls) {
if (urls != null && urls.size() > 0) {
ResolveTask task = new ResolveTask(urls, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (notificationListAdapter != null) {
notificationListAdapter.updateAuthorClaims(claims);
}
}
@Override
public void onError(Exception error) {
// pass
}
});
task.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

@ -6,15 +6,20 @@ import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;
import java.util.List;
import io.lbry.browser.R;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.ui.controls.SolidIconView;
import io.lbry.browser.utils.Helper;
@ -39,17 +44,25 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
this.items = new ArrayList<>(notifications);
}
public List<LbryNotification> getItems() {
return new ArrayList<>(items);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
protected View layoutView;
protected TextView titleView;
protected TextView bodyView;
protected TextView timeView;
protected SolidIconView iconView;
protected ImageView thumbnailView;
public ViewHolder(View v) {
super(v);
layoutView = v.findViewById(R.id.notification_layout);
titleView = v.findViewById(R.id.notification_title);
bodyView = v.findViewById(R.id.notification_body);
timeView = v.findViewById(R.id.notification_time);
iconView = v.findViewById(R.id.notification_icon);
thumbnailView = v.findViewById(R.id.notification_author_thumbnail);
}
}
@ -71,7 +84,34 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
notifyDataSetChanged();
}
public void addTags(List<LbryNotification> notifications) {
public List<String> getAuthorUrls() {
List<String> urls = new ArrayList<>();
for (LbryNotification item : items) {
if (!Helper.isNullOrEmpty(item.getAuthorUrl())) {
urls.add(item.getAuthorUrl());
}
}
return urls;
}
public void updateAuthorClaims(List<Claim> claims) {
for (Claim claim : claims) {
if (claim != null && claim.getThumbnailUrl() != null) {
updateClaimForAuthorUrl(claim);
}
}
notifyDataSetChanged();
}
private void updateClaimForAuthorUrl(Claim claim) {
for (LbryNotification item : items) {
if (claim.getPermanentUrl().equalsIgnoreCase(item.getAuthorUrl())) {
item.setCommentAuthor(claim);
}
}
}
public void addNotifications(List<LbryNotification> notifications) {
for (LbryNotification notification : notifications) {
if (!items.contains(notification)) {
items.add(notification);
@ -109,14 +149,23 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
@Override
public void onBindViewHolder(NotificationListAdapter.ViewHolder vh, int position) {
LbryNotification notification = items.get(position);
vh.titleView.setText(notification.getTitle());
vh.layoutView.setBackgroundColor(ContextCompat.getColor(context, notification.isSeen() ? R.color.white : R.color.nextLbryGreenSemiTransparent));
vh.titleView.setVisibility(!Helper.isNullOrEmpty(notification.getTitle()) ? View.VISIBLE : View.GONE);
vh.titleView.setText(notification.getTitle());
vh.bodyView.setText(notification.getDescription());
vh.timeView.setText(DateUtils.getRelativeTimeSpanString(
notification.getTimestamp().getTime(),
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
vh.thumbnailView.setVisibility(notification.getCommentAuthor() == null ? View.INVISIBLE : View.VISIBLE);
if (notification.getCommentAuthor() != null) {
Glide.with(context.getApplicationContext()).load(
notification.getCommentAuthor().getThumbnailUrl()).apply(RequestOptions.circleCropTransform()).into(vh.thumbnailView);
}
vh.iconView.setVisibility(notification.getCommentAuthor() != null ? View.INVISIBLE : View.VISIBLE);
vh.iconView.setText(getStringIdForRule(notification.getRule()));
vh.iconView.setTextColor(getColorForRule(notification.getRule()));

View file

@ -21,7 +21,7 @@ import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri;
public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 5;
public static final int DATABASE_VERSION = 6;
public static final String DATABASE_NAME = "LbryApp.db";
private static DatabaseHelper instance;
@ -51,6 +51,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"CREATE TABLE notifications (" +
" id INTEGER PRIMARY KEY NOT NULL" +
", remote_id INTEGER NOT NULL" +
", author_url TEXT" +
", title TEXT" +
", description TEXT" +
", thumbnail_url TEXT" +
@ -95,6 +96,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"ALTER TABLE notifications ADD COLUMN rule TEXT",
"ALTER TABLE notifications ADD COLUMN is_seen TEXT"
};
private static final String[] SQL_V5_V6_UPGRADE = {
"ALTER TABLE notifications ADD COLUMN author_url TEXT"
};
private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url) VALUES (?, ?)";
private static final String SQL_CLEAR_SUBSCRIPTIONS = "DELETE FROM subscriptions";
@ -106,10 +110,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final String SQL_CLEAR_URL_HISTORY_BEFORE_TIME = "DELETE FROM url_history WHERE timestamp < ?";
private static final String SQL_GET_RECENT_URL_HISTORY = "SELECT value, url, type FROM url_history ORDER BY timestamp DESC LIMIT 10";
private static final String SQL_INSERT_NOTIFICATION = "REPLACE INTO notifications (remote_id, title, description, rule, target_url, is_read, is_seen, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
private static final String SQL_GET_NOTIFICATIONS = "SELECT id, title, description, rule, target_url, is_read, is_seen, timestamp FROM notifications ORDER BY timestamp DESC LIMIT 500";
private static final String SQL_INSERT_NOTIFICATION = "REPLACE INTO notifications (remote_id, author_url, title, description, rule, target_url, is_read, is_seen, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String SQL_GET_NOTIFICATIONS = "SELECT id, remote_id, author_url, title, description, rule, target_url, is_read, is_seen, 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_MARK_NOTIFICATION_READ_AND_SEEN = "UPDATE notifications SET is_read = 1, is_seen = 1 WHERE id = ?";
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
@ -164,6 +169,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL(sql);
}
}
if (oldVersion < 6) {
for (String sql : SQL_V5_V6_UPGRADE) {
db.execSQL(sql);
}
}
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -307,6 +317,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
public static void createOrUpdateNotification(LbryNotification notification, SQLiteDatabase db) {
db.execSQL(SQL_INSERT_NOTIFICATION, new Object[] {
notification.getRemoteId(),
notification.getAuthorUrl(),
notification.getTitle(),
notification.getDescription(),
notification.getRule(),
@ -325,6 +336,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
LbryNotification notification = new LbryNotification();
int columnIndex = 0;
notification.setId(cursor.getLong(columnIndex++));
notification.setRemoteId(cursor.getLong(columnIndex++));
notification.setAuthorUrl(cursor.getString(columnIndex++));
notification.setTitle(cursor.getString(columnIndex++));
notification.setDescription(cursor.getString(columnIndex++));
notification.setRule(cursor.getString(columnIndex++));
@ -360,4 +373,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
public static void markNotificationsRead(SQLiteDatabase db) {
db.execSQL(SQL_MARK_NOTIFICATIONS_READ);
}
public static void markNotificationReadAndSeen(long notificationId, SQLiteDatabase db) {
db.execSQL(SQL_MARK_NOTIFICATION_READ_AND_SEEN, new Object[] { notificationId });
}
}

View file

@ -2,6 +2,7 @@ package io.lbry.browser.model.lbryinc;
import java.util.Date;
import io.lbry.browser.model.Claim;
import lombok.Data;
@Data
@ -10,10 +11,14 @@ public class LbryNotification {
private long remoteId;
private String title;
private String description;
private String rule;
private String thumbnailUrl;
private String rule;
private String targetUrl;
private boolean read;
private boolean seen;
private Date timestamp;
// only for comment notifications
private String authorUrl;
private Claim commentAuthor;
}

View file

@ -2,7 +2,9 @@ package io.lbry.browser.tasks.lbryinc;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
@ -26,6 +28,8 @@ import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbryio;
public class NotificationListTask extends AsyncTask<Void, Void, List<LbryNotification>> {
private static final String TAG = "Notifications";
private Context context;
private ListNotificationsHandler handler;
private ProgressBar progressBar;
@ -59,6 +63,9 @@ public class NotificationListTask extends AsyncTask<Void, Void, List<LbryNotific
}
if (notificationParams.has("dynamic") && !notificationParams.isNull("dynamic")) {
JSONObject dynamic = notificationParams.getJSONObject("dynamic");
if (dynamic.has("comment_author")) {
notification.setAuthorUrl(Helper.getJSONString("comment_author", null, dynamic));
}
if (dynamic.has("channelURI")) {
String channelUrl = Helper.getJSONString("channelURI", null, dynamic);
if (!Helper.isNullOrEmpty(channelUrl)) {
@ -95,7 +102,8 @@ public class NotificationListTask extends AsyncTask<Void, Void, List<LbryNotific
}
}
}
} catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException | IllegalStateException ex) {
} catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException | SQLiteException | IllegalStateException ex) {
Log.e(TAG, ex.getMessage(), ex);
error = ex;
return null;
}

View file

@ -0,0 +1,47 @@
package io.lbry.browser.tasks.lbryinc;
import android.os.AsyncTask;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.lbry.browser.exceptions.LbryioRequestException;
import io.lbry.browser.exceptions.LbryioResponseException;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbryio;
public class NotificationUpdateTask extends AsyncTask<Void, Void, Boolean> {
private List<Long> ids;
private boolean seen;
private boolean read;
private boolean updateSeen;
public NotificationUpdateTask(List<Long> ids, boolean read) {
this(ids, read, false, false);
}
public NotificationUpdateTask(List<Long> ids, boolean read, boolean seen, boolean updateSeen) {
this.ids = ids;
this.read = read;
this.seen = seen;
this.updateSeen = updateSeen;
}
protected Boolean doInBackground(Void... params) {
Map<String, String> options = new HashMap<>();
options.put("notification_ids", Helper.joinL(ids, ","));
options.put("is_read", String.valueOf(read));
if (updateSeen) {
options.put("is_seen", String.valueOf(seen));
}
try {
Object result = Lbryio.parseResponse(Lbryio.call("notification", "edit", options, null));
return "ok".equalsIgnoreCase(result.toString());
} catch (LbryioResponseException | LbryioRequestException ex) {
}
return false;
}
}

View file

@ -132,6 +132,16 @@ public final class Helper {
return sb.toString();
}
public static String joinL(List<Long> list, String delimiter) {
StringBuilder sb = new StringBuilder();
String delim = "";
for (Long item : list) {
sb.append(delim).append(item);
delim = delimiter;
}
return sb.toString();
}
public static <T> JSONArray jsonArrayFromList(List<T> values) {
JSONArray array = new JSONArray();
for (T value : values) {

View file

@ -201,7 +201,6 @@ public final class Lbryio {
throw new LbryioResponseException("Unknown API error signature.", response.code());
}
} catch (JSONException | IOException ex) {
throw new LbryioResponseException(String.format("Could not parse response: %s", responseString), ex);
}
}

View file

@ -9,16 +9,26 @@
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp">
<LinearLayout
android:id="@+id/notification_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/notification_icon"
<RelativeLayout
android:layout_marginLeft="16dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_gravity="center_vertical">
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/notification_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:textSize="24dp" />
<ImageView
android:id="@+id/notification_author_thumbnail"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="invisible" />
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"