In-app notifications #969
6 changed files with 156 additions and 13 deletions
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue