diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index cc071cda..405613c8 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -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,10 +2177,11 @@ 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(); } - markNotificationsRead(); } public void hideNotifications() { @@ -2200,6 +2190,21 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } private void markNotificationsRead() { + List all = notificationListAdapter != null ? notificationListAdapter.getItems() : null; + if (all != null) { + List 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() { 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 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 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); + + 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() { + 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 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 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 diff --git a/app/src/main/java/io/lbry/browser/adapter/NotificationListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/NotificationListAdapter.java index ca16ffc0..31839fc5 100644 --- a/app/src/main/java/io/lbry/browser/adapter/NotificationListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/NotificationListAdapter.java @@ -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(notifications); } + public List 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 notifications) { + public List getAuthorUrls() { + List urls = new ArrayList<>(); + for (LbryNotification item : items) { + if (!Helper.isNullOrEmpty(item.getAuthorUrl())) { + urls.add(item.getAuthorUrl()); + } + } + return urls; + } + + public void updateAuthorClaims(List 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 notifications) { for (LbryNotification notification : notifications) { if (!items.contains(notification)) { items.add(notification); @@ -109,14 +149,23 @@ public class NotificationListAdapter extends RecyclerView.Adapter> { + private static final String TAG = "Notifications"; + private Context context; private ListNotificationsHandler handler; private ProgressBar progressBar; @@ -59,6 +63,9 @@ public class NotificationListTask extends AsyncTask { + private List ids; + private boolean seen; + private boolean read; + private boolean updateSeen; + + public NotificationUpdateTask(List ids, boolean read) { + this(ids, read, false, false); + } + + public NotificationUpdateTask(List 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 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; + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index 5114e840..ae5a63ad 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -132,6 +132,16 @@ public final class Helper { return sb.toString(); } + public static String joinL(List 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 JSONArray jsonArrayFromList(List values) { JSONArray array = new JSONArray(); for (T value : values) { diff --git a/app/src/main/java/io/lbry/browser/utils/Lbryio.java b/app/src/main/java/io/lbry/browser/utils/Lbryio.java index 4f157605..40b34939 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -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); } } diff --git a/app/src/main/res/layout/list_item_notification.xml b/app/src/main/res/layout/list_item_notification.xml index 840442f0..423d7fe4 100644 --- a/app/src/main/res/layout/list_item_notification.xml +++ b/app/src/main/res/layout/list_item_notification.xml @@ -9,16 +9,26 @@ android:layout_marginRight="8dp" android:layout_marginBottom="8dp"> - + android:layout_gravity="center_vertical"> + + +