In-app notifications (#969)
* 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:
parent
ddcb190457
commit
4d024c06cc
26 changed files with 892 additions and 57 deletions
app/src/main/java/io/lbry/browser
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue