In-app notifications #969
26 changed files with 894 additions and 59 deletions
|
@ -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'
|
||||
|
|
|
@ -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<String> enabledTypes = new ArrayList<String>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -114,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;
|
||||
|
@ -136,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;
|
||||
|
@ -146,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;
|
||||
|
@ -223,6 +226,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 {
|
||||
|
@ -281,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";
|
||||
|
@ -438,6 +444,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
|
||||
// setup uri bar
|
||||
setupUriBar();
|
||||
initNotificationsPage();
|
||||
loadUnreadNotificationsCount();
|
||||
|
||||
// other
|
||||
pendingSyncSetQueue = new ArrayList<>();
|
||||
|
@ -474,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);
|
||||
}
|
||||
|
@ -502,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);
|
||||
}
|
||||
}
|
||||
|
@ -550,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() {
|
||||
|
@ -715,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;
|
||||
|
@ -1059,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);
|
||||
|
@ -1094,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()) {
|
||||
|
@ -1628,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);
|
||||
|
@ -1998,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) {
|
||||
|
@ -2018,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2063,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<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;
|
||||
}
|
||||
|
@ -3030,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<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
|
||||
|
|
|
@ -193,6 +193,10 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
|
|||
protected TextView fileSizeView;
|
||||
protected ProgressBar downloadProgressView;
|
||||
protected TextView deviceView;
|
||||
|
||||
protected View loadingImagePlaceholder;
|
||||
protected View loadingTextPlaceholder1;
|
||||
protected View loadingTextPlaceholder2;
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
feeContainer = v.findViewById(R.id.claim_fee_container);
|
||||
|
@ -212,6 +216,10 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
|
|||
fileSizeView = v.findViewById(R.id.claim_file_size);
|
||||
downloadProgressView = v.findViewById(R.id.claim_download_progress);
|
||||
deviceView = v.findViewById(R.id.claim_view_device);
|
||||
|
||||
loadingImagePlaceholder = v.findViewById(R.id.claim_thumbnail_placeholder);
|
||||
loadingTextPlaceholder1 = v.findViewById(R.id.claim_text_loading_placeholder_1);
|
||||
loadingTextPlaceholder2 = v.findViewById(R.id.claim_text_loading_placeholder_2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,7 +402,7 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
|
|||
});
|
||||
|
||||
vh.publishTimeView.setVisibility(!isPending ? View.VISIBLE : View.GONE);
|
||||
vh.pendingTextView.setVisibility(isPending ? View.VISIBLE : View.GONE);
|
||||
vh.pendingTextView.setVisibility(isPending && !item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
vh.repostInfoView.setVisibility(isRepost && type != VIEW_TYPE_FEATURED ? View.VISIBLE : View.GONE);
|
||||
vh.repostChannelView.setText(isRepost ? original.getSigningChannel().getName() : null);
|
||||
vh.repostChannelView.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -417,6 +425,13 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
|
|||
vh.noThumbnailView.setVisibility(Helper.isNullOrEmpty(thumbnailUrl) ? View.VISIBLE : View.GONE);
|
||||
Helper.setIconViewBackgroundColor(vh.noThumbnailView, bgColor, false, context);
|
||||
|
||||
Helper.setViewVisibility(vh.loadingImagePlaceholder, item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(vh.loadingTextPlaceholder1, item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(vh.loadingTextPlaceholder2, item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(vh.titleView, !item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(vh.publisherView, !item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(vh.publishTimeView, !item.isLoadingPlaceholder() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (type == VIEW_TYPE_FEATURED && item.isUnresolved()) {
|
||||
vh.durationView.setVisibility(View.GONE);
|
||||
vh.titleView.setText("Nothing here. Publish something!");
|
||||
|
|
|
@ -61,6 +61,16 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
public int getPositionForComment(String commentHash) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (commentHash.equalsIgnoreCase(items.get(i).getId())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items != null ? items.size() : 0;
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package io.lbry.browser.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.lbry.browser.R;
|
||||
import io.lbry.browser.model.lbryinc.LbryNotification;
|
||||
import io.lbry.browser.ui.controls.SolidIconView;
|
||||
import io.lbry.browser.utils.Helper;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class NotificationListAdapter extends RecyclerView.Adapter<NotificationListAdapter.ViewHolder> {
|
||||
|
||||
private static final String RULE_CREATOR_SUBSCRIBER = "creator_subscriber";
|
||||
private static final String RULE_COMMENT = "comment";
|
||||
|
||||
private Context context;
|
||||
private List<LbryNotification> items;
|
||||
@Setter
|
||||
private NotificationClickListener clickListener;
|
||||
@Getter
|
||||
@Setter
|
||||
private int customizeMode;
|
||||
|
||||
public NotificationListAdapter(List<LbryNotification> 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<LbryNotification> 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);
|
||||
}
|
||||
}
|
|
@ -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<LbryNotification> getNotifications(SQLiteDatabase db) {
|
||||
List<LbryNotification> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -45,10 +45,12 @@ public class CommentListTask extends AsyncTask<Void, Void, List<Comment>> {
|
|||
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");
|
||||
|
|
|
@ -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<Void, Void, List<LbryNotification>> {
|
||||
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<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") && !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<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);
|
||||
}
|
||||
}
|
|
@ -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,11 +361,29 @@ public class FileViewFragment extends BaseFragment implements
|
|||
}
|
||||
}
|
||||
if (params.containsKey("url")) {
|
||||
newUrl = params.get("url").toString();
|
||||
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) {
|
||||
updateRequired = true;
|
||||
} else if (context instanceof MainActivity) {
|
||||
|
@ -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<Claim> 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<Comment> 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<String> 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<Claim> claims) {
|
||||
if (commentListAdapter != null) {
|
||||
|
|
|
@ -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");
|
||||
|
|
11
app/src/main/res/drawable-anydpi/ic_notifications.xml
Normal file
11
app/src/main/res/drawable-anydpi/ic_notifications.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF"
|
||||
android:alpha="0.8">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable-hdpi/ic_notifications.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_notifications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 273 B |
BIN
app/src/main/res/drawable-mdpi/ic_notifications.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_notifications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 203 B |
BIN
app/src/main/res/drawable-xhdpi/ic_notifications.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_notifications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 333 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_notifications.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_notifications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 B |
6
app/src/main/res/drawable/bg_notification_badge.xml
Normal file
6
app/src/main/res/drawable/bg_notification_badge.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/red" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
|
@ -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">
|
||||
<EditText
|
||||
android:id="@+id/wunderbar"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_centerVertical="true"
|
||||
android:drawableLeft="@drawable/ic_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="@color/actionBarForeground"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/uri_placeholder"
|
||||
android:imeOptions="actionGo"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingRight="36dp"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:textFontWeight="300"
|
||||
|
@ -52,6 +54,7 @@
|
|||
android:background="?attr/selectableItemBackground"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone">
|
||||
|
@ -61,6 +64,39 @@
|
|||
android:src="@drawable/ic_close"
|
||||
android:tint="@color/actionBarForeground" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/wunderbar_notifications"
|
||||
android:clickable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true">
|
||||
<ImageView
|
||||
android:id="@+id/notifications_toggle_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/ic_notifications"
|
||||
android:tint="@color/actionBarForeground" />
|
||||
<TextView
|
||||
android:id="@+id/notifications_badge_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_notification_badge"
|
||||
android:fontFamily="@font/inter"
|
||||
android:gravity="center_horizontal"
|
||||
android:minWidth="12dp"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="10sp"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
|
|
@ -33,6 +33,53 @@
|
|||
android:layout_height="match_parent" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/notifications_container"
|
||||
android:background="@color/pageBackground"
|
||||
android:elevation="6dp"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
android:id="@+id/notifications_progress"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="gone" />
|
||||
<LinearLayout
|
||||
android:id="@+id/notification_list_empty_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:padding="36dp"
|
||||
android:visibility="gone">
|
||||
<ImageView
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="300dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/gerbil_happy" />
|
||||
<TextView
|
||||
android:text="@string/no_notifications"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="center" />
|
||||
</LinearLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/notifications_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:clipToPadding="false"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<include layout="@layout/floating_wallet_balance" />
|
||||
|
||||
<RelativeLayout
|
||||
|
|
|
@ -721,19 +721,6 @@
|
|||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_view_comments_sdk_initializing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:text="@string/sdk_initializing_comments"
|
||||
android:textFontWeight="300"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/file_view_comments_list"
|
||||
android:layout_width="match_parent"
|
||||
|
|
50
app/src/main/res/layout/list_item_notification.xml
Normal file
50
app/src/main/res/layout/list_item_notification.xml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<io.lbry.browser.ui.controls.SolidIconView
|
||||
android:id="@+id/notification_icon"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textSize="24dp" />
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:id="@+id/notification_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp" />
|
||||
<TextView
|
||||
android:id="@+id/notification_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/notification_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -138,6 +138,13 @@
|
|||
android:src="@drawable/ic_check"
|
||||
android:tint="@color/nextLbryGreen" />
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/claim_thumbnail_placeholder"
|
||||
android:background="@color/lighterGrey"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="90dp"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -146,6 +153,22 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_toRightOf="@id/claim_media_container">
|
||||
<LinearLayout
|
||||
android:id="@+id/claim_text_loading_placeholder_1"
|
||||
android:background="@color/lighterGrey"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:visibility="gone" />
|
||||
<LinearLayout
|
||||
android:id="@+id/claim_text_loading_placeholder_2"
|
||||
android:background="@color/lighterGrey"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/claim_vanity_url"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -603,6 +603,9 @@
|
|||
<string name="cannot_find_lbrynet_log">The lbrynet.log file could not be found.</string>
|
||||
<string name="cannot_share_lbrynet_log">The lbrynet.log file cannot be shared due to permission restrictions.</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="no_notifications">It\'s quiet here! New notifications will be displayed when you receive them.</string>
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<string name="fa_gift" translatable="false"></string>
|
||||
<string name="fa_lock" translatable="false"></string>
|
||||
|
@ -626,4 +629,7 @@
|
|||
<string name="fa_mobile_alt" translatable="false"></string>
|
||||
<string name="fa_repost" translatable="false"></string>
|
||||
<string name="fa_folder_open" translatable="false"></string>
|
||||
|
||||
<string name="fa_asterisk" translatable="false"></string>
|
||||
<string name="fa_comment_alt" translatable="false"></string>
|
||||
</resources>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
app:iconSpaceReserved="false">
|
||||
<SwitchPreferenceCompat
|
||||
app:key="io.lbry.browser.preference.userinterface.BackgroundPlayback"
|
||||
app:defaultValue="true"
|
||||
app:title="@string/enable_background_playback"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
|
@ -27,6 +28,11 @@
|
|||
<PreferenceCategory
|
||||
android:title="@string/notifications"
|
||||
app:iconSpaceReserved="false">
|
||||
<SwitchPreferenceCompat
|
||||
app:key="io.lbry.browser.preference.notifications.Comments"
|
||||
app:title="@string/comments"
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="io.lbry.browser.preference.notifications.Subscriptions"
|
||||
app:title="@string/subscriptions"
|
||||
|
|
Loading…
Reference in a new issue