diff --git a/app/build.gradle b/app/build.gradle
index 99ed5f8e..3b8e12de 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,8 @@ android {
applicationId "io.lbry.browser"
minSdkVersion 21
targetSdkVersion 29
- versionCode 1516
- versionName "0.15.16"
+ versionCode 1517
+ versionName "0.15.17"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -101,8 +101,8 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- __32bitImplementation 'io.lbry:lbrysdk32:0.79.1'
- __64bitImplementation 'io.lbry:lbrysdk64:0.79.1'
+ __32bitImplementation 'io.lbry:lbrysdk32:0.80.0'
+ __64bitImplementation 'io.lbry:lbrysdk64:0.80.0'
}
apply plugin: 'com.google.gms.google-services'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7d535e9..7ac0cf49 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,13 +50,6 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java b/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java
index 62536467..aa8f1fe6 100644
--- a/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java
+++ b/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java
@@ -6,6 +6,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
@@ -20,19 +21,22 @@ import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
+import io.lbry.browser.data.DatabaseHelper;
+import io.lbry.browser.model.lbryinc.LbryNotification;
+import io.lbry.browser.utils.Helper;
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.Date;
import java.util.List;
import java.util.Map;
public class LbrynetMessagingService extends FirebaseMessagingService {
+ public static final String ACTION_NOTIFICATION_RECEIVED = "io.lbry.browser.Broadcast.NotificationReceived";
private static final String TAG = "LbrynetMessagingService";
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL";
+ private static final String TYPE_COMMENT = "comment";
private static final String TYPE_SUBSCRIPTION = "subscription";
private static final String TYPE_REWARD = "reward";
private static final String TYPE_INTERESTS = "interests";
@@ -52,10 +56,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
String title = payload.get("title");
String body = payload.get("body");
String name = payload.get("name"); // notification name
- String contentTitle = payload.get("content_title");
- String channelUrl = payload.get("channel_url");
- //String publishTime = payload.get("publish_time");
- String publishTime = null;
+ String hash = payload.get("hash"); // comment hash
if (type != null && getEnabledTypes().indexOf(type) > -1 && body != null && body.trim().length() > 0) {
// only log the receive event for valid notifications received
@@ -65,7 +66,34 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
firebaseAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_RECEIVE, bundle);
}
- sendNotification(title, body, type, url, name, contentTitle, channelUrl, publishTime);
+ if (!Helper.isNullOrEmpty(hash)) {
+ url = String.format("%s?comment_hash=%s", url, hash);
+ }
+
+ sendNotification(title, body, type, url, name);
+ }
+
+ // persist the notification data
+ try {
+ DatabaseHelper helper = DatabaseHelper.getInstance();
+ SQLiteDatabase db = helper.getWritableDatabase();
+ LbryNotification lnotification = new LbryNotification();
+ lnotification.setTitle(title);
+ lnotification.setDescription(body);
+ lnotification.setTargetUrl(url);
+ lnotification.setTimestamp(new Date());
+ DatabaseHelper.createOrUpdateNotification(lnotification, db);
+
+ // send a broadcast
+ Intent intent = new Intent(ACTION_NOTIFICATION_RECEIVED);
+ intent.putExtra("title", title);
+ intent.putExtra("body", body);
+ intent.putExtra("url", url);
+ intent.putExtra("timestamp", lnotification.getTimestamp().getTime());
+ sendBroadcast(intent);
+ } catch (Exception ex) {
+ // don't fail if any error occurs while saving a notification
+ Log.e(TAG, "could not save notification", ex);
}
}
}
@@ -97,8 +125,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
*
* @param messageBody FCM message body received.
*/
- private void sendNotification(String title, String messageBody, String type, String url, String name,
- String contentTitle, String channelUrl, String publishTime) {
+ private void sendNotification(String title, String messageBody, String type, String url, String name) {
//Intent intent = new Intent(this, MainActivity.class);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (url == null) {
@@ -143,6 +170,9 @@ public class LbrynetMessagingService extends FirebaseMessagingService {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
List enabledTypes = new ArrayList();
+ if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_COMMENTS, true)) {
+ enabledTypes.add(TYPE_COMMENT);
+ }
if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS, true)) {
enabledTypes.add(TYPE_SUBSCRIPTION);
}
diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java
index aa82cb97..d9ab24a8 100644
--- a/app/src/main/java/io/lbry/browser/MainActivity.java
+++ b/app/src/main/java/io/lbry/browser/MainActivity.java
@@ -8,7 +8,6 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +114,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 +137,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 +148,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 +226,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Getter
private String firebaseMessagingToken;
+ private NotificationListAdapter notificationListAdapter;
+
private Map openNavFragments;
private static final Map fragmentClassNavIdMap = new HashMap<>();
static {
@@ -282,6 +286,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 +444,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// setup uri bar
setupUriBar();
+ initNotificationsPage();
+ loadUnreadNotificationsCount();
// other
pendingSyncSetQueue = new ArrayList<>();
@@ -475,6 +482,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 +511,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 +572,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() {
@@ -580,7 +601,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- checkSendToIntent(intent);
checkUrlIntent(intent);
checkNotificationOpenIntent(intent);
}
@@ -717,8 +737,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;
@@ -835,12 +856,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
openFragment(FileViewFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params);
}
- public void openSendTo(String path) {
- Map params = new HashMap<>();
- params.put("directFilePath", path);
- openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params);
- }
-
public void openFileClaim(Claim claim) {
Map params = new HashMap<>();
params.put("claimId", claim.getClaimId());
@@ -1067,7 +1082,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 +1117,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 +1656,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 +2034,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 +2055,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 +2119,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() {
+ 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;
}
@@ -2652,19 +2735,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
LbryAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_OPEN, bundle);
}
- private void checkSendToIntent(Intent intent) {
- String intentAction = intent.getAction();
- if (intentAction != null && intentAction.equals("android.intent.action.SEND")) {
- ClipData clipData = intent.getClipData();
- if (clipData != null) {
- Uri uri = clipData.getItemAt(0).getUri();
-
- String path = Helper.getRealPathFromURI_API19(this, uri);
- openSendTo(path);
- }
- }
- }
-
private void registerServiceActionsReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT);
@@ -3051,6 +3121,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() {
+ @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 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>() {
+ protected void onPreExecute() {
+ findViewById(R.id.notification_list_empty_container).setVisibility(View.GONE);
+ findViewById(R.id.notifications_progress).setVisibility(View.VISIBLE);
+ }
+ @Override
+ protected List doInBackground(Void... params) {
+ List notifications = new ArrayList<>();
+ try {
+ SQLiteDatabase db = dbHelper.getReadableDatabase();
+ notifications = DatabaseHelper.getNotifications(db);
+ } catch (Exception ex) {
+ // pass
+ }
+ return notifications;
+ }
+ 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);
+ 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
diff --git a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
index c21bbf20..c36cbf3c 100644
--- a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
+++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
@@ -193,6 +193,10 @@ public class ClaimListAdapter extends RecyclerView.Adapter {
+
+ private static final String RULE_CREATOR_SUBSCRIBER = "creator_subscriber";
+ private static final String RULE_COMMENT = "comment";
+
+ private Context context;
+ private List items;
+ @Setter
+ private NotificationClickListener clickListener;
+ @Getter
+ @Setter
+ private int customizeMode;
+
+ public NotificationListAdapter(List notifications, Context context) {
+ this.context = context;
+ this.items = new ArrayList<>(notifications);
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ protected TextView titleView;
+ protected TextView bodyView;
+ protected TextView timeView;
+ protected SolidIconView iconView;
+ public ViewHolder(View v) {
+ super(v);
+ 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);
+ }
+ }
+
+ public int getItemCount() {
+ return items != null ? items.size() : 0;
+ }
+
+ public void insertNotification(LbryNotification notification, int index) {
+ if (!items.contains(notification)) {
+ items.add(index, notification);
+ }
+ notifyDataSetChanged();
+ }
+
+ public void addNotification(LbryNotification notification) {
+ if (!items.contains(notification)) {
+ items.add(notification);
+ }
+ notifyDataSetChanged();
+ }
+
+ public void addTags(List notifications) {
+ for (LbryNotification notification : notifications) {
+ if (!items.contains(notification)) {
+ items.add(notification);
+ }
+ }
+ notifyDataSetChanged();
+ }
+ @Override
+ public NotificationListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) {
+ View v = LayoutInflater.from(context).inflate(R.layout.list_item_notification, root, false);
+ return new NotificationListAdapter.ViewHolder(v);
+ }
+
+ private int getStringIdForRule(String rule) {
+ if (RULE_CREATOR_SUBSCRIBER.equalsIgnoreCase(rule)) {
+ return R.string.fa_heart;
+ }
+ if (RULE_COMMENT.equalsIgnoreCase(rule)) {
+ return R.string.fa_comment_alt;
+ }
+ return R.string.fa_asterisk;
+ }
+
+ private int getColorForRule(String rule) {
+ if (RULE_CREATOR_SUBSCRIBER.equalsIgnoreCase(rule)) {
+ return Color.RED;
+ }
+ if (RULE_COMMENT.equalsIgnoreCase(rule)) {
+ return ContextCompat.getColor(context, R.color.nextLbryGreen);
+ }
+
+ return ContextCompat.getColor(context, R.color.lbryGreen);
+ }
+
+ @Override
+ public void onBindViewHolder(NotificationListAdapter.ViewHolder vh, int position) {
+ LbryNotification notification = items.get(position);
+ vh.titleView.setText(notification.getTitle());
+ vh.titleView.setVisibility(!Helper.isNullOrEmpty(notification.getTitle()) ? View.VISIBLE : View.GONE);
+ vh.bodyView.setText(notification.getDescription());
+ vh.timeView.setText(DateUtils.getRelativeTimeSpanString(
+ notification.getTimestamp().getTime(),
+ System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
+
+
+ vh.iconView.setText(getStringIdForRule(notification.getRule()));
+ vh.iconView.setTextColor(getColorForRule(notification.getRule()));
+
+ vh.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (clickListener != null) {
+ clickListener.onNotificationClicked(notification);
+ }
+ }
+ });
+ }
+
+ public interface NotificationClickListener {
+ void onNotificationClicked(LbryNotification notification);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java b/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
index 67dabb82..035be3db 100644
--- a/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
+++ b/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
@@ -4,7 +4,6 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.opengl.Visibility;
import java.math.BigDecimal;
import java.text.ParseException;
@@ -13,16 +12,16 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.model.Tag;
import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.ViewHistory;
+import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri;
public class DatabaseHelper extends SQLiteOpenHelper {
- public static final int DATABASE_VERSION = 2;
+ public static final int DATABASE_VERSION = 5;
public static final String DATABASE_NAME = "LbryApp.db";
private static DatabaseHelper instance;
@@ -48,7 +47,18 @@ public class DatabaseHelper extends SQLiteOpenHelper {
", thumbnail_url TEXT" +
", release_time INTEGER " +
", device TEXT" +
- ", timestamp TEXT NOT NULL)"
+ ", timestamp TEXT NOT NULL)",
+ "CREATE TABLE notifications (" +
+ " id INTEGER PRIMARY KEY NOT NULL" +
+ ", remote_id INTEGER NOT NULL" +
+ ", title TEXT" +
+ ", description TEXT" +
+ ", thumbnail_url TEXT" +
+ ", target_url TEXT" +
+ ", rule TEXT" +
+ ", is_read INTEGER DEFAULT 0 NOT NULL" +
+ ", is_seen INTEGER DEFAULT 0 NOT NULL " +
+ ", timestamp TEXT NOT NULL)",
};
private static final String[] SQL_CREATE_INDEXES = {
"CREATE UNIQUE INDEX idx_subscription_url ON subscriptions (url)",
@@ -56,13 +66,36 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"CREATE UNIQUE INDEX idx_url_history_url ON url_history (url)",
"CREATE UNIQUE INDEX idx_tag_name ON tags (name)",
"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)"
};
private static final String[] SQL_V1_V2_UPGRADE = {
"ALTER TABLE view_history ADD COLUMN currency TEXT"
};
+ private static final String[] SQL_V2_V3_UPGRADE = {
+ "CREATE TABLE notifications (" +
+ " id INTEGER PRIMARY KEY NOT NULL" +
+ ", title TEXT" +
+ ", description TEXT" +
+ ", thumbnail_url TEXT" +
+ ", target_url TEXT" +
+ ", is_read INTEGER DEFAULT 0 NOT NULL" +
+ ", timestamp TEXT NOT NULL)",
+ "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_V4_V5_UPGRADE = {
+ "ALTER TABLE notifications ADD COLUMN rule TEXT",
+ "ALTER TABLE notifications ADD COLUMN is_seen TEXT"
+ };
+
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_DELETE_SUBSCRIPTION = "DELETE FROM subscriptions WHERE url = ?";
@@ -73,6 +106,11 @@ 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_GET_RECENT_URL_HISTORY = "SELECT value, url, type FROM url_history ORDER BY timestamp DESC LIMIT 10";
+ private static final String SQL_INSERT_NOTIFICATION = "REPLACE INTO notifications (remote_id, title, description, rule, target_url, is_read, is_seen, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+ private static final String SQL_GET_NOTIFICATIONS = "SELECT id, title, description, rule, target_url, is_read, is_seen, 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_MARK_NOTIFICATIONS_READ = "UPDATE notifications SET is_read = 1 WHERE is_read = 0";
+
private static final String SQL_INSERT_VIEW_HISTORY =
"REPLACE INTO view_history (url, claim_id, claim_name, cost, currency, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String SQL_GET_VIEW_HISTORY =
@@ -111,6 +149,21 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL(sql);
}
}
+ if (oldVersion < 3) {
+ for (String sql : SQL_V2_V3_UPGRADE) {
+ db.execSQL(sql);
+ }
+ }
+ if (oldVersion < 4) {
+ for (String sql : SQL_V3_V4_UPGRADE) {
+ db.execSQL(sql);
+ }
+ }
+ if (oldVersion < 5) {
+ for (String sql : SQL_V4_V5_UPGRADE) {
+ db.execSQL(sql);
+ }
+ }
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
@@ -251,4 +304,60 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return subscriptions;
}
+ public static void createOrUpdateNotification(LbryNotification notification, SQLiteDatabase db) {
+ db.execSQL(SQL_INSERT_NOTIFICATION, new Object[] {
+ notification.getRemoteId(),
+ notification.getTitle(),
+ notification.getDescription(),
+ notification.getRule(),
+ notification.getTargetUrl(),
+ notification.isRead() ? 1 : 0,
+ notification.isSeen() ? 1 : 0,
+ new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(notification.getTimestamp() != null ? notification.getTimestamp() : new Date())
+ });
+ }
+ public static List getNotifications(SQLiteDatabase db) {
+ List notifications = new ArrayList<>();
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery(SQL_GET_NOTIFICATIONS, null);
+ while (cursor.moveToNext()) {
+ LbryNotification notification = new LbryNotification();
+ int columnIndex = 0;
+ notification.setId(cursor.getLong(columnIndex++));
+ notification.setTitle(cursor.getString(columnIndex++));
+ notification.setDescription(cursor.getString(columnIndex++));
+ notification.setRule(cursor.getString(columnIndex++));
+ notification.setTargetUrl(cursor.getString(columnIndex++));
+ notification.setRead(cursor.getInt(columnIndex++) == 1);
+ notification.setSeen(cursor.getInt(columnIndex++) == 1);
+ try {
+ notification.setTimestamp(new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).parse(cursor.getString(columnIndex++)));
+ } catch (ParseException ex) {
+ // invalid timestamp (which shouldn't happen). Skip this item
+ continue;
+ }
+ notifications.add(notification);
+ }
+ } finally {
+ Helper.closeCursor(cursor);
+ }
+ return notifications;
+ }
+ public static int getUnreadNotificationsCount(SQLiteDatabase db) {
+ int count = 0;
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery(SQL_GET_UNREAD_NOTIFICATIONS_COUNT, null);
+ if (cursor.moveToNext()) {
+ count = cursor.getInt(0);
+ }
+ } finally {
+ Helper.closeCursor(cursor);
+ }
+ return count;
+ }
+ public static void markNotificationsRead(SQLiteDatabase db) {
+ db.execSQL(SQL_MARK_NOTIFICATIONS_READ);
+ }
}
diff --git a/app/src/main/java/io/lbry/browser/model/Claim.java b/app/src/main/java/io/lbry/browser/model/Claim.java
index 640b30a5..22794d8f 100644
--- a/app/src/main/java/io/lbry/browser/model/Claim.java
+++ b/app/src/main/java/io/lbry/browser/model/Claim.java
@@ -54,6 +54,7 @@ public class Claim {
@EqualsAndHashCode.Include
private boolean placeholder;
private boolean placeholderAnonymous;
+ private boolean loadingPlaceholder;
private boolean featured;
private boolean unresolved; // used for featured
private String address;
diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/LbryNotification.java b/app/src/main/java/io/lbry/browser/model/lbryinc/LbryNotification.java
new file mode 100644
index 00000000..223acbb0
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/lbryinc/LbryNotification.java
@@ -0,0 +1,19 @@
+package io.lbry.browser.model.lbryinc;
+
+import java.util.Date;
+
+import lombok.Data;
+
+@Data
+public class LbryNotification {
+ private long id;
+ private long remoteId;
+ private String title;
+ private String description;
+ private String rule;
+ private String thumbnailUrl;
+ private String targetUrl;
+ private boolean read;
+ private boolean seen;
+ private Date timestamp;
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/CommentListTask.java b/app/src/main/java/io/lbry/browser/tasks/CommentListTask.java
index 61d49c4b..170fc4c4 100644
--- a/app/src/main/java/io/lbry/browser/tasks/CommentListTask.java
+++ b/app/src/main/java/io/lbry/browser/tasks/CommentListTask.java
@@ -45,10 +45,12 @@ public class CommentListTask extends AsyncTask> {
options.put("claim_id", claim);
options.put("page", page);
options.put("page_size", pageSize);
+ options.put("hidden", false);
options.put("include_replies", false);
options.put("is_channel_signature_valid", true);
+ options.put("skip_validation", true);
options.put("visible", true);
- options.put("hidden", false);
+
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_COMMENT_LIST, options);
JSONArray items = result.getJSONArray("items");
diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/NotificationListTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/NotificationListTask.java
new file mode 100644
index 00000000..dd16b146
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/NotificationListTask.java
@@ -0,0 +1,120 @@
+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 NotificationListTask extends AsyncTask> {
+ private Context context;
+ private ListNotificationsHandler handler;
+ private ProgressBar progressBar;
+ private Exception error;
+
+ public NotificationListTask(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 doInBackground(Void... params) {
+ List 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") && !notificationParams.isNull("dynamic")) {
+ JSONObject dynamic = notificationParams.getJSONObject("dynamic");
+ if (dynamic.has("channelURI")) {
+ String channelUrl = Helper.getJSONString("channelURI", null, dynamic);
+ if (!Helper.isNullOrEmpty(channelUrl)) {
+ notification.setTargetUrl(channelUrl);
+ }
+ }
+ if (dynamic.has("hash") && "comment".equalsIgnoreCase(Helper.getJSONString("notification_rule", null, item))) {
+ notification.setTargetUrl(String.format("%s?comment_hash=%s", notification.getTargetUrl(), dynamic.getString("hash")));
+ }
+ }
+
+ notification.setRule(Helper.getJSONString("notification_rule", null, item));
+ notification.setRemoteId(Helper.getJSONLong("id", 0, item));
+ notification.setRead(Helper.getJSONBoolean("is_read", false, item));
+ notification.setSeen(Helper.getJSONBoolean("is_seen", 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 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 notifications);
+ void onError(Exception exception);
+ }
+}
diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java
index 9adec2d7..6a34eca8 100644
--- a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java
+++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java
@@ -38,6 +38,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatSpinner;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
+import androidx.core.widget.NestedScrollView;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -233,6 +234,9 @@ public class FileViewFragment extends BaseFragment implements
private View inlineChannelCreatorProgress;
private MaterialButton inlineChannelCreatorCreateButton;
+ // if this is set, scroll to the specific comment on load
+ private String commentHash;
+
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_file_view, container, false);
@@ -357,9 +361,27 @@ public class FileViewFragment extends BaseFragment implements
}
}
if (params.containsKey("url")) {
- newUrl = params.get("url").toString();
- if (claim == null || !newUrl.equalsIgnoreCase(currentUrl)) {
- updateRequired = true;
+ LbryUri newLbryUri = LbryUri.tryParse(params.get("url").toString());
+ if (newLbryUri != null) {
+ newUrl = newLbryUri.toString();
+ String qs = newLbryUri.getQueryString();
+ if (!Helper.isNullOrEmpty(qs)) {
+ String[] qsPairs = qs.split("&");
+ for (String pair : qsPairs) {
+ String[] parts = pair.split("=");
+ if (parts.length < 2) {
+ continue;
+ }
+ if ("comment_hash".equalsIgnoreCase(parts[0])) {
+ commentHash = parts[1];
+ break;
+ }
+ }
+ }
+
+ if (claim == null || !newUrl.equalsIgnoreCase(currentUrl)) {
+ updateRequired = true;
+ }
}
}
} else if (currentUrl != null) {
@@ -567,7 +589,6 @@ public class FileViewFragment extends BaseFragment implements
}
checkOwnClaim();
fetchChannels();
- checkAndLoadComments();
}
private String getStreamingUrl() {
@@ -709,7 +730,6 @@ public class FileViewFragment extends BaseFragment implements
} else {
onSdkReady();
}
- checkCommentSdkInitializing();
}
public void onStop() {
@@ -1513,7 +1533,6 @@ public class FileViewFragment extends BaseFragment implements
private void checkAndLoadComments() {
View root = getView();
if (root != null) {
- checkCommentSdkInitializing();
RecyclerView commentsList = root.findViewById(R.id.file_view_comments_list);
if (commentsList == null || commentsList.getAdapter() == null || commentsList.getAdapter().getItemCount() == 0) {
loadComments();
@@ -1521,14 +1540,6 @@ public class FileViewFragment extends BaseFragment implements
}
}
- private void checkCommentSdkInitializing() {
- View root = getView();
- if (root != null) {
- TextView commentsSDKInitializing = root.findViewById(R.id.file_view_comments_sdk_initializing);
- Helper.setViewVisibility(commentsSDKInitializing, Lbry.SDK_READY ? View.GONE : View.VISIBLE);
- }
- }
-
private void showUnsupportedView() {
View root = getView();
if (root != null) {
@@ -2075,10 +2086,21 @@ public class FileViewFragment extends BaseFragment implements
// reset the list view
View root = getView();
if (claim != null && root != null) {
+ Context context = getContext();
+
+ List loadingPlaceholders = new ArrayList<>();
+ for (int i = 0; i < 15; i++) {
+ Claim placeholder = new Claim();
+ placeholder.setLoadingPlaceholder(true);
+ loadingPlaceholders.add(placeholder);
+ }
+ relatedContentAdapter = new ClaimListAdapter(loadingPlaceholders, context);
+ RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list);
+ relatedContentList.setAdapter(relatedContentAdapter);
+
String title = claim.getTitle();
String claimId = claim.getClaimId();
ProgressBar relatedLoading = root.findViewById(R.id.file_view_related_content_progress);
- Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -2098,10 +2120,14 @@ public class FileViewFragment extends BaseFragment implements
Context ctx = getContext();
if (ctx != null) {
- relatedContentAdapter = new ClaimListAdapter(filteredClaims, ctx);
+ relatedContentAdapter.setItems(filteredClaims);
relatedContentAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
@Override
public void onClaimClicked(Claim claim) {
+ if (claim.isLoadingPlaceholder()) {
+ return;
+ }
+
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
if (claim.getName().startsWith("@")) {
@@ -2123,6 +2149,10 @@ public class FileViewFragment extends BaseFragment implements
v.findViewById(R.id.file_view_no_related_content),
relatedContentAdapter == null || relatedContentAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
+
+ // if related content loads before comment, this will affect the scroll position
+ // so just ensure that we are at the correct position
+ scrollToCommentHash();
}
}
@@ -2137,9 +2167,9 @@ public class FileViewFragment extends BaseFragment implements
private void loadComments() {
View root = getView();
- ProgressBar relatedLoading = root.findViewById(R.id.file_view_comments_progress);
+ ProgressBar commentsLoading = root.findViewById(R.id.file_view_comments_progress);
if (claim != null && root != null) {
- CommentListTask task = new CommentListTask(1, 500, claim.getClaimId(), relatedLoading, new CommentListHandler() {
+ CommentListTask task = new CommentListTask(1, 200, claim.getClaimId(), commentsLoading, new CommentListHandler() {
@Override
public void onSuccess(List comments, boolean hasReachedEnd) {
Context ctx = getContext();
@@ -2167,6 +2197,7 @@ public class FileViewFragment extends BaseFragment implements
relatedContentList.setAdapter(commentListAdapter);
commentListAdapter.notifyDataSetChanged();
+ scrollToCommentHash();
checkNoComments();
resolveCommentPosters();
}
@@ -2181,6 +2212,20 @@ public class FileViewFragment extends BaseFragment implements
}
}
+ private void scrollToCommentHash() {
+ View root = getView();
+ // check for the position of commentHash if set
+ if (root != null && !Helper.isNullOrEmpty(commentHash) && commentListAdapter != null && commentListAdapter.getItemCount() > 0) {
+ RecyclerView commentList = root.findViewById(R.id.file_view_comments_list);
+ int position = commentListAdapter.getPositionForComment(commentHash);
+ if (position > -1) {
+ NestedScrollView scrollView = root.findViewById(R.id.file_view_scroll_view);
+ scrollView.requestChildFocus(commentList, commentList);
+ commentList.getLayoutManager().scrollToPosition(position);
+ }
+ }
+ }
+
private void checkNoComments() {
View root = getView();
if (root != null) {
@@ -2194,7 +2239,7 @@ public class FileViewFragment extends BaseFragment implements
long st = System.currentTimeMillis();;
List urlsToResolve = new ArrayList<>(commentListAdapter.getClaimUrlsToResolve());
if (urlsToResolve.size() > 0) {
- ResolveTask task = new ResolveTask(urlsToResolve, Lbry.SDK_CONNECTION_STRING, null, new ClaimListResultHandler() {
+ ResolveTask task = new ResolveTask(urlsToResolve, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() {
@Override
public void onSuccess(List claims) {
if (commentListAdapter != null) {
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 eac70533..5114e840 100644
--- a/app/src/main/java/io/lbry/browser/utils/Helper.java
+++ b/app/src/main/java/io/lbry/browser/utils/Helper.java
@@ -70,6 +70,7 @@ public final class Helper {
public static final String METHOD_GET = "GET";
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_JSON = "yyyy-MM-dd'T'HH:mm:ss'Z'";
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 JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
diff --git a/app/src/main/res/drawable-anydpi/ic_notifications.xml b/app/src/main/res/drawable-anydpi/ic_notifications.xml
new file mode 100644
index 00000000..0dbf589c
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi/ic_notifications.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_notifications.png b/app/src/main/res/drawable-hdpi/ic_notifications.png
new file mode 100644
index 00000000..3a83a649
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notifications.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notifications.png b/app/src/main/res/drawable-mdpi/ic_notifications.png
new file mode 100644
index 00000000..a3a94d02
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notifications.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notifications.png b/app/src/main/res/drawable-xhdpi/ic_notifications.png
new file mode 100644
index 00000000..66126048
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notifications.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notifications.png b/app/src/main/res/drawable-xxhdpi/ic_notifications.png
new file mode 100644
index 00000000..f4016deb
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notifications.png differ
diff --git a/app/src/main/res/drawable/bg_notification_badge.xml b/app/src/main/res/drawable/bg_notification_badge.xml
new file mode 100644
index 00000000..3af51c12
--- /dev/null
+++ b/app/src/main/res/drawable/bg_notification_badge.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml
index dc408393..6777cd17 100644
--- a/app/src/main/res/layout/app_bar_main.xml
+++ b/app/src/main/res/layout/app_bar_main.xml
@@ -28,20 +28,22 @@
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="24dp">
+ android:layout_height="wrap_content">
@@ -61,6 +64,39 @@
android:src="@drawable/ic_close"
android:tint="@color/actionBarForeground" />
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 9fd48cf6..e4315423 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -33,6 +33,53 @@
android:layout_height="match_parent" />
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_stream.xml b/app/src/main/res/layout/list_item_stream.xml
index 04f785a8..6e73ec64 100644
--- a/app/src/main/res/layout/list_item_stream.xml
+++ b/app/src/main/res/layout/list_item_stream.xml
@@ -138,6 +138,13 @@
android:src="@drawable/ic_check"
android:tint="@color/nextLbryGreen" />
+
+
+
+
The lbrynet.log file could not be found.
The lbrynet.log file cannot be shared due to permission restrictions.
+
+ It\'s quiet here! New notifications will be displayed when you receive them.
+
@@ -626,4 +629,7 @@
+
+
+
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index c54f18e5..8e9647bf 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -7,6 +7,7 @@
app:iconSpaceReserved="false">
+