Merge from upstream

This commit is contained in:
Javi Rueda 2020-10-09 17:12:13 +02:00
commit e024876163
19 changed files with 490 additions and 59 deletions

View file

@ -14,8 +14,8 @@ android {
applicationId "io.lbry.browser"
minSdkVersion 21
targetSdkVersion 29
versionCode 1606
versionName "0.16.6"
versionCode 1607
versionName "0.16.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View file

@ -11,6 +11,7 @@ import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
@ -36,6 +37,7 @@ import android.text.style.TypefaceSpan;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.Menu;
import android.view.WindowManager;
@ -64,7 +66,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.view.ActionMode;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
@ -87,6 +91,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
@ -131,6 +136,7 @@ import io.lbry.browser.listener.FilePickerListener;
import io.lbry.browser.listener.PIPModeListener;
import io.lbry.browser.listener.ScreenOrientationListener;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.listener.StoragePermissionListener;
import io.lbry.browser.listener.WalletBalanceListener;
import io.lbry.browser.model.Claim;
@ -151,6 +157,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.NotificationDeleteTask;
import io.lbry.browser.tasks.lbryinc.NotificationListTask;
import io.lbry.browser.tasks.lbryinc.NotificationUpdateTask;
import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask;
@ -195,7 +202,9 @@ import lombok.Setter;
import lombok.SneakyThrows;
import okhttp3.OkHttpClient;
public class MainActivity extends AppCompatActivity implements SdkStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
public class MainActivity extends AppCompatActivity implements SdkStatusListener,
SharedPreferences.OnSharedPreferenceChangeListener,
ActionMode.Callback, SelectionModeListener {
private static final String CHANNEL_ID_PLAYBACK = "io.lbry.browser.LBRY_PLAYBACK_CHANNEL";
private static final int PLAYBACK_NOTIFICATION_ID = 3;
private static final String SPECIAL_URL_PREFIX = "lbry://?";
@ -347,6 +356,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
private MediaSessionCompat mediaSession;
private boolean receivedStopService;
private ActionBarDrawerToggle toggle;
private SwipeRefreshLayout notificationsSwipeContainer;
private SyncSetTask syncSetTask = null;
private List<WalletSync> pendingSyncSetQueue;
@Getter
@ -530,6 +540,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
});
notificationsSwipeContainer = findViewById(R.id.notifications_list_swipe_container);
notificationsSwipeContainer.setColorSchemeResources(R.color.nextLbryGreen);
notificationsSwipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
notificationsSwipeContainer.setRefreshing(true);
loadRemoteNotifications(false);
}
});
findViewById(R.id.global_now_playing_card).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -1795,6 +1815,103 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
this.actionMode = mode;
if (isDarkMode()) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
actionMode.getMenuInflater().inflate(R.menu.menu_notification, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (R.id.action_delete == item.getItemId()) {
if (notificationListAdapter != null && notificationListAdapter.getSelectedCount() > 0) {
final List<LbryNotification> selectedNotifications = new ArrayList<>(notificationListAdapter.getSelectedItems());
String message = getResources().getQuantityString(R.plurals.confirm_delete_notifications, selectedNotifications.size());
AlertDialog.Builder builder = new AlertDialog.Builder(this).
setTitle(R.string.delete_selection).
setMessage(message)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
handleDeleteSelectedNotifications(selectedNotifications);
}
}).setNegativeButton(R.string.no, null);
builder.show();
return true;
}
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
if (notificationListAdapter != null) {
notificationListAdapter.clearSelectedItems();
notificationListAdapter.setInSelectionMode(false);
notificationListAdapter.notifyDataSetChanged();
}
if (isDarkMode()) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
this.actionMode = null;
}
@Override
public void onEnterSelectionMode() {
startSupportActionMode(this);
}
@Override
public void onExitSelectionMode() {
if (actionMode != null) {
actionMode.finish();
}
}
@Override
public void onItemSelectionToggled() {
if (actionMode != null) {
actionMode.setTitle(notificationListAdapter != null ? String.valueOf(notificationListAdapter.getSelectedCount()) : "");
actionMode.invalidate();
}
}
private void handleDeleteSelectedNotifications(List<LbryNotification> notifications) {
List<Long> remoteIds = new ArrayList<>();
for (LbryNotification notification : notifications) {
remoteIds.add(notification.getRemoteId());
}
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
DatabaseHelper.deleteNotifications(notifications, db);
} catch (Exception ex) {
// pass
}
return null;
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new NotificationDeleteTask(remoteIds).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (notificationListAdapter != null) {
notificationListAdapter.removeNotifications(notifications);
}
if (actionMode != null) {
actionMode.finish();
}
}
private class PlayerNotificationDescriptionAdapter implements PlayerNotificationManager.MediaDescriptionAdapter {
@Override
@ -2631,11 +2748,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
JSONObject item = array.getJSONObject(i);
String claimId = item.getString("claim_id");
String channelName = item.getString("channel_name");
boolean isNotificationsDisabled = item.getBoolean("is_notifications_disabled");
LbryUri url = new LbryUri();
url.setChannelName(channelName);
url.setClaimId(claimId);
subscriptions.add(new Subscription(channelName, url.toString()));
subscriptions.add(new Subscription(channelName, url.toString(), isNotificationsDisabled));
subUrls.add(url.toString());
}
Lbryio.subscriptions = subscriptions;
@ -3040,7 +3158,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// TODO: Broadcast startup status changes
JSONObject startupStatus = status.getJSONObject("startup_status");
android.util.Log.d(TAG, startupStatus.toString(2));
sdkReady = startupStatus.getBoolean("file_manager") && startupStatus.getBoolean("wallet");
}
} catch (ConnectException | JSONException ex) {
@ -3308,6 +3425,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
if (markRead && findViewById(R.id.notifications_container).getVisibility() == View.VISIBLE) {
markNotificationsRead();
}
if (notificationsSwipeContainer != null) {
notificationsSwipeContainer.setRefreshing(false);
}
}
@Override
@ -3315,6 +3436,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// pass
Log.e(TAG, "error loading remote notifications", exception);
loadLocalNotifications();
if (notificationsSwipeContainer != null) {
notificationsSwipeContainer.setRefreshing(false);
}
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -3344,6 +3468,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
if (notificationListAdapter == null) {
notificationListAdapter = new NotificationListAdapter(notifications, MainActivity.this);
notificationListAdapter.setSelectionModeListener(MainActivity.this);
((RecyclerView) findViewById(R.id.notifications_list)).setAdapter(notificationListAdapter);
} else {
notificationListAdapter.addNotifications(notifications);

View file

@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.Calendar;
@ -24,6 +25,7 @@ import java.util.List;
import java.util.TimeZone;
import io.lbry.browser.R;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.lbryinc.LbryNotification;
import io.lbry.browser.ui.controls.SolidIconView;
@ -43,15 +45,19 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
private Context context;
private List<LbryNotification> items;
private List<LbryNotification> selectedItems;
@Setter
private NotificationClickListener clickListener;
@Getter
@Setter
private int customizeMode;
private boolean inSelectionMode;
@Setter
private SelectionModeListener selectionModeListener;
public NotificationListAdapter(List<LbryNotification> notifications, Context context) {
this.context = context;
this.items = new ArrayList<>(notifications);
this.selectedItems = new ArrayList<>();
Collections.sort(items, Collections.reverseOrder(new LbryNotification()));
}
@ -62,6 +68,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
protected TextView timeView;
protected SolidIconView iconView;
protected ImageView thumbnailView;
protected View selectedOverlayView;
public ViewHolder(View v) {
super(v);
layoutView = v.findViewById(R.id.notification_layout);
@ -70,12 +77,25 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
timeView = v.findViewById(R.id.notification_time);
iconView = v.findViewById(R.id.notification_icon);
thumbnailView = v.findViewById(R.id.notification_author_thumbnail);
selectedOverlayView = v.findViewById(R.id.notification_selected_overlay);
}
}
public int getItemCount() {
return items != null ? items.size() : 0;
}
public List<LbryNotification> getSelectedItems() {
return this.selectedItems;
}
public int getSelectedCount() {
return selectedItems != null ? selectedItems.size() : 0;
}
public void clearSelectedItems() {
this.selectedItems.clear();
}
public boolean isNotificationSelected(LbryNotification notification) {
return selectedItems.contains(notification);
}
public void insertNotification(LbryNotification notification, int index) {
if (!items.contains(notification)) {
@ -90,6 +110,12 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
}
notifyDataSetChanged();
}
public void removeNotifications(List<LbryNotification> notifications) {
for (LbryNotification notification : notifications) {
items.remove(notification);
}
notifyDataSetChanged();
}
public List<String> getAuthorUrls() {
List<String> urls = new ArrayList<>();
@ -158,9 +184,8 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
@Override
public void onBindViewHolder(NotificationListAdapter.ViewHolder vh, int position) {
LbryNotification notification = items.get(position);
vh.layoutView.setBackgroundColor(ContextCompat.getColor(context, notification.isSeen() ? android.R.color.transparent : R.color.nextLbryGreenSemiTransparent));
vh.selectedOverlayView.setVisibility(isNotificationSelected(notification) ? View.VISIBLE : View.GONE);
vh.titleView.setVisibility(!Helper.isNullOrEmpty(notification.getTitle()) ? View.VISIBLE : View.GONE);
vh.titleView.setText(notification.getTitle());
@ -181,11 +206,49 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (clickListener != null) {
clickListener.onNotificationClicked(notification);
if (inSelectionMode) {
toggleSelectedNotification(notification);
} else {
if (clickListener != null) {
clickListener.onNotificationClicked(notification);
}
}
}
});
vh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (!inSelectionMode) {
inSelectionMode = true;
if (selectionModeListener != null) {
selectionModeListener.onEnterSelectionMode();
}
}
toggleSelectedNotification(notification);
return true;
}
});
}
private void toggleSelectedNotification(LbryNotification notification) {
if (selectedItems.contains(notification)) {
selectedItems.remove(notification);
} else {
selectedItems.add(notification);
}
if (selectionModeListener != null) {
selectionModeListener.onItemSelectionToggled();
}
if (selectedItems.size() == 0) {
inSelectionMode = false;
if (selectionModeListener != null) {
selectionModeListener.onExitSelectionMode();
}
}
notifyDataSetChanged();
}
private long getLocalNotificationTime(LbryNotification notification) {

View file

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.math.BigDecimal;
import java.sql.SQLInput;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -21,13 +22,13 @@ import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri;
public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 7;
public static final int DATABASE_VERSION = 8;
public static final String DATABASE_NAME = "LbryApp.db";
private static DatabaseHelper instance;
private static final String[] SQL_CREATE_TABLES = {
// local subscription store
"CREATE TABLE subscriptions (url TEXT PRIMARY KEY NOT NULL, channel_name TEXT NOT NULL)",
"CREATE TABLE subscriptions (url TEXT PRIMARY KEY NOT NULL, channel_name TEXT NOT NULL, is_notifications_disabled INTEGER DEFAULT 0 NOT NULL)",
// url entry / suggestion history
"CREATE TABLE url_history (id INTEGER PRIMARY KEY NOT NULL, value TEXT NOT NULL, url TEXT, type INTEGER NOT NULL, timestamp TEXT NOT NULL)",
// tags (known and followed)
@ -105,11 +106,15 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"CREATE TABLE shuffle_watched (id INTEGER PRIMARY KEY NOT NULL, claim_id TEXT NOT NULL)",
"CREATE UNIQUE INDEX idx_shuffle_watched_claim ON shuffle_watched (claim_id)"
};
private static final String[] SQL_V7_V8_UPGRADE = {
"AlTER TABLE subscriptions ADD COLUMN is_notifications_disabled INTEGER DEFAULT 0 NOT NULL"
};
private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url) VALUES (?, ?)";
private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url, is_notifications_disabled) VALUES (?, ?, ?)";
private static final String SQL_UPDATE_SUBSCRIPTION_NOTIFICATION = "UPDATE subscriptions SET is_notification_disabled = ? WHERE url = ?";
private static final String SQL_CLEAR_SUBSCRIPTIONS = "DELETE FROM subscriptions";
private static final String SQL_DELETE_SUBSCRIPTION = "DELETE FROM subscriptions WHERE url = ?";
private static final String SQL_GET_SUBSCRIPTIONS = "SELECT channel_name, url FROM subscriptions";
private static final String SQL_GET_SUBSCRIPTIONS = "SELECT channel_name, url, is_notifications_disabled FROM subscriptions";
private static final String SQL_INSERT_URL_HISTORY = "REPLACE INTO url_history (value, url, type, timestamp) VALUES (?, ?, ?, ?)";
private static final String SQL_CLEAR_URL_HISTORY = "DELETE FROM url_history";
@ -188,6 +193,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL(sql);
}
}
if (oldVersion < 8) {
for (String sql : SQL_V7_V8_UPGRADE) {
db.execSQL(sql);
}
}
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -304,7 +314,14 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
public static void createOrUpdateSubscription(Subscription subscription, SQLiteDatabase db) {
db.execSQL(SQL_INSERT_SUBSCRIPTION, new Object[] { subscription.getChannelName(), subscription.getUrl() });
db.execSQL(SQL_INSERT_SUBSCRIPTION, new Object[] {
subscription.getChannelName(),
subscription.getUrl(),
subscription.isNotificationsDisabled() ? 1 : 0
});
}
public static void setSubscriptionNotificationDisabled(boolean flag, String url, SQLiteDatabase db) {
db.execSQL(SQL_UPDATE_SUBSCRIPTION_NOTIFICATION, new Object[] { flag ? 1 : 0, url });
}
public static void deleteSubscription(Subscription subscription, SQLiteDatabase db) {
db.execSQL(SQL_DELETE_SUBSCRIPTION, new Object[] { subscription.getUrl() });
@ -321,6 +338,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
Subscription subscription = new Subscription();
subscription.setChannelName(cursor.getString(0));
subscription.setUrl(cursor.getString(1));
subscription.setNotificationsDisabled(cursor.getInt(2) == 1);
subscriptions.add(subscription);
}
} finally {
@ -372,6 +390,20 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
return notifications;
}
public static void deleteNotifications(List<LbryNotification> notifications, SQLiteDatabase db) {
StringBuilder sb = new StringBuilder("DELETE FROM notifications WHERE remote_id IN (");
List<Object> remoteIds = new ArrayList<>();
String delim = "";
for (int i = 0; i < notifications.size(); i++) {
remoteIds.add(String.valueOf(notifications.get(i).getRemoteId()));
sb.append(delim).append("?");
delim = ",";
}
sb.append(")");
String sql = sb.toString();
db.execSQL(sql, remoteIds.toArray());
}
public static int getUnreadNotificationsCount(SQLiteDatabase db) {
int count = 0;
Cursor cursor = null;

View file

@ -11,17 +11,21 @@ public class Subscription {
@Getter
@Setter
private String url;
@Getter
@Setter
private boolean isNotificationsDisabled;
public Subscription() {
}
public Subscription(String channelName, String url) {
public Subscription(String channelName, String url, boolean isNotificationsDisabled) {
this.channelName = channelName;
this.url = url;
this.isNotificationsDisabled = isNotificationsDisabled;
}
public static Subscription fromClaim(Claim claim) {
return new Subscription(claim.getName(), claim.getPermanentUrl());
return new Subscription(claim.getName(), claim.getPermanentUrl(), false);
}
public String toString() {
return url;

View file

@ -52,7 +52,6 @@ public class CommentListTask extends AsyncTask<Void, Void, List<Comment>> {
options.put("visible", true);
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_COMMENT_LIST, options);
android.util.Log.d("LbryMain", result.toString(2));
JSONArray items = result.getJSONArray("items");
List<Comment> children = new ArrayList<>();
@ -78,7 +77,6 @@ public class CommentListTask extends AsyncTask<Void, Void, List<Comment>> {
}
} catch (Exception ex) {
error = ex;
android.util.Log.d("LbryMain", ex.toString(), ex);
}
return comments;
}

View file

@ -76,13 +76,15 @@ public class MergeSubscriptionsTask extends AsyncTask<Void, Void, List<Subscript
// check for any remote subs that may have been removed, and unsubscribe from them
for (int i = 0; i < array.length(); i++) {
JSONObject item = array.getJSONObject(i);
// TODO: Refactor by creating static Subscription.fromJSON method
String claimId = item.getString("claim_id");
String channelName = item.getString("channel_name");
boolean isNotificationsDisabled = item.getBoolean("is_notifications_disabled");
LbryUri url = new LbryUri();
url.setChannelName(channelName);
url.setClaimId(claimId);
Subscription subscription = new Subscription(channelName, url.toString());
Subscription subscription = new Subscription(channelName, url.toString(), isNotificationsDisabled);
remoteSubs.add(subscription);
}
}

View file

@ -51,11 +51,11 @@ public class ChannelSubscribeTask extends AsyncTask<Void, Void, Boolean> {
options.put("claim_id", channelClaimId);
if (!isUnsubscribing) {
options.put("channel_name", subscription.getChannelName());
options.put("notifications_disabled", String.valueOf(subscription.isNotificationsDisabled()).toLowerCase());
}
String action = isUnsubscribing ? "delete" : "new";
Lbryio.call("subscription", action, options, context);
Object response = Lbryio.parseResponse(Lbryio.call("subscription", action, options, context));
if (!isUnsubscribing) {
Lbryio.addSubscription(subscription);
} else {

View file

@ -49,11 +49,12 @@ public class FetchSubscriptionsTask extends AsyncTask<Void, Void, List<Subscript
JSONObject item = array.getJSONObject(i);
String claimId = item.getString("claim_id");
String channelName = item.getString("channel_name");
boolean isNotificationsDisabled = item.getBoolean("is_notifications_disabled");
LbryUri url = new LbryUri();
url.setChannelName(channelName);
url.setClaimId(claimId);
Subscription subscription = new Subscription(channelName, url.toString());
Subscription subscription = new Subscription(channelName, url.toString(), isNotificationsDisabled);
subscriptions.add(subscription);
// Persist the subscription locally if it doesn't exist
if (db != null) {

View file

@ -0,0 +1,33 @@
package io.lbry.browser.tasks.lbryinc;
import android.os.AsyncTask;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.lbry.browser.exceptions.LbryioRequestException;
import io.lbry.browser.exceptions.LbryioResponseException;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbryio;
public class NotificationDeleteTask extends AsyncTask<Void, Void, Boolean> {
private List<Long> ids;
public NotificationDeleteTask(List<Long> ids) {
this.ids = ids;
}
protected Boolean doInBackground(Void... params) {
Map<String, String> options = new HashMap<>();
options.put("notification_ids", Helper.joinL(ids, ","));
try {
Object result = Lbryio.parseResponse(Lbryio.call("notification", "delete", options, null));
return "ok".equalsIgnoreCase(result.toString());
} catch (LbryioResponseException | LbryioRequestException ex) {
// pass
}
return false;
}
}

View file

@ -80,6 +80,8 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
private View buttonShare;
private View buttonTip;
private View buttonFollowUnfollow;
private View buttonBell;
private SolidIconView iconBell;
private int subCount;
private OutlineIconView iconFollow;
private SolidIconView iconUnfollow;
@ -112,6 +114,8 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
buttonFollowUnfollow = root.findViewById(R.id.channel_view_follow_unfollow);
iconFollow = root.findViewById(R.id.channel_view_icon_follow);
iconUnfollow = root.findViewById(R.id.channel_view_icon_unfollow);
buttonBell = root.findViewById(R.id.channel_view_subscribe_notify);
iconBell = root.findViewById(R.id.channel_view_icon_bell);
tabPager = root.findViewById(R.id.channel_view_pager);
tabLayout = root.findViewById(R.id.channel_view_tabs);
@ -202,6 +206,39 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
}
});
buttonBell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (claim != null) {
boolean isNotificationsDisabled = Lbryio.isNotificationsDisabled(claim);
final Subscription subscription = Subscription.fromClaim(claim);
subscription.setNotificationsDisabled(!isNotificationsDisabled);
view.setEnabled(false);
Context context = getContext();
new ChannelSubscribeTask(context, claim.getClaimId(), subscription, false, new ChannelSubscribeTask.ChannelSubscribeHandler() {
@Override
public void onSuccess() {
view.setEnabled(true);
Lbryio.updateSubscriptionNotificationsDisabled(subscription);
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).showMessage(subscription.isNotificationsDisabled() ?
R.string.receive_no_notifications : R.string.receive_all_notifications);
}
checkIsFollowing();
}
@Override
public void onError(Exception exception) {
view.setEnabled(true);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
buttonFollowUnfollow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -213,7 +250,7 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
subscribing = true;
boolean isFollowing = Lbryio.isFollowing(claim);
Subscription subscription = Subscription.fromClaim(claim);
buttonFollowUnfollow.setEnabled(false);
view.setEnabled(false);
new ChannelSubscribeTask(getContext(), claim.getClaimId(), subscription, isFollowing, new ChannelSubscribeTask.ChannelSubscribeHandler() {
@Override
public void onSuccess() {
@ -280,8 +317,14 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
private void checkIsFollowing() {
if (claim != null) {
boolean isFollowing = Lbryio.isFollowing(claim);
boolean notificationsDisabled = Lbryio.isNotificationsDisabled(claim);
Helper.setViewVisibility(iconFollow, !isFollowing ? View.VISIBLE : View.GONE);
Helper.setViewVisibility(iconUnfollow, isFollowing ? View.VISIBLE : View.GONE);
Helper.setViewVisibility(buttonBell, isFollowing ? View.VISIBLE : View.GONE);
if (iconBell != null) {
iconBell.setText(notificationsDisabled ? R.string.fa_bell : R.string.fa_bell_slash);
}
}
}

View file

@ -785,6 +785,38 @@ public class FileViewFragment extends BaseFragment implements
}
}
private View.OnClickListener bellIconListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
if (claim != null && claim.getSigningChannel() != null) {
Claim publisher = claim.getSigningChannel();
boolean isNotificationsDisabled = Lbryio.isNotificationsDisabled(publisher);
final Subscription subscription = Subscription.fromClaim(publisher);
subscription.setNotificationsDisabled(!isNotificationsDisabled);
view.setEnabled(false);
Context context = getContext();
new ChannelSubscribeTask(context, publisher.getClaimId(), subscription, false, new ChannelSubscribeTask.ChannelSubscribeHandler() {
@Override
public void onSuccess() {
view.setEnabled(true);
Lbryio.updateSubscriptionNotificationsDisabled(subscription);
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).showMessage(subscription.isNotificationsDisabled() ?
R.string.receive_no_notifications : R.string.receive_all_notifications);
}
checkIsFollowing();
}
@Override
public void onError(Exception exception) {
view.setEnabled(true);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
};
private View.OnClickListener followUnfollowListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -1170,8 +1202,10 @@ public class FileViewFragment extends BaseFragment implements
View buttonFollow = root.findViewById(R.id.file_view_icon_follow);
View buttonUnfollow = root.findViewById(R.id.file_view_icon_unfollow);
View buttonBell = root.findViewById(R.id.file_view_icon_bell);
buttonFollow.setOnClickListener(followUnfollowListener);
buttonUnfollow.setOnClickListener(followUnfollowListener);
buttonBell.setOnClickListener(bellIconListener);
commentChannelSpinnerAdapter = new InlineChannelSpinnerAdapter(getContext(), R.layout.spinner_item_channel, new ArrayList<>());
commentChannelSpinnerAdapter.addPlaceholder(false);
@ -2524,13 +2558,18 @@ public class FileViewFragment extends BaseFragment implements
private void checkIsFollowing() {
if (claim != null && claim.getSigningChannel() != null) {
boolean isFollowing = Lbryio.isFollowing(claim.getSigningChannel());
boolean notificationsDisabled = Lbryio.isNotificationsDisabled(claim.getSigningChannel());
Context context = getContext();
View root = getView();
if (context != null && root != null) {
OutlineIconView iconFollow = root.findViewById(R.id.file_view_icon_follow);
SolidIconView iconUnfollow = root.findViewById(R.id.file_view_icon_unfollow);
Helper.setViewVisibility(iconFollow, !isFollowing ? View.VISIBLE: View.INVISIBLE);
Helper.setViewVisibility(iconUnfollow, isFollowing ? View.VISIBLE : View.INVISIBLE);
SolidIconView iconBell = root.findViewById(R.id.file_view_icon_bell);
Helper.setViewVisibility(iconFollow, !isFollowing ? View.VISIBLE: View.GONE);
Helper.setViewVisibility(iconUnfollow, isFollowing ? View.VISIBLE : View.GONE);
Helper.setViewVisibility(iconBell, isFollowing ? View.VISIBLE : View.GONE);
iconBell.setText(notificationsDisabled ? R.string.fa_bell : R.string.fa_bell_slash);
}
}
}

View file

@ -327,6 +327,15 @@ public final class Lbryio {
}
}
public static void updateSubscriptionNotificationsDisabled(Subscription subscription) {
synchronized (lock) {
int index = subscriptions.indexOf(subscription);
if (index > -1) {
subscriptions.get(index).setNotificationsDisabled(subscription.isNotificationsDisabled());
}
}
}
public static void addSubscription(Subscription subscription) {
synchronized (lock) {
if (!subscriptions.contains(subscription)) {
@ -358,6 +367,15 @@ public final class Lbryio {
public static boolean isFollowing(Claim claim) {
return subscriptions.contains(Subscription.fromClaim(claim));
}
public static boolean isNotificationsDisabled(Claim claim) {
Subscription sub = Subscription.fromClaim(claim);
int index = subscriptions.indexOf(sub);
if (index > -1) {
Subscription actual = subscriptions.get(subscriptions.indexOf(sub));
return actual.isNotificationsDisabled();
}
return false;
}
public static void updateRewardsLists(List<Reward> rewards) {
synchronized (lock) {

View file

@ -71,13 +71,18 @@
android:textSize="16sp"
android:textAlignment="center" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notifications_list"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/notifications_list_swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:clipToPadding="false"
/>
android:layout_height="match_parent">
<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"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</RelativeLayout>
<include layout="@layout/floating_wallet_balance" />

View file

@ -224,9 +224,27 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:text="@string/fa_heart_broken"
android:textColor="@color/foreground"
android:textSize="20dp"
android:visibility="invisible"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/channel_view_subscribe_notify"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="8dp"
android:visibility="gone">
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/channel_view_icon_bell"
android:layout_centerInParent="true"
android:layout_width="24dp"
android:layout_height="24dp"
android:text="@string/fa_bell_slash"
android:textColor="@color/foreground"
android:textSize="20dp"/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View file

@ -503,7 +503,6 @@
android:id="@+id/file_view_publisher_area"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/file_view_publisher_info_area"
android:background="?attr/selectableItemBackground"
@ -513,7 +512,7 @@
android:paddingTop="12dp"
android:paddingStart="16dp"
android:paddingBottom="12dp"
android:layout_toStartOf="@id/file_view_icon_follow">
android:layout_toStartOf="@id/file_view_publisher_area_actions">
<RelativeLayout
android:id="@+id/file_view_publisher_avatar"
android:layout_width="50dp"
@ -570,33 +569,50 @@
</LinearLayout>
</LinearLayout>
<io.lbry.browser.ui.controls.OutlineIconView
android:id="@+id/file_view_icon_follow"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:layout_alignParentEnd="true"
<LinearLayout
android:id="@+id/file_view_publisher_area_actions"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_centerVertical="true"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/fa_heart"
android:textColor="@color/red"
android:textSize="20dp" />
android:layout_alignParentEnd="true">
<io.lbry.browser.ui.controls.OutlineIconView
android:id="@+id/file_view_icon_follow"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/fa_heart"
android:textColor="@color/red"
android:textSize="20dp" />
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/file_view_icon_unfollow"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/fa_heart_broken"
android:textSize="20dp"
android:visibility="invisible" />
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/file_view_icon_unfollow"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/fa_heart_broken"
android:textColor="@color/foreground"
android:textSize="20dp"
android:visibility="gone" />
<io.lbry.browser.ui.controls.SolidIconView
android:id="@+id/file_view_icon_bell"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:text="@string/fa_bell_slash"
android:textColor="@color/foreground"
android:textSize="20dp"
android:visibility="gone"/>
</LinearLayout>
</RelativeLayout>
<View

View file

@ -28,6 +28,20 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="invisible" />
<RelativeLayout
android:layout_centerHorizontal="true"
android:background="@drawable/bg_channel_overlay_icon"
android:id="@+id/notification_selected_overlay"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_check"
android:tint="@color/nextLbryGreen" />
</RelativeLayout>
</RelativeLayout>
<LinearLayout
android:orientation="vertical"

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete"
android:title="@string/delete"
app:iconTint="@color/actionBarForeground"
app:showAsAction="always" />
</menu>

View file

@ -117,6 +117,8 @@
<string name="content">Content</string>
<string name="website">Website</string>
<string name="reposted">reposted</string>
<string name="receive_all_notifications">You will receive all notifications</string>
<string name="receive_no_notifications">You will not receive notifications for this channel</string>
<plurals name="follower_count">
<item quantity="one">%1$s follower</item>
<item quantity="other">%1$s followers</item>
@ -382,6 +384,7 @@
<string name="channel_to_show_support_as">Channel to show support as</string>
<string name="make_this_a_tip">Make this a tip</string>
<string name="send_revocable_support">Send Revocable Support</string>
<string name="send_lbc_tip">Tip %1$s</string>
<plurals name="send_lbc_tip">
<item quantity="one">Tip %1$s Credit</item>
<item quantity="other">Tip %1$s Credits</item>
@ -616,6 +619,10 @@
<!-- Notifications -->
<string name="no_notifications">It\'s quiet here! New notifications will be displayed when you receive them.</string>
<plurals name="confirm_delete_notifications">
<item quantity="one">Are you sure you want to remove the selected notification?</item>
<item quantity="other">Are you sure you want to remove the selected notifications?</item>
</plurals>
<!-- Font Awesome -->
<string name="fa_gift" translatable="false">&#xf06b;</string>
@ -645,4 +652,7 @@
<string name="fa_comment_alt" translatable="false">&#xf27a;</string>
<string name="fa_broadcast_tower" translatable="false">&#xf519;</string>
<string name="fa_random" translatable="false">&#xf074;</string>
<string name="fa_bell" translatable="false">&#xf0f3;</string>
<string name="fa_bell_slash" translatable="false">&#xf1f6;</string>
</resources>