In-app notifications #969

Merged
akinwale merged 13 commits from in-app-notifications into master 2020-08-18 15:19:35 +02:00
6 changed files with 156 additions and 13 deletions
Showing only changes of commit e78f54077f - Show all commits

View file

@ -1,6 +1,5 @@
package io.lbry.browser; package io.lbry.browser;
import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -14,10 +13,8 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import android.text.Html;
import android.util.Log; import android.util.Log;
import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.analytics.FirebaseAnalytics;
@ -27,10 +24,7 @@ import com.google.firebase.messaging.RemoteMessage;
import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.model.lbryinc.LbryNotification; import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.utils.LbryAnalytics; import io.lbry.browser.utils.LbryAnalytics;
import io.lbry.lbrysdk.LbrynetService;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -81,7 +75,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
lnotification.setDescription(body); lnotification.setDescription(body);
lnotification.setTargetUrl(url); lnotification.setTargetUrl(url);
lnotification.setTimestamp(new Date()); lnotification.setTimestamp(new Date());
DatabaseHelper.createNotification(lnotification, db); DatabaseHelper.createOrUpdateNotification(lnotification, db);
// send a broadcast // send a broadcast
Intent intent = new Intent(ACTION_NOTIFICATION_RECEIVED); Intent intent = new Intent(ACTION_NOTIFICATION_RECEIVED);

View file

@ -148,6 +148,7 @@ import io.lbry.browser.tasks.lbryinc.FetchRewardsTask;
import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask;
import io.lbry.browser.tasks.MergeSubscriptionsTask; import io.lbry.browser.tasks.MergeSubscriptionsTask;
import io.lbry.browser.tasks.claim.ResolveTask; import io.lbry.browser.tasks.claim.ResolveTask;
import io.lbry.browser.tasks.lbryinc.ListNotificationsTask;
import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask; import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask;
import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler;
import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask; import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask;
@ -1562,6 +1563,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
try { try {
Lbryio.AUTH_TOKEN = new String(Utils.decrypt( Lbryio.AUTH_TOKEN = new String(Utils.decrypt(
Base64.decode(encryptedAuthToken, Base64.NO_WRAP), this, Lbry.KEYSTORE), "UTF8"); Base64.decode(encryptedAuthToken, Base64.NO_WRAP), this, Lbry.KEYSTORE), "UTF8");
Log.d(TAG, Lbryio.AUTH_TOKEN);
} catch (Exception ex) { } catch (Exception ex) {
// pass. A new auth token would have to be generated if the old one cannot be decrypted // pass. A new auth token would have to be generated if the old one cannot be decrypted
Log.e(TAG, "Could not decrypt existing auth token.", ex); Log.e(TAG, "Could not decrypt existing auth token.", ex);
@ -2071,7 +2073,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
notificationListAdapter.insertNotification(lnotification, 0); notificationListAdapter.insertNotification(lnotification, 0);
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE); findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
} else { } else {
loadNotifications(); loadRemoteNotifications();
} }
} }
@ -2122,7 +2124,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
findViewById(R.id.notifications_container).setVisibility(View.VISIBLE); findViewById(R.id.notifications_container).setVisibility(View.VISIBLE);
((ImageView) findViewById(R.id.notifications_toggle_icon)).setColorFilter(ContextCompat.getColor(this, R.color.lbryGreen)); ((ImageView) findViewById(R.id.notifications_toggle_icon)).setColorFilter(ContextCompat.getColor(this, R.color.lbryGreen));
if (notificationListAdapter == null) { if (notificationListAdapter == null) {
loadNotifications(); loadRemoteNotifications();
} }
markNotificationsRead(); markNotificationsRead();
} }
@ -3144,7 +3146,25 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void loadNotifications() { private void loadRemoteNotifications() {
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
ListNotificationsTask task = new ListNotificationsTask(this, findViewById(R.id.notifications_progress), new ListNotificationsTask.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>>() { (new AsyncTask<Void, Void, List<LbryNotification>>() {
protected void onPreExecute() { protected void onPreExecute() {
findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE); findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);

View file

@ -21,7 +21,7 @@ import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri; import io.lbry.browser.utils.LbryUri;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 3; public static final int DATABASE_VERSION = 4;
public static final String DATABASE_NAME = "LbryApp.db"; public static final String DATABASE_NAME = "LbryApp.db";
private static DatabaseHelper instance; private static DatabaseHelper instance;
@ -50,6 +50,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
", timestamp TEXT NOT NULL)", ", timestamp TEXT NOT NULL)",
"CREATE TABLE notifications (" + "CREATE TABLE notifications (" +
" id INTEGER PRIMARY KEY NOT NULL" + " id INTEGER PRIMARY KEY NOT NULL" +
", remote_id INTEGER NOT NULL" +
", title TEXT" + ", title TEXT" +
", description TEXT" + ", description TEXT" +
", thumbnail_url TEXT" + ", thumbnail_url TEXT" +
@ -64,6 +65,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"CREATE UNIQUE INDEX idx_tag_name ON tags (name)", "CREATE UNIQUE INDEX idx_tag_name ON tags (name)",
"CREATE UNIQUE INDEX idx_view_history_url_device ON view_history (url, device)", "CREATE UNIQUE INDEX idx_view_history_url_device ON view_history (url, device)",
"CREATE INDEX idx_view_history_device ON view_history (device)", "CREATE INDEX idx_view_history_device ON view_history (device)",
"CREATE UNIQUE INDEX idx_notification_remote_id ON notifications (remote_id)",
"CREATE INDEX idx_notification_timestamp ON notifications (timestamp)" "CREATE INDEX idx_notification_timestamp ON notifications (timestamp)"
}; };
@ -83,6 +85,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"CREATE INDEX idx_notification_timestamp ON notifications (timestamp)" "CREATE INDEX idx_notification_timestamp ON notifications (timestamp)"
}; };
private static final String[] SQL_V3_V4_UPGRADE = {
"ALTER TABLE notifications ADD COLUMN remote_id INTEGER",
"CREATE UNIQUE INDEX idx_notification_remote_id ON notifications (remote_id)",
};
private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url) VALUES (?, ?)"; private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url) VALUES (?, ?)";
private static final String SQL_CLEAR_SUBSCRIPTIONS = "DELETE FROM subscriptions"; private static final String SQL_CLEAR_SUBSCRIPTIONS = "DELETE FROM subscriptions";
private static final String SQL_DELETE_SUBSCRIPTION = "DELETE FROM subscriptions WHERE url = ?"; private static final String SQL_DELETE_SUBSCRIPTION = "DELETE FROM subscriptions WHERE url = ?";
@ -93,7 +100,7 @@ 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_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_GET_RECENT_URL_HISTORY = "SELECT value, url, type FROM url_history ORDER BY timestamp DESC LIMIT 10";
private static final String SQL_INSERT_NOTIFICATION = "INSERT INTO notifications (title, description, target_url, timestamp) VALUES (?, ?, ?, ?)"; private static final String SQL_INSERT_NOTIFICATION = "REPLACE INTO notifications (remote_id, title, description, target_url, is_read, 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_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_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_NOTIFICATIONS_READ = "UPDATE notifications SET is_read = 1 WHERE is_read = 0";
@ -141,6 +148,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL(sql); db.execSQL(sql);
} }
} }
if (oldVersion < 4) {
for (String sql : SQL_V3_V4_UPGRADE) {
db.execSQL(sql);
}
}
} }
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -281,11 +293,13 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return subscriptions; return subscriptions;
} }
public static void createNotification(LbryNotification notification, SQLiteDatabase db) { public static void createOrUpdateNotification(LbryNotification notification, SQLiteDatabase db) {
db.execSQL(SQL_INSERT_NOTIFICATION, new Object[] { db.execSQL(SQL_INSERT_NOTIFICATION, new Object[] {
notification.getRemoteId(),
notification.getTitle(), notification.getTitle(),
notification.getDescription(), notification.getDescription(),
notification.getTargetUrl(), notification.getTargetUrl(),
notification.isRead() ? 1 : 0,
new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(notification.getTimestamp() != null ? notification.getTimestamp() : new Date()) new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(notification.getTimestamp() != null ? notification.getTimestamp() : new Date())
}); });
} }

View file

@ -7,6 +7,7 @@ import lombok.Data;
@Data @Data
public class LbryNotification { public class LbryNotification {
private long id; private long id;
private long remoteId;
private String title; private String title;
private String description; private String description;
private String thumbnailUrl; private String thumbnailUrl;

View file

@ -0,0 +1,113 @@
package io.lbry.browser.tasks.lbryinc;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ProgressBar;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import io.lbry.browser.MainActivity;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.exceptions.LbryioRequestException;
import io.lbry.browser.exceptions.LbryioResponseException;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbryio;
public class ListNotificationsTask extends AsyncTask<Void, Void, List<LbryNotification>> {
private Context context;
private ListNotificationsHandler handler;
private ProgressBar progressBar;
private Exception error;
public ListNotificationsTask(Context context, ProgressBar progressBar, ListNotificationsHandler handler) {
this.context = context;
this.progressBar = progressBar;
this.handler = handler;
}
protected void onPreExecute() {
Helper.setViewVisibility(progressBar, View.VISIBLE);
}
protected List<LbryNotification> doInBackground(Void... params) {
List<LbryNotification> notifications = new ArrayList<>();
SQLiteDatabase db = null;
try {
JSONArray array = (JSONArray) Lbryio.parseResponse(Lbryio.call("notification", "list", context));
if (array != null) {
for (int i = 0; i < array.length(); i++) {
JSONObject item = array.getJSONObject(i);
if (item.has("notification_parameters")) {
LbryNotification notification = new LbryNotification();
JSONObject notificationParams = item.getJSONObject("notification_parameters");
if (notificationParams.has("device")) {
JSONObject device = notificationParams.getJSONObject("device");
notification.setTitle(Helper.getJSONString("title", null, device));
notification.setDescription(Helper.getJSONString("text", null, device));
notification.setTargetUrl(Helper.getJSONString("target", null, device));
}
if (notificationParams.has("dynamic")) {
JSONObject dynamic = notificationParams.getJSONObject("dynamic");
String channelUrl = Helper.getJSONString("channelURI", null, dynamic);
if (!Helper.isNullOrEmpty(channelUrl)) {
notification.setTargetUrl(channelUrl);
}
}
notification.setRemoteId(Helper.getJSONLong("id", 0, item));
notification.setRead(Helper.getJSONBoolean("is_read", false, item));
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(Helper.ISO_DATE_FORMAT_JSON, Locale.US);
notification.setTimestamp(dateFormat.parse(Helper.getJSONString("created_at", dateFormat.format(new Date()), item)));
} catch (ParseException ex) {
notification.setTimestamp(new Date());
}
if (notification.getRemoteId() > 0 && !Helper.isNullOrEmpty(notification.getDescription())) {
notifications.add(notification);
}
}
}
if (context instanceof MainActivity) {
db = ((MainActivity) context).getDbHelper().getWritableDatabase();
for (LbryNotification notification : notifications) {
DatabaseHelper.createOrUpdateNotification(notification, db);
}
}
}
} catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException | IllegalStateException ex) {
error = ex;
return null;
}
return notifications;
}
protected void onPostExecute(List<LbryNotification> notifications) {
Helper.setViewVisibility(progressBar, View.GONE);
if (handler != null) {
if (notifications != null) {
handler.onSuccess(notifications);
} else {
handler.onError(error);
}
}
}
public interface ListNotificationsHandler {
void onSuccess(List<LbryNotification> notifications);
void onError(Exception exception);
}
}

View file

@ -70,6 +70,7 @@ public final class Helper {
public static final String METHOD_GET = "GET"; public static final String METHOD_GET = "GET";
public static final String METHOD_POST = "POST"; public static final String METHOD_POST = "POST";
public static final String ISO_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; public static final String ISO_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
public static final String ISO_DATE_FORMAT_JSON = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String SDK_AMOUNT_FORMAT = "0.0#######"; public static final String SDK_AMOUNT_FORMAT = "0.0#######";
public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded"); public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded");
public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");