In-app notifications ()

* display notification bell icon beside URL bar
* notification model
* persist received notifications
* Refresh notification list. Add  is_read flag and display unread count.
* Hide notifications when opening the navigatinon drawer. Open selected notification item before hiding the list.
* update local store with remote notifications
* use card view for notification items
* make notification cards clickable
* handle comment hash
* fix remote notifications not loading. fix comment scroll.
* Improve in-app comment notification linking. Add icons.
This commit is contained in:
Akinwale Ariwodola 2020-08-18 14:19:35 +01:00 committed by GitHub
parent ddcb190457
commit 4d024c06cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 892 additions and 57 deletions
app/src/main/java/io/lbry/browser

View file

@ -115,6 +115,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;
@ -137,6 +138,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;
@ -147,6 +149,7 @@ import io.lbry.browser.tasks.lbryinc.FetchRewardsTask;
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.localdata.FetchRecentUrlHistoryTask;
import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler;
import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask;
@ -224,6 +227,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 {
@ -282,6 +287,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode";
public static final String PREFERENCE_KEY_SHOW_MATURE_CONTENT = "io.lbry.browser.preference.userinterface.ShowMatureContent";
public static final String PREFERENCE_KEY_SHOW_URL_SUGGESTIONS = "io.lbry.browser.preference.userinterface.UrlSuggestions";
public static final String PREFERENCE_KEY_NOTIFICATION_COMMENTS = "io.lbry.browser.preference.notifications.Comments";
public static final String PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS = "io.lbry.browser.preference.notifications.Subscriptions";
public static final String PREFERENCE_KEY_NOTIFICATION_REWARDS = "io.lbry.browser.preference.notifications.Rewards";
public static final String PREFERENCE_KEY_NOTIFICATION_CONTENT_INTERESTS = "io.lbry.browser.preference.notifications.ContentInterests";
@ -439,6 +445,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// setup uri bar
setupUriBar();
initNotificationsPage();
loadUnreadNotificationsCount();
// other
pendingSyncSetQueue = new ArrayList<>();
@ -475,6 +483,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public void onDrawerSlide(View drawerView, float slideOffset) {
if (slideOffset != 0) {
clearWunderbarFocus(findViewById(R.id.wunderbar));
hideNotifications();
}
super.onDrawerSlide(drawerView, slideOffset);
}
@ -503,10 +512,23 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
});
findViewById(R.id.wunderbar_notifications).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
View container = findViewById(R.id.notifications_container);
if (container.getVisibility() != View.VISIBLE) {
showNotifications();
} else {
hideNotifications();
}
}
});
findViewById(R.id.global_now_playing_card).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) {
hideNotifications();
openFileUrl(nowPlayingClaimUrl);
}
}
@ -551,7 +573,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public boolean isBackgroundPlaybackEnabled() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
return sp.getBoolean(PREFERENCE_KEY_BACKGROUND_PLAYBACK, false);
return sp.getBoolean(PREFERENCE_KEY_BACKGROUND_PLAYBACK, true);
}
public boolean initialSubscriptionMergeDone() {
@ -717,8 +739,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;
@ -1067,7 +1090,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
View container = findViewById(R.id.url_suggestions_container);
View closeIcon = findViewById(R.id.wunderbar_close);
EditText wunderbar = findViewById(R.id.wunderbar);
wunderbar.setPadding(0, 0, visible ? getScaledValue(36) : 0, 0);
//wunderbar.setPadding(0, 0, visible ? getScaledValue(36) : 0, 0);
container.setVisibility(visible ? View.VISIBLE : View.GONE);
closeIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
@ -1102,8 +1125,13 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) {
hideNotifications();
findViewById(R.id.wunderbar_notifications).setVisibility(View.INVISIBLE);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, 0);
} else {
findViewById(R.id.wunderbar_notifications).setVisibility(View.VISIBLE);
}
if (canShowUrlSuggestions()) {
@ -1636,6 +1664,14 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
private static final String CHANNEL_ID_PLAYBACK = "io.lbry.browser.LBRY_PLAYBACK_CHANNEL";
private static final int PLAYBACK_NOTIFICATION_ID = 3;
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() {
if (isBackgroundPlaybackEnabled()) {
playerNotificationManager.setPlayer(MainActivity.appPlayer);
@ -2006,6 +2042,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) {
@ -2026,6 +2063,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 {
loadRemoteNotifications();
}
}
@ -2071,13 +2127,48 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show();
}
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) {
loadRemoteNotifications();
}
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) {
clearWunderbarFocus(findViewById(R.id.wunderbar));
return;
}
if (findViewById(R.id.notifications_container).getVisibility() == View.VISIBLE) {
hideNotifications();
return;
}
if (backPressInterceptor != null && backPressInterceptor.onBackPressed()) {
return;
}
@ -3051,6 +3142,90 @@ 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 loadRemoteNotifications() {
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();
}
@Override
public void onError(Exception exception) {
// pass
Log.e(TAG, "error loading remote notifications", exception);
loadLocalNotifications();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void loadLocalNotifications() {
(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) {
if (target.isChannel()) {
openChannelUrl(notification.getTargetUrl());
} else {
openFileUrl(notification.getTargetUrl());
}
hideNotifications();
}
}
});
((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