Channel management and channel creation / editing

This commit is contained in:
Akinwale Ariwodola 2020-05-07 19:06:59 +01:00
parent f45c517048
commit 2df4d76927
44 changed files with 2342 additions and 141 deletions

View file

@ -26,7 +26,7 @@
android:supportsPictureInPicture="true"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -49,6 +49,7 @@ import io.lbry.browser.model.Claim;
import io.lbry.browser.model.ClaimCacheKey;
import io.lbry.browser.model.File;
import io.lbry.browser.model.Tag;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.ClaimSearchTask;
import io.lbry.browser.tasks.FileListTask;
import io.lbry.browser.tasks.LighthouseSearchTask;
@ -266,7 +267,7 @@ public class FileViewActivity extends AppCompatActivity {
private void resolveUrl(String url) {
resolving = true;
View loadingView = findViewById(R.id.file_view_loading_container);
ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, loadingView, new ResolveTask.ResolveResultHandler() {
ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, loadingView, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (claims.size() > 0) {

View file

@ -1,5 +1,6 @@
package io.lbry.browser;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
@ -10,6 +11,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.sqlite.SQLiteDatabase;
@ -38,6 +40,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
@ -47,7 +50,6 @@ import androidx.core.view.GravityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.preference.PreferenceManager;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
@ -63,6 +65,7 @@ import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.nio.channels.Channel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -88,6 +91,7 @@ import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.WalletBalance;
import io.lbry.browser.model.WalletSync;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.LighthouseAutoCompleteTask;
import io.lbry.browser.tasks.MergeSubscriptionsTask;
import io.lbry.browser.tasks.ResolveTask;
@ -99,7 +103,9 @@ import io.lbry.browser.tasks.wallet.SyncGetTask;
import io.lbry.browser.tasks.wallet.SyncSetTask;
import io.lbry.browser.tasks.wallet.WalletBalanceTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.ui.channel.ChannelFormFragment;
import io.lbry.browser.ui.channel.ChannelFragment;
import io.lbry.browser.ui.channel.ChannelManagerFragment;
import io.lbry.browser.ui.editorschoice.EditorsChoiceFragment;
import io.lbry.browser.ui.following.FollowingFragment;
import io.lbry.browser.ui.search.SearchFragment;
@ -132,14 +138,23 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS);
fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT);
fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS);
// Internal (sub-)pages
fragmentClassNavIdMap.put(ChannelFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
fragmentClassNavIdMap.put(SearchFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
//fragmentClassNavIdMap.put(ChannelFormFragment.class, NavMenuItem.ID_ITEM_CHANNELS);
}
public static final int REQUEST_STORAGE_PERMISSION = 1001;
public static final int REQUEST_SIMPLE_SIGN_IN = 2001;
public static final int REQUEST_WALLET_SYNC_SIGN_IN = 2002;
public static final int REQUEST_FILE_PICKER = 5001;
// broadcast action names
public static final String ACTION_SDK_READY = "io.lbry.browser.Broadcast.SdkReady";
public static final String ACTION_AUTH_TOKEN_GENERATED = "io.lbry.browser.Broadcast.AuthTokenGenerated";
@ -205,24 +220,33 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
private boolean pendingFollowingReload;
private final List<Integer> supportedMenuItemIds = Arrays.asList(
// find content
NavMenuItem.ID_ITEM_FOLLOWING,
NavMenuItem.ID_ITEM_EDITORS_CHOICE,
NavMenuItem.ID_ITEM_ALL_CONTENT,
// your content
NavMenuItem.ID_ITEM_CHANNELS,
// wallet
NavMenuItem.ID_ITEM_WALLET,
NavMenuItem.ID_ITEM_SETTINGS
);
public boolean isDarkMode() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
return sp.getBoolean(PREFERENCE_KEY_DARK_MODE, false);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
boolean darkMode = sp.getBoolean(PREFERENCE_KEY_DARK_MODE, false);
AppCompatDelegate.setDefaultNightMode(darkMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(isDarkMode() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
initKeyStore();
loadAuthToken();
dbHelper = new DatabaseHelper(this);
if (!darkMode) {
if (!isDarkMode()) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
@ -367,6 +391,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
case NavMenuItem.ID_ITEM_ALL_CONTENT:
openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT);
break;
case NavMenuItem.ID_ITEM_CHANNELS:
openFragment(ChannelManagerFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS);
break;
case NavMenuItem.ID_ITEM_WALLET:
openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET);
break;
@ -384,6 +413,14 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
openFragment(ChannelFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params);
}
public void openChannelForm(Claim claim) {
Map<String, Object> params = new HashMap<>();
if (claim != null) {
params.put("claim", claim);
}
openFragment(ChannelFormFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS, params);
}
public void openChannelUrl(String url) {
Map<String, Object> params = new HashMap<>();
params.put("url", url);
@ -667,7 +704,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
private void resolveUrlSuggestions(List<String> urls) {
ResolveTask task = new ResolveTask(urls, Lbry.LBRY_TV_CONNECTION_STRING, null, new ResolveTask.ResolveResultHandler() {
ResolveTask task = new ResolveTask(urls, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) {
@ -1181,23 +1218,50 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
registerReceiver(userActionsReceiver, intentFilter);
}
public void showMessage(int stringResourceId) {
Snackbar.make(findViewById(R.id.content_main), stringResourceId, Snackbar.LENGTH_LONG).show();
}
public void showMessage(String message) {
Snackbar.make(findViewById(R.id.content_main), message, Snackbar.LENGTH_LONG).show();
}
@Override
public void onBackPressed() {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
// check fragment and nav history
FragmentManager manager = getSupportFragmentManager();
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount > 0) {
// we can pop the stack
manager.popBackStack();
setSelectedNavMenuItemForFragment(getCurrentFragment());
} else if (!enterPIPMode()) {
// we're at the top of the stack
moveTaskToBack(true);
return;
boolean handled = false;
if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) {
clearWunderbarFocus(findViewById(R.id.wunderbar));
handled = true;
} else {
ChannelFormFragment channelFormFragment = null;
for (Fragment fragment : openNavFragments.values()) {
if (fragment instanceof ChannelFormFragment) {
channelFormFragment = ((ChannelFormFragment) fragment);
break;
}
}
if (channelFormFragment != null && channelFormFragment.isSaveInProgress()) {
handled = true;
return;
}
}
if (!handled) {
// check fragment and nav history
FragmentManager manager = getSupportFragmentManager();
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount > 0) {
// we can pop the stack
manager.popBackStack();
setSelectedNavMenuItemForFragment(getCurrentFragment());
} else if (!enterPIPMode()) {
// we're at the top of the stack
moveTaskToBack(true);
return;
}
}
}
}
@ -1215,22 +1279,72 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
// user signed in
showSignedInUser();
if (requestCode == REQUEST_WALLET_SYNC_SIGN_IN) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, true).apply();
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_STORAGE_PERMISSION:
ChannelFormFragment channelFormFragment = null;
//PublishFormFragment publishFormFragment = null;
for (Fragment fragment : openNavFragments.values()) {
if (fragment instanceof WalletFragment) {
((WalletFragment) fragment).onWalletSyncEnabled();
if (fragment instanceof ChannelFormFragment) {
channelFormFragment = ((ChannelFormFragment) fragment);
break;
}
}
scheduleWalletSyncTask();
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (channelFormFragment != null) {
channelFormFragment.onStoragePermissionGranted();
}
} else {
if (channelFormFragment != null) {
channelFormFragment.onStoragePermissionRefused();
}
}
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_FILE_PICKER) {
ChannelFormFragment channelFormFragment = null;
//PublishFormFragment publishFormFragment = null;
for (Fragment fragment : openNavFragments.values()) {
if (fragment instanceof ChannelFormFragment) {
channelFormFragment = ((ChannelFormFragment) fragment);
break;
}
}
if (resultCode == RESULT_OK) {
Uri fileUri = data.getData();
String filePath = Helper.getRealPathFromURI_API19(this, fileUri);
if (channelFormFragment != null) {
channelFormFragment.onFilePicked(filePath);
}
} else {
if (channelFormFragment != null) {
channelFormFragment.onFilePicked(null);
}
}
} else if (requestCode == REQUEST_SIMPLE_SIGN_IN || requestCode == REQUEST_WALLET_SYNC_SIGN_IN) {
if (resultCode == RESULT_OK) {
// user signed in
showSignedInUser();
if (requestCode == REQUEST_WALLET_SYNC_SIGN_IN) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, true).apply();
for (Fragment fragment : openNavFragments.values()) {
if (fragment instanceof WalletFragment) {
((WalletFragment) fragment).onWalletSyncEnabled();
}
}
scheduleWalletSyncTask();
}
}
}
}
@ -1765,4 +1879,19 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static void requestPermission(String permission, int requestCode, String rationale, Context context, boolean forceRequest) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
if (!forceRequest && ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) {
Toast.makeText(context, rationale, Toast.LENGTH_LONG).show();
} else {
ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, requestCode);
}
}
}
public static boolean hasPermission(String permission, Context context) {
return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
}
}

View file

@ -18,9 +18,11 @@ import java.util.ArrayList;
import java.util.List;
import io.lbry.browser.R;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri;
import lombok.Getter;
import lombok.Setter;
public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.ViewHolder> {
@ -33,6 +35,11 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
private List<Claim> selectedItems;
@Setter
private ClaimListItemListener listener;
@Getter
@Setter
private boolean inSelectionMode;
@Setter
private SelectionModeListener selectionModeListener;
public ClaimListAdapter(List<Claim> items, Context context) {
this.context = context;
@ -43,6 +50,9 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
public List<Claim> getSelectedItems() {
return this.selectedItems;
}
public int getSelectedCount() {
return selectedItems != null ? selectedItems.size() : 0;
}
public void clearSelectedItems() {
this.selectedItems.clear();
}
@ -78,6 +88,16 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
}
notifyDataSetChanged();
}
public void setItems(List<Claim> claims) {
items = new ArrayList<>(claims);
notifyDataSetChanged();
}
public void removeItem(Claim claim) {
items.remove(claim);
selectedItems.remove(claim);
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
protected View feeContainer;
@ -92,6 +112,7 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
protected TextView publishTimeView;
protected View repostInfoView;
protected TextView repostChannelView;
protected View selectedOverlayView;
public ViewHolder(View v) {
super(v);
feeContainer = v.findViewById(R.id.claim_fee_container);
@ -106,6 +127,7 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
publishTimeView = v.findViewById(R.id.claim_publish_time);
repostInfoView = v.findViewById(R.id.claim_repost_info);
repostChannelView = v.findViewById(R.id.claim_repost_channel);
selectedOverlayView = v.findViewById(R.id.claim_selected_overlay);
}
}
@ -160,14 +182,34 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
bgColor = Helper.generateRandomColorForValue(item.getName());
}
boolean isSelected = isClaimSelected(item);
vh.itemView.setSelected(isSelected);
vh.selectedOverlayView.setVisibility(isSelected ? View.VISIBLE : View.GONE);
vh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
listener.onClaimClicked(item);
if (inSelectionMode) {
toggleSelectedClaim(item);
} else {
if (listener != null) {
listener.onClaimClicked(item);
}
}
}
});
vh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (!inSelectionMode) {
inSelectionMode = true;
if (selectionModeListener != null) {
selectionModeListener.onEnterSelectionMode();
}
}
toggleSelectedClaim(item);
return true;
}
});
vh.publisherView.setOnClickListener(new View.OnClickListener() {
@Override
@ -241,6 +283,27 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
}
}
private void toggleSelectedClaim(Claim claim) {
if (selectedItems.contains(claim)) {
selectedItems.remove(claim);
} else {
selectedItems.add(claim);
}
if (selectionModeListener != null) {
selectionModeListener.onItemSelectionToggled();
}
if (selectedItems.size() == 0) {
inSelectionMode = false;
if (selectionModeListener != null) {
selectionModeListener.onExitSelectionMode();
}
}
notifyDataSetChanged();
}
public interface ClaimListItemListener {
void onClaimClicked(Claim claim);
}

View file

@ -18,11 +18,12 @@ import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import io.lbry.browser.R;
import io.lbry.browser.adapter.TagListAdapter;
import io.lbry.browser.listener.TagListener;
import io.lbry.browser.model.Tag;
import io.lbry.browser.tasks.UpdateSuggestedTagsTask;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
import lombok.Setter;
@ -49,7 +50,7 @@ public class CustomizeTagsDialogFragment extends BottomSheetDialogFragment {
}
public void addTag(Tag tag) {
if (followedTagsAdapter.getTags().contains(tag)) {
Snackbar.make(getView(), getString(R.string.tag_already_followed, tag.getName()), Snackbar.LENGTH_LONG).show();
Snackbar.make(getView(), getString(R.string.tag_already_added, tag.getName()), Snackbar.LENGTH_LONG).show();
return;
}
@ -158,37 +159,9 @@ public class CustomizeTagsDialogFragment extends BottomSheetDialogFragment {
}
private void updateKnownTags(String filter, int limit, boolean clearPrevious) {
(new AsyncTask<Void, Void, List<Tag>>() {
protected List<Tag> doInBackground(Void... params) {
List<Tag> tags = new ArrayList<>();
if (Helper.isNullOrEmpty(filter)) {
Random random = new Random();
if (suggestedTagsAdapter != null && !clearPrevious) {
tags = new ArrayList<>(suggestedTagsAdapter.getTags());
}
while (tags.size() < limit) {
Tag randomTag = Lbry.knownTags.get(random.nextInt(Lbry.knownTags.size()));
if (!Lbry.followedTags.contains(randomTag) && (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(randomTag))) {
tags.add(randomTag);
}
}
} else {
Tag filterTag = new Tag(filter);
if (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(filterTag)) {
tags.add(new Tag(filter));
}
for (int i = 0; i < Lbry.knownTags.size() && tags.size() < SUGGESTED_LIMIT - 1; i++) {
Tag knownTag = Lbry.knownTags.get(i);
if ((knownTag.getLowercaseName().startsWith(filter) || knownTag.getLowercaseName().matches(filter)) &&
(!tags.contains(knownTag) &&
!Lbry.followedTags.contains(knownTag) && (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(knownTag)))) {
tags.add(knownTag);
}
}
}
return tags;
}
protected void onPostExecute(List<Tag> tags) {
UpdateSuggestedTagsTask task = new UpdateSuggestedTagsTask(filter, SUGGESTED_LIMIT, followedTagsAdapter, suggestedTagsAdapter, clearPrevious, new UpdateSuggestedTagsTask.KnownTagsHandler() {
@Override
public void onSuccess(List<Tag> tags) {
if (suggestedTagsAdapter == null) {
suggestedTagsAdapter = new TagListAdapter(tags, getContext());
suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD);
@ -201,11 +174,7 @@ public class CustomizeTagsDialogFragment extends BottomSheetDialogFragment {
}
checkNoResults();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface TagListener {
void onTagAdded(Tag tag);
void onTagRemoved(Tag tag);
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View file

@ -0,0 +1,7 @@
package io.lbry.browser.listener;
public interface SelectionModeListener {
void onEnterSelectionMode();
void onExitSelectionMode();
void onItemSelectionToggled();
}

View file

@ -0,0 +1,8 @@
package io.lbry.browser.listener;
import io.lbry.browser.model.Tag;
public interface TagListener {
void onTagAdded(Tag tag);
void onTagRemoved(Tag tag);
}

View file

@ -112,6 +112,14 @@ public class Claim {
return (value != null) ? value.getDescription() : null;
}
public String getWebsiteUrl() {
return (value instanceof ChannelMetadata) ? ((ChannelMetadata) value).getWebsiteUrl() : null;
}
public String getEmail() {
return (value instanceof ChannelMetadata) ? ((ChannelMetadata) value).getEmail() : null;
}
public String getPublisherName() {
if (signingChannel != null) {
return signingChannel.getName();

View file

@ -28,7 +28,7 @@ public class Subscription {
}
public boolean equals(Object o) {
return (o instanceof Subscription) && url.equalsIgnoreCase(((Subscription) o).getUrl());
return (o instanceof Subscription) && url != null && url.equalsIgnoreCase(((Subscription) o).getUrl());
}
public int hashCode() {
return url.toLowerCase().hashCode();

View file

@ -0,0 +1,10 @@
package io.lbry.browser.tasks;
import java.util.List;
import io.lbry.browser.model.Claim;
public interface ClaimListResultHandler {
void onSuccess(List<Claim> claims);
void onError(Exception error);
}

View file

@ -0,0 +1,67 @@
package io.lbry.browser.tasks;
import android.os.AsyncTask;
import android.view.View;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.lbry.browser.exceptions.ApiCallException;
import io.lbry.browser.model.Claim;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
public class ClaimListTask extends AsyncTask<Void, Void, List<Claim>> {
private String type;
private View progressView;
private ClaimListResultHandler handler;
private Exception error;
public ClaimListTask(String type, View progressView, ClaimListResultHandler handler) {
this.type = type;
this.progressView = progressView;
this.handler = handler;
}
protected void onPreExecute() {
Helper.setViewVisibility(progressView, View.VISIBLE);
}
protected List<Claim> doInBackground(Void... params) {
List<Claim> claims = null;
try {
Map<String, Object> options = new HashMap<>();
if (!Helper.isNullOrEmpty(type)) {
options.put("claim_type", type);
}
options.put("page", 1);
options.put("page_size", 999);
options.put("resolve", true);
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_CLAIM_LIST, options);
JSONArray items = result.getJSONArray("items");
claims = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
claims.add(Claim.fromJSONObject(items.getJSONObject(i)));
}
} catch (ApiCallException | JSONException ex) {
error = ex;
}
return claims;
}
protected void onPostExecute(List<Claim> claims) {
Helper.setViewVisibility(progressView, View.GONE);
if (handler != null) {
if (claims != null) {
handler.onSuccess(claims);
} else {
handler.onError(error);
}
}
}
}

View file

@ -14,15 +14,15 @@ import io.lbry.browser.utils.Lbry;
public class ResolveTask extends AsyncTask<Void, Void, List<Claim>> {
private List<String> urls;
private String connectionString;
private ResolveResultHandler handler;
private ClaimListResultHandler handler;
private View progressView;
private ApiCallException error;
public ResolveTask(String url, String connectionString, View progressView, ResolveResultHandler handler) {
public ResolveTask(String url, String connectionString, View progressView, ClaimListResultHandler handler) {
this(Arrays.asList(url), connectionString, progressView, handler);
}
public ResolveTask(List<String> urls, String connectionString, View progressView, ResolveResultHandler handler) {
public ResolveTask(List<String> urls, String connectionString, View progressView, ClaimListResultHandler handler) {
this.urls = urls;
this.connectionString = connectionString;
this.progressView = progressView;
@ -50,8 +50,4 @@ public class ResolveTask extends AsyncTask<Void, Void, List<Claim>> {
}
}
public interface ResolveResultHandler {
void onSuccess(List<Claim> claims);
void onError(Exception error);
}
}

View file

@ -0,0 +1,70 @@
package io.lbry.browser.tasks;
import android.os.AsyncTask;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import io.lbry.browser.adapter.TagListAdapter;
import io.lbry.browser.model.Tag;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
public class UpdateSuggestedTagsTask extends AsyncTask<Void, Void, List<Tag>> {
private boolean clearPrevious;
private int limit;
private String filter;
private TagListAdapter addedTagsAdapter;
private TagListAdapter suggestedTagsAdapter;
private KnownTagsHandler handler;
public UpdateSuggestedTagsTask(String filter, int limit, TagListAdapter addedTagsAdapter, TagListAdapter suggestedTagsAdapter, boolean clearPrevious, KnownTagsHandler handler) {
this.filter = filter;
this.limit = limit;
this.addedTagsAdapter = addedTagsAdapter;
this.suggestedTagsAdapter = suggestedTagsAdapter;
this.clearPrevious = clearPrevious;
this.handler = handler;
}
protected List<Tag> doInBackground(Void... params) {
List<Tag> tags = new ArrayList<>();
if (Helper.isNullOrEmpty(filter)) {
Random random = new Random();
if (suggestedTagsAdapter != null && !clearPrevious) {
tags = new ArrayList<>(suggestedTagsAdapter.getTags());
}
while (tags.size() < limit) {
Tag randomTag = Lbry.knownTags.get(random.nextInt(Lbry.knownTags.size()));
if (!Lbry.followedTags.contains(randomTag) && (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(randomTag))) {
tags.add(randomTag);
}
}
} else {
Tag filterTag = new Tag(filter);
if (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(filterTag)) {
tags.add(new Tag(filter));
}
for (int i = 0; i < Lbry.knownTags.size() && tags.size() < limit - 1; i++) {
Tag knownTag = Lbry.knownTags.get(i);
if ((knownTag.getLowercaseName().startsWith(filter) || knownTag.getLowercaseName().matches(filter)) &&
(!tags.contains(knownTag) &&
!Lbry.followedTags.contains(knownTag) && (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(knownTag)))) {
tags.add(knownTag);
}
}
}
return tags;
}
protected void onPostExecute(List<Tag> tags) {
if (handler != null) {
handler.onSuccess(tags);
}
}
public interface KnownTagsHandler {
void onSuccess(List<Tag> tags);
}
}

View file

@ -0,0 +1,101 @@
package io.lbry.browser.tasks;
import android.os.AsyncTask;
import android.view.View;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Random;
import java.io.File;
import io.lbry.browser.exceptions.LbryResponseException;
import io.lbry.browser.utils.Helper;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class UploadImageTask extends AsyncTask<Void, Void, String> {
private String filePath;
private View progressView;
private UploadThumbnailHandler handler;
private Exception error;
public UploadImageTask(String filePath, View progressView, UploadThumbnailHandler handler) {
this.filePath = filePath;
this.progressView = progressView;
this.handler = handler;
}
protected void onPreExecute() {
Helper.setViewVisibility(progressView, View.VISIBLE);
}
protected String doInBackground(Void... params) {
String thumbnailUrl = null;
try {
File file = new File(filePath);
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf('.');
String extension = "jpg";
if (dotIndex > -1) {
extension = fileName.substring(dotIndex + 1);
}
String fileType = String.format("image/%s", extension);
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM).
addFormDataPart("name", makeid()).
addFormDataPart("file", fileName, RequestBody.create(file, MediaType.parse(fileType))).
build();
Request request = new Request.Builder().url("https://spee.ch/api/claim/publish").post(body).build();
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
JSONObject json = new JSONObject(response.body().string());
if (json.has("success")) {
JSONObject data = json.getJSONObject("data");
String url = Helper.getJSONString("url", null, data);
if (Helper.isNullOrEmpty(url)) {
throw new LbryResponseException("Invalid thumbnail url returned after upload.");
}
thumbnailUrl = String.format("%s.%s", url, extension);
} else if (json.has("error")) {
JSONObject error = json.getJSONObject("error");
String message = Helper.getJSONString("message", null, error);
throw new LbryResponseException(Helper.isNullOrEmpty(message) ? "The image failed to upload." : message);
}
} catch (IOException | JSONException | LbryResponseException ex) {
error = ex;
}
return thumbnailUrl;
}
protected void onPostExecute(String thumbnailUrl) {
Helper.setViewVisibility(progressView, View.GONE);
if (handler != null) {
if (!Helper.isNullOrEmpty(thumbnailUrl)) {
handler.onSuccess(thumbnailUrl);
} else {
handler.onError(error);
}
}
}
private static String makeid() {
Random random = new Random();
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder id = new StringBuilder();
for (int i = 0; i < 24; i++) {
id.append(chars.charAt(random.nextInt(chars.length())));
}
return id.toString();
}
public interface UploadThumbnailHandler {
void onSuccess(String url);
void onError(Exception error);
}
}

View file

@ -0,0 +1,78 @@
package io.lbry.browser.tasks.content;
import android.os.AsyncTask;
import android.view.View;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import io.lbry.browser.exceptions.ApiCallException;
import io.lbry.browser.model.Claim;
import io.lbry.browser.tasks.GenericTaskHandler;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
public class ChannelCreateUpdateTask extends AsyncTask<Void, Void, Boolean> {
private Claim claim;
private BigDecimal deposit;
private boolean update;
private Exception error;
private GenericTaskHandler handler;
private View progressView;
public ChannelCreateUpdateTask(Claim claim, BigDecimal deposit, boolean update, View progressView, GenericTaskHandler handler) {
this.claim = claim;
this.deposit = deposit;
this.update = update;
this.progressView = progressView;
this.handler = handler;
}
protected void onPreExecute() {
Helper.setViewVisibility(progressView, View.VISIBLE);
if (handler != null) {
handler.beforeStart();
}
}
protected Boolean doInBackground(Void... params) {
Map<String, Object> options = new HashMap<>();
if (!update) {
options.put("name", claim.getName());
} else {
options.put("claim_id", claim.getClaimId());
}
options.put("bid", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT).format(deposit.doubleValue()));
options.put("title", claim.getTitle());
options.put("cover_url", claim.getCoverUrl());
options.put("thumbnail_url", claim.getThumbnailUrl());
options.put("description", claim.getDescription());
options.put("website_url", claim.getWebsiteUrl());
options.put("email", claim.getEmail());
options.put("tags", claim.getTags());
options.put("blocking", true);
String method = !update ? Lbry.METHOD_CHANNEL_CREATE : Lbry.METHOD_CHANNEL_UPDATE;
try {
Lbry.genericApiCall(method, options);
} catch (ApiCallException | ClassCastException ex) {
error = ex;
return false;
}
return true;
}
protected void onPostExecute(Boolean result) {
Helper.setViewVisibility(progressView, View.GONE);
if (handler != null) {
if (result) {
handler.onSuccess();
} else {
handler.onError(error);
}
}
}
}

View file

@ -0,0 +1,4 @@
package io.lbry.browser.tasks.content;
public class LogPublishTask {
}

View file

@ -30,12 +30,11 @@ import io.lbry.browser.dialog.ContentFromDialogFragment;
import io.lbry.browser.dialog.ContentScopeDialogFragment;
import io.lbry.browser.dialog.ContentSortDialogFragment;
import io.lbry.browser.dialog.CustomizeTagsDialogFragment;
import io.lbry.browser.listener.TagListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.Tag;
import io.lbry.browser.tasks.ClaimSearchTask;
import io.lbry.browser.tasks.FollowUnfollowTagTask;
import io.lbry.browser.tasks.GenericTaskHandler;
import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
@ -260,7 +259,7 @@ public class AllContentFragment extends BaseFragment implements SharedPreference
private void showCustomizeTagsDialog() {
CustomizeTagsDialogFragment dialog = CustomizeTagsDialogFragment.newInstance();
dialog.setListener(new CustomizeTagsDialogFragment.TagListener() {
dialog.setListener(new TagListener() {
@Override
public void onTagAdded(Tag tag) {
// heavy-lifting
@ -373,8 +372,12 @@ public class AllContentFragment extends BaseFragment implements SharedPreference
}
private Map<String, Object> buildContentOptions() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
}
return Lbry.buildClaimSearchOptions(
null,

View file

@ -199,8 +199,12 @@ public class ChannelContentFragment extends Fragment implements SharedPreference
}
private Map<String, Object> buildContentOptions() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
}
return Lbry.buildClaimSearchOptions(
null,

View file

@ -0,0 +1,597 @@
package io.lbry.browser.ui.channel;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.flexbox.FlexboxLayoutManager;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.adapter.TagListAdapter;
import io.lbry.browser.listener.WalletBalanceListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.Tag;
import io.lbry.browser.model.WalletBalance;
import io.lbry.browser.tasks.GenericTaskHandler;
import io.lbry.browser.tasks.UpdateSuggestedTagsTask;
import io.lbry.browser.tasks.UploadImageTask;
import io.lbry.browser.tasks.content.ChannelCreateUpdateTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
import io.lbry.browser.utils.LbryUri;
import lombok.Getter;
public class ChannelFormFragment extends BaseFragment implements WalletBalanceListener, TagListAdapter.TagClickListener {
private static final int SUGGESTED_LIMIT = 8;
@Getter
private boolean saveInProgress;
private boolean uploading;
private Claim currentClaim;
private boolean editFieldsLoaded;
private boolean editMode;
private View linkCancel;
private TextView linkShowOptional;
private MaterialButton buttonSave;
private TextView inlineBalanceValue;
private View uploadProgress;
private View containerOptionalFields;
private ImageView imageCover;
private ImageView imageThumbnail;
private TextInputEditText inputTitle;
private TextInputEditText inputChannelName;
private TextInputEditText inputDeposit;
private TextInputEditText inputDescription;
private TextInputEditText inputWebsite;
private TextInputEditText inputEmail;
private TextInputEditText inputTagFilter;
private RecyclerView addedTagsList;
private RecyclerView suggestedTagsList;
private TagListAdapter addedTagsAdapter;
private TagListAdapter suggestedTagsAdapter;
private View noTagsView;
private View noTagResultsView;
private View coverEditArea;
private View iconContainer;
private View channelSaveProgress;
private boolean launchCoverSelectPending;
private boolean launchThumbnailSelectPending;
private boolean coverFilePickerActive;
private boolean thumbnailFilePickerActive;
private String currentFilter;
private String coverUrl;
private String thumbnailUrl;
private String lastSelectedCoverFile;
private String lastSelectedThumbnailFile;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_channel_form, container, false);
linkCancel = root.findViewById(R.id.channel_form_cancel_link);
linkShowOptional = root.findViewById(R.id.channel_form_toggle_optional);
buttonSave = root.findViewById(R.id.channel_form_save_button);
containerOptionalFields = root.findViewById(R.id.channel_form_optional_fields_container);
inputTitle = root.findViewById(R.id.channel_form_input_title);
inputChannelName = root.findViewById(R.id.channel_form_input_channel_name);
inputDeposit = root.findViewById(R.id.channel_form_input_deposit);
inputDescription = root.findViewById(R.id.channel_form_input_description);
inputWebsite = root.findViewById(R.id.channel_form_input_website);
inputEmail = root.findViewById(R.id.channel_form_input_email);
inputTagFilter = root.findViewById(R.id.form_tag_filter_input);
coverEditArea = root.findViewById(R.id.channel_form_cover_edit_area);
iconContainer = root.findViewById(R.id.channel_form_icon_container);
imageCover = root.findViewById(R.id.channel_form_cover_image);
imageThumbnail = root.findViewById(R.id.channel_form_thumbnail);
inlineBalanceValue = root.findViewById(R.id.channel_form_inline_balance_value);
uploadProgress = root.findViewById(R.id.channel_form_upload_progress);
channelSaveProgress = root.findViewById(R.id.channel_form_save_progress);
Context context = getContext();
FlexboxLayoutManager flm1 = new FlexboxLayoutManager(context);
FlexboxLayoutManager flm2 = new FlexboxLayoutManager(context);
addedTagsList = root.findViewById(R.id.form_added_tags);
addedTagsList.setLayoutManager(flm1);
suggestedTagsList = root.findViewById(R.id.form_suggested_tags);
suggestedTagsList.setLayoutManager(flm2);
addedTagsAdapter = new TagListAdapter(new ArrayList<>(), context);
addedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_REMOVE);
addedTagsAdapter.setClickListener(this);
addedTagsList.setAdapter(addedTagsAdapter);
suggestedTagsAdapter = new TagListAdapter(new ArrayList<>(), getContext());
suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD);
suggestedTagsAdapter.setClickListener(this);
suggestedTagsList.setAdapter(suggestedTagsAdapter);
noTagsView = root.findViewById(R.id.form_no_added_tags);
noTagResultsView = root.findViewById(R.id.form_no_tag_results);
buttonSave = root.findViewById(R.id.channel_form_save_button);
linkShowOptional.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (containerOptionalFields.getVisibility() != View.VISIBLE) {
containerOptionalFields.setVisibility(View.VISIBLE);
linkShowOptional.setText(R.string.hide_optional_fields);
} else {
containerOptionalFields.setVisibility(View.GONE);
linkShowOptional.setText(R.string.show_optional_fields);
}
}
});
linkCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).onBackPressed();
}
}
});
coverEditArea.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (uploading) {
Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show();
return;
}
checkPermissionsAndLaunchFilePicker(true);
}
});
iconContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (uploading) {
Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show();
return;
}
checkPermissionsAndLaunchFilePicker(false);
}
});
buttonSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Claim claimToSave = buildChannelClaimToSave();
validateAndSaveClaim(claimToSave);
}
});
inputTagFilter.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
String value = Helper.getValue(charSequence);
setFilter(value);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
return root;
}
private void checkParams() {
Map<String, Object> params = getParams();
if (params.containsKey("claim")) {
Claim claim = (Claim) params.get("claim");
if (claim != null && !claim.equals(this.currentClaim)) {
this.currentClaim = claim;
editFieldsLoaded = false;
}
}
}
private void updateFieldsFromCurrentClaim() {
if (currentClaim != null && !editFieldsLoaded) {
inputTitle.setText(currentClaim.getTitle());
inputChannelName.setText(currentClaim.getName());
inputDeposit.setText(currentClaim.getAmount());
inputEmail.setText(currentClaim.getEmail());
inputWebsite.setText(currentClaim.getWebsiteUrl());
inputDescription.setText(currentClaim.getDescription());
if (currentClaim.getTagObjects() != null) {
addedTagsAdapter.addTags(currentClaim.getTagObjects());
}
Context context = getContext();
if (context != null) {
if (!Helper.isNullOrEmpty(currentClaim.getCoverUrl())) {
Glide.with(context.getApplicationContext()).load(currentClaim.getCoverUrl()).centerCrop().into(imageCover);
coverUrl = currentClaim.getCoverUrl();
}
if (!Helper.isNullOrEmpty(currentClaim.getThumbnailUrl())) {
Glide.with(context.getApplicationContext()).load(currentClaim.getThumbnailUrl()).apply(RequestOptions.circleCropTransform()).into(imageThumbnail);
thumbnailUrl = currentClaim.getThumbnailUrl();
}
}
inputChannelName.setEnabled(false);
editMode = true;
editFieldsLoaded = true;
}
}
private void validateAndSaveClaim(Claim claim) {
if (!editMode) {
String channelName = claim.getName().startsWith("@") ? claim.getName().substring(1) : claim.getName();
if (Helper.isNullOrEmpty(channelName)) {
showError(getString(R.string.please_enter_channel_name));
return;
}
if (!LbryUri.isNameValid(channelName)) {
showError(getString(R.string.channel_name_invalid_characters));
return;
}
if (Helper.channelExists(channelName)) {
showError(getString(R.string.channel_name_already_created));
return;
}
}
String depositString = Helper.getValue(inputDeposit.getText());
double depositAmount = 0;
try {
depositAmount = Double.valueOf(depositString);
} catch (NumberFormatException ex) {
// pass
showError(getString(R.string.please_enter_valid_deposit));
return;
}
if (depositAmount == 0) {
String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT));
showError(error);
return;
}
if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) {
showError(getString(R.string.deposit_more_than_balance));
return;
}
ChannelCreateUpdateTask task = new ChannelCreateUpdateTask(claim, new BigDecimal(depositString), editMode, channelSaveProgress, new GenericTaskHandler() {
@Override
public void beforeStart() {
preSave();
}
@Override
public void onSuccess() {
postSave();
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.showMessage(R.string.channel_save_successful);
activity.onBackPressed();
}
}
@Override
public void onError(Exception error) {
showError(error != null ? error.getMessage() : getString(R.string.channel_save_failed));
postSave();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void showError(String message) {
Context context = getContext();
if (context != null) {
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(
ContextCompat.getColor(context, R.color.red)).show();
}
}
public void checkPermissionsAndLaunchFilePicker(boolean isCover) {
Context context = getContext();
if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) {
launchCoverSelectPending = false;
launchThumbnailSelectPending = false;
coverFilePickerActive = isCover;
thumbnailFilePickerActive = !isCover;
launchFilePicker();
} else {
launchCoverSelectPending = isCover;
launchThumbnailSelectPending = !isCover;
MainActivity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
MainActivity.REQUEST_STORAGE_PERMISSION,
getString(R.string.storage_permission_rationale_images),
context,
true);
}
}
private void launchFilePicker() {
Context context = getContext();
if (context instanceof MainActivity) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("image/*");
((MainActivity) context).startActivityForResult(
Intent.createChooser(intent, getString(coverFilePickerActive ? R.string.select_cover : R.string.select_thumbnail)),
MainActivity.REQUEST_FILE_PICKER);
}
}
public void onFilePicked(String filePath) {
if (Helper.isNullOrEmpty(filePath)) {
Snackbar.make(getView(), R.string.undetermined_image_filepath, Snackbar.LENGTH_LONG).setBackgroundTint(
ContextCompat.getColor(getContext(), R.color.red)).show();
return;
}
Context context = getContext();
if (context != null) {
Uri fileUri = Uri.fromFile(new File(filePath));
if (coverFilePickerActive) {
// cover selected
if (filePath.equalsIgnoreCase(lastSelectedCoverFile)) {
// previous selected cover was uploaded successfully
return;
}
Glide.with(context.getApplicationContext()).load(fileUri).centerCrop().into(imageCover);
} else if (thumbnailFilePickerActive) {
if (filePath.equalsIgnoreCase(lastSelectedThumbnailFile)) {
// previous selected thumbnail was uploaded successfully
return;
}
// thumbnail selected
Glide.with(context.getApplicationContext()).load(fileUri).apply(RequestOptions.circleCropTransform()).into(imageThumbnail);
}
// Upload the image
uploading = true;
UploadImageTask task = new UploadImageTask(filePath, uploadProgress, new UploadImageTask.UploadThumbnailHandler() {
@Override
public void onSuccess(String url) {
if (coverFilePickerActive) {
coverUrl = url;
lastSelectedCoverFile = filePath;
} else if (thumbnailFilePickerActive) {
thumbnailUrl = url;
lastSelectedThumbnailFile = filePath;
}
coverFilePickerActive = false;
thumbnailFilePickerActive = false;
uploading = false;
}
@Override
public void onError(Exception error) {
Snackbar.make(getView(), R.string.image_upload_failed, Snackbar.LENGTH_LONG).setBackgroundTint(
ContextCompat.getColor(context, R.color.red)
).show();
coverFilePickerActive = false;
thumbnailFilePickerActive = false;
uploading = false;
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
coverFilePickerActive = false;
thumbnailFilePickerActive = false;
}
}
public void onStoragePermissionGranted() {
if (launchCoverSelectPending) {
checkPermissionsAndLaunchFilePicker(true);
} else if (launchThumbnailSelectPending) {
checkPermissionsAndLaunchFilePicker(false);
}
}
public void onStoragePermissionRefused() {
Snackbar.make(getView(), R.string.storage_permission_rationale_images, Snackbar.LENGTH_LONG).setBackgroundTint(
ContextCompat.getColor(getContext(), R.color.red)
).show();
}
@Override
public void onStart() {
super.onStart();
MainActivity activity = (MainActivity) getContext();
if (activity != null) {
activity.hideSearchBar();
activity.showNavigationBackIcon();
activity.lockDrawer();
activity.hideFloatingWalletBalance();
activity.addWalletBalanceListener(this);
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(editMode ? R.string.edit_channel : R.string.create_a_channel);
}
}
}
@Override
public void onStop() {
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) getContext();
activity.removeWalletBalanceListener(this);
activity.restoreToggle();
activity.showFloatingWalletBalance();
}
super.onStop();
}
public void onResume() {
super.onResume();
checkParams();
updateFieldsFromCurrentClaim();
String filterText = Helper.getValue(inputTagFilter.getText());
updateSuggestedTags(filterText, SUGGESTED_LIMIT, true);
}
private void checkNoAddedTags() {
Helper.setViewVisibility(noTagsView, addedTagsAdapter == null || addedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
private void checkNoTagResults() {
Helper.setViewVisibility(noTagResultsView, suggestedTagsAdapter == null || suggestedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
public void addTag(Tag tag) {
if (addedTagsAdapter.getTags().contains(tag)) {
Snackbar.make(getView(), getString(R.string.tag_already_added, tag.getName()), Snackbar.LENGTH_LONG).show();
return;
}
tag.setFollowed(true);
addedTagsAdapter.addTag(tag);
if (suggestedTagsAdapter != null) {
suggestedTagsAdapter.removeTag(tag);
}
updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false);
checkNoAddedTags();
checkNoTagResults();
}
public void removeTag(Tag tag) {
tag.setFollowed(false);
addedTagsAdapter.removeTag(tag);
updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false);
checkNoAddedTags();
checkNoTagResults();
}
@Override
public void onWalletBalanceUpdated(WalletBalance walletBalance) {
if (walletBalance != null && inlineBalanceValue != null) {
inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue()));
}
}
public void setFilter(String filter) {
currentFilter = filter;
updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, true);
}
private void updateSuggestedTags(String filter, int limit, boolean clearPrevious) {
UpdateSuggestedTagsTask task = new UpdateSuggestedTagsTask(filter, limit, addedTagsAdapter, suggestedTagsAdapter, clearPrevious, new UpdateSuggestedTagsTask.KnownTagsHandler() {
@Override
public void onSuccess(List<Tag> tags) {
if (suggestedTagsAdapter == null) {
suggestedTagsAdapter = new TagListAdapter(tags, getContext());
suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD);
suggestedTagsAdapter.setClickListener(ChannelFormFragment.this);
if (suggestedTagsList != null) {
suggestedTagsList.setAdapter(suggestedTagsAdapter);
}
} else {
suggestedTagsAdapter.setTags(tags);
}
checkNoAddedTags();
checkNoTagResults();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private Claim buildChannelClaimToSave() {
Claim claim = new Claim();
if (!editMode) {
String name = Helper.getValue(inputChannelName.getText());
if (!name.startsWith("@")) {
name = String.format("@%s", name);
}
claim.setName(name);
} else if (currentClaim != null) {
claim.setClaimId(currentClaim.getClaimId());
}
Claim.ChannelMetadata metadata = new Claim.ChannelMetadata();
metadata.setTitle(Helper.getValue(inputTitle.getText()));
metadata.setDescription(Helper.getValue(inputDescription.getText()));
metadata.setWebsiteUrl(Helper.getValue(inputWebsite.getText()));
metadata.setEmail(Helper.getValue(inputEmail.getText()));
Claim.Resource cover = new Claim.Resource();
cover.setUrl(coverUrl == null ? "" : coverUrl);
Claim.Resource thumbnail = new Claim.Resource();
thumbnail.setUrl(thumbnailUrl == null ? "" : thumbnailUrl);
metadata.setThumbnail(thumbnail);
metadata.setCover(cover);
List<Tag> addedTags = addedTagsAdapter != null ? new ArrayList<>(addedTagsAdapter.getTags()) : new ArrayList<>();
metadata.setTags(Helper.getTagsForTagObjects(addedTags));
claim.setValue(metadata);
return claim;
}
private void preSave() {
saveInProgress = true;
Helper.setViewVisibility(linkShowOptional, View.GONE);
Helper.setViewEnabled(linkCancel, false);
Helper.setViewEnabled(buttonSave, false);
}
private void postSave() {
Helper.setViewVisibility(linkShowOptional, View.VISIBLE);
Helper.setViewEnabled(linkCancel, true);
Helper.setViewEnabled(buttonSave, true);
saveInProgress = false;
}
@Override
public void onTagClicked(Tag tag, int customizeMode) {
if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) {
addTag(tag);
} else if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_REMOVE) {
removeTag(tag);
}
}
}

View file

@ -14,8 +14,6 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
@ -37,6 +35,7 @@ import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.ChannelSubscribeTask;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.ResolveTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.ui.controls.SolidIconView;
@ -196,7 +195,10 @@ public class ChannelFragment extends BaseFragment {
boolean isFollowing = Lbryio.isFollowing(claim);
if (iconFollowUnfollow != null) {
iconFollowUnfollow.setText(isFollowing ? R.string.fa_heart_broken : R.string.fa_heart);
iconFollowUnfollow.setTextColor(ContextCompat.getColor(getContext(), isFollowing ? R.color.foreground : R.color.red));
Context context = getContext();
if (context != null) {
iconFollowUnfollow.setTextColor(ContextCompat.getColor(context, isFollowing ? R.color.foreground : R.color.red));
}
}
}
}
@ -244,7 +246,7 @@ public class ChannelFragment extends BaseFragment {
private void resolveUrl() {
layoutDisplayArea.setVisibility(View.INVISIBLE);
ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, layoutResolving, new ResolveTask.ResolveResultHandler() {
ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, layoutResolving, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (claims.size() > 0) {
@ -300,7 +302,9 @@ public class ChannelFragment extends BaseFragment {
int bgColor = Helper.generateRandomColorForValue(claim.getClaimId());
Helper.setIconViewBackgroundColor(noThumbnailView, bgColor, false, getContext());
noThumbnailView.setVisibility(View.VISIBLE);
textAlpha.setText(claim.getName().substring(1, 2));
if (claim.getName() != null) {
textAlpha.setText(claim.getName().substring(1, 2));
}
}
try {
@ -332,16 +336,20 @@ public class ChannelFragment extends BaseFragment {
switch (position) {
case 0:
ChannelContentFragment contentFragment = ChannelContentFragment.class.newInstance();
contentFragment.setChannelId(channelClaim.getClaimId());
if (channelClaim != null) {
contentFragment.setChannelId(channelClaim.getClaimId());
}
return contentFragment;
case 1:
ChannelAboutFragment aboutFragment = ChannelAboutFragment.class.newInstance();
try {
Claim.ChannelMetadata metadata = (Claim.ChannelMetadata) channelClaim.getValue();
aboutFragment.setDescription(metadata.getDescription());
aboutFragment.setEmail(metadata.getEmail());
aboutFragment.setWebsite(metadata.getWebsiteUrl());
if (metadata != null) {
aboutFragment.setDescription(metadata.getDescription());
aboutFragment.setEmail(metadata.getEmail());
aboutFragment.setWebsite(metadata.getWebsiteUrl());
}
} catch (ClassCastException ex) {
// pass
}

View file

@ -0,0 +1,273 @@
package io.lbry.browser.ui.channel;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.NavMenuItem;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.ClaimListTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
public class ChannelManagerFragment extends BaseFragment implements ActionMode.Callback, SelectionModeListener, SdkStatusListener {
private Button buttonNewChannel;
private FloatingActionButton fabNewChannel;
private ActionMode actionMode;
private View emptyView;
private View layoutSdkInitializing;
private ProgressBar loading;
private ProgressBar bigLoading;
private RecyclerView channelList;
private ClaimListAdapter adapter;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_channel_manager, container, false);
buttonNewChannel = root.findViewById(R.id.channel_manager_create_button);
fabNewChannel = root.findViewById(R.id.channel_manager_fab_new_channel);
buttonNewChannel.setOnClickListener(newChannelClickListener);
fabNewChannel.setOnClickListener(newChannelClickListener);
emptyView = root.findViewById(R.id.channel_manager_empty_container);
layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing);
channelList = root.findViewById(R.id.channel_manager_list);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
channelList.setLayoutManager(llm);
loading = root.findViewById(R.id.channel_manager_list_loading);
bigLoading = root.findViewById(R.id.channel_manager_list_big_loading);
layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE);
return root;
}
private View.OnClickListener newChannelClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).openChannelForm(null);
}
}
};
@Override
public void onStart() {
super.onStart();
Context context = getContext();
if (context != null) {
MainActivity activity = (MainActivity) context;
activity.hideFloatingWalletBalance();
}
}
@Override
public void onStop() {
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.showFloatingWalletBalance();
}
super.onStop();
}
public void onResume() {
super.onResume();
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).setWunderbarValue(null);
}
if (!Lbry.SDK_READY) {
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.addSdkStatusListener(this);
}
} else {
onSdkReady();
}
}
public void onSdkReady() {
Helper.setViewVisibility(layoutSdkInitializing, View.GONE);
Helper.setViewVisibility(fabNewChannel, View.VISIBLE);
if (adapter != null && channelList != null) {
channelList.setAdapter(adapter);
}
fetchChannels();
}
public View getLoading() {
return (adapter == null || adapter.getItemCount() == 0) ? bigLoading : loading;
}
private void checkNoChannels() {
Helper.setViewVisibility(emptyView, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
private void fetchChannels() {
Helper.setViewVisibility(emptyView, View.GONE);
ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, getLoading(), new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
Lbry.ownChannels = new ArrayList<>(claims);
Context context = getContext();
if (adapter == null) {
adapter = new ClaimListAdapter(claims, context);
adapter.setSelectionModeListener(ChannelManagerFragment.this);
adapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
@Override
public void onClaimClicked(Claim claim) {
if (context instanceof MainActivity) {
((MainActivity) context).openChannelClaim(claim);
}
}
});
if (channelList != null) {
channelList.setAdapter(adapter);
}
} else {
adapter.setItems(claims);
}
checkNoChannels();
}
@Override
public void onError(Exception error) {
checkNoChannels();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public void onEnterSelectionMode() {
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.startSupportActionMode(this);
}
}
public void onItemSelectionToggled() {
if (actionMode != null) {
actionMode.setTitle(String.valueOf(adapter.getSelectedCount()));
actionMode.invalidate();
}
}
public void onExitSelectionMode() {
if (actionMode != null) {
actionMode.finish();
}
}
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
this.actionMode = actionMode;
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
if (!activity.isDarkMode()) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
actionMode.getMenuInflater().inflate(R.menu.menu_claim_list, menu);
return true;
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
if (adapter != null) {
adapter.clearSelectedItems();
adapter.setInSelectionMode(false);
adapter.notifyDataSetChanged();
}
Context context = getContext();
if (context != null) {
MainActivity activity = (MainActivity) context;
if (!activity.isDarkMode()) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
this.actionMode = null;
}
@Override
public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode actionMode, Menu menu) {
int selectionCount = adapter != null ? adapter.getSelectedCount() : 0;
menu.findItem(R.id.action_edit).setVisible(selectionCount == 1);
return true;
}
@Override
public boolean onActionItemClicked(androidx.appcompat.view.ActionMode actionMode, MenuItem menuItem) {
if (R.id.action_edit == menuItem.getItemId()) {
if (adapter != null && adapter.getSelectedCount() > 0) {
Claim claim = adapter.getSelectedItems().get(0);
// start channel editor with the claim
Context context = getContext();
if (context instanceof MainActivity) {
Map<String, Object> params = new HashMap<>();
params.put("claim", claim);
((MainActivity) context).openFragment(ChannelFormFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS, params);
}
actionMode.finish();
return true;
}
}
if (R.id.action_delete == menuItem.getItemId()) {
if (adapter != null && adapter.getSelectedCount() > 0) {
final List<Claim> selectedClaims = new ArrayList<>(adapter.getSelectedItems());
String message = getResources().getQuantityString(R.plurals.confirm_delete_channels, selectedClaims.size());
AlertDialog.Builder builder = new AlertDialog.Builder(getContext()).
setTitle(R.string.delete_selection).
setMessage(message)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//handleDeleteSelectedClaims(selectedClaims);
}
}).setNegativeButton(R.string.no, null);
builder.show();
return true;
}
}
return false;
}
}

View file

@ -65,8 +65,13 @@ public class EditorsChoiceFragment extends BaseFragment {
}
private Map<String, Object> buildContentOptions() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
}
return Lbry.buildClaimSearchOptions(
Claim.TYPE_REPOST,
null,

View file

@ -38,6 +38,7 @@ import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.ChannelSubscribeTask;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.ClaimSearchTask;
import io.lbry.browser.tasks.FetchSubscriptionsTask;
import io.lbry.browser.tasks.ResolveTask;
@ -266,7 +267,6 @@ public class FollowingFragment extends BaseFragment implements
}
});
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
@ -389,8 +389,12 @@ public class FollowingFragment extends BaseFragment implements
}
private Map<String, Object> buildContentOptions() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
}
return Lbry.buildClaimSearchOptions(
Claim.TYPE_STREAM,
@ -467,7 +471,7 @@ public class FollowingFragment extends BaseFragment implements
private void fetchAndResolveChannelList() {
buildChannelIdsAndUrls();
if (channelIds.size() > 0) {
ResolveTask resolveSubscribedTask = new ResolveTask(channelUrls, Lbry.LBRY_TV_CONNECTION_STRING, channelListLoading, new ResolveTask.ResolveResultHandler() {
ResolveTask resolveSubscribedTask = new ResolveTask(channelUrls, Lbry.LBRY_TV_CONNECTION_STRING, channelListLoading, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
updateChannelFilterListAdapter(claims, true);

View file

@ -21,6 +21,7 @@ import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.ClaimCacheKey;
import io.lbry.browser.tasks.ClaimListResultHandler;
import io.lbry.browser.tasks.ClaimSearchTask;
import io.lbry.browser.tasks.LighthouseSearchTask;
import io.lbry.browser.tasks.ResolveTask;
@ -138,7 +139,7 @@ public class SearchFragment extends BaseFragment implements
return;
}
ResolveTask task = new ResolveTask(vanityUrl, Lbry.LBRY_TV_CONNECTION_STRING, null, new ResolveTask.ResolveResultHandler() {
ResolveTask task = new ResolveTask(vanityUrl, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (claims.size() > 0) {

View file

@ -122,6 +122,7 @@ public class TransactionHistoryFragment extends BaseFragment implements Transact
MainActivity activity = (MainActivity) getContext();
if (activity != null) {
activity.hideSearchBar();
activity.hideFloatingWalletBalance();
activity.showNavigationBackIcon();
activity.lockDrawer();
@ -136,7 +137,9 @@ public class TransactionHistoryFragment extends BaseFragment implements Transact
public void onStop() {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).restoreToggle();
MainActivity activity = (MainActivity) context;
activity.restoreToggle();
activity.showFloatingWalletBalance();
}
super.onStop();
}

View file

@ -51,8 +51,6 @@ import io.lbry.browser.utils.Lbryio;
public class WalletFragment extends BaseFragment implements SdkStatusListener, WalletBalanceListener {
private boolean sdkReady;
private View layoutAccountRecommended;
private View layoutSdkInitializing;
private View linkSkipAccount;
@ -96,7 +94,7 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
loadingRecentContainer = root.findViewById(R.id.wallet_loading_recent_container);
layoutAccountRecommended = root.findViewById(R.id.wallet_account_recommended_container);
layoutSdkInitializing = root.findViewById(R.id.wallet_sdk_initializing_container);
layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing);
linkSkipAccount = root.findViewById(R.id.wallet_skip_account_link);
buttonSignUp = root.findViewById(R.id.wallet_sign_up_button);
@ -441,7 +439,6 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
}
public void onSdkReady() {
sdkReady = true;
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).removeSdkStatusListener(this);

View file

@ -1,6 +1,8 @@
package io.lbry.browser.utils;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@ -9,7 +11,11 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.view.View;
import android.widget.TextView;
@ -20,6 +26,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
@ -46,6 +53,7 @@ public final class Helper {
public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded");
public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
public static final int CONTENT_PAGE_SIZE = 25;
public static final double MIN_DEPOSIT = 0.05;
public static boolean isNull(String value) {
return value == null;
@ -340,4 +348,192 @@ public final class Helper {
}
return String.format("%s %s", Build.MANUFACTURER, Build.MODEL);
}
public static boolean channelExists(String channelName) {
for (Claim claim : Lbry.ownChannels) {
if (channelName.equalsIgnoreCase(claim.getName())) {
return true;
}
}
return false;
}
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
return getRealPathFromURI_API19(context, uri, false);
}
/**
* https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8
*/
@SuppressLint("NewApi")
public static String getRealPathFromURI_API19(final Context context, final Uri uri, boolean folderPath) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
// This is for checking Main Memory
if ("primary".equalsIgnoreCase(type)) {
if (split.length > 1) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
return Environment.getExternalStorageDirectory() + "/";
}
// This is for checking SD Card
} else {
return "storage" + "/" + docId.replace(":", "/");
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
String fileName = getFilePath(context, uri);
if (fileName != null) {
String extStorageDirectory = Environment.getExternalStorageDirectory().toString();
return folderPath ?
String.format("%s/Download", extStorageDirectory) :
String.format("%s/Download/%s", extStorageDirectory, fileName);
}
String id = DocumentsContract.getDocumentId(uri);
if (id.startsWith("raw:")) {
id = id.replaceFirst("raw:", "");
File file = new File(id);
if (file.exists())
return id;
}
String[] contentUriPrefixesToTry = new String[]{
"content://downloads/public_downloads",
"content://downloads/my_downloads",
"content://downloads/all_downloads"
};
for (String contentUriPrefix : contentUriPrefixesToTry) {
Uri contentUri = Helper.parseInt(id, -1) > 0 ?
ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)) :
Uri.parse(contentUriPrefix);
try {
String path = getDataColumn(context, contentUri, null, null);
if (path != null) {
return path;
}
} catch (Exception ex) {
// pass
}
}
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getFilePath(Context context, Uri uri) {
Cursor cursor = null;
final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME };
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

View file

@ -47,6 +47,8 @@ public final class Lbry {
public static WalletBalance walletBalance = new WalletBalance();
public static List<Tag> knownTags = new ArrayList<>();
public static List<Tag> followedTags = new ArrayList<>();
public static List<Claim> ownClaims = new ArrayList<>();
public static List<Claim> ownChannels = new ArrayList<>(); // Make this a subset of ownClaims?
public static final int TTL_CLAIM_SEARCH_VALUE = 120000; // 2-minute TTL for cache
public static final String SDK_CONNECTION_STRING = "http://127.0.0.1:5279";
@ -87,6 +89,12 @@ public final class Lbry {
public static final String METHOD_PREFERENCE_GET = "preference_get";
public static final String METHOD_PREFERENCE_SET = "preference_set";
public static final String METHOD_CHANNEL_ABANDON = "channel_abandon";
public static final String METHOD_CHANNEL_CREATE = "channel_create";
public static final String METHOD_CHANNEL_UPDATE = "channel_update";
public static final String METHOD_CLAIM_LIST = "claim_list";
public static KeyStore KEYSTORE;
public static boolean SDK_READY = false;

View file

@ -43,6 +43,10 @@ public class LbryUri {
return (!Helper.isNullOrEmpty(channelName) && Helper.isNullOrEmpty(streamName)) || (!Helper.isNullOrEmpty(claimName) && claimName.startsWith("@"));
}
public static boolean isNameValid(String name) {
return !name.matches(REGEX_INVALID_URI);
}
public static LbryUri tryParse(String url) {
try {
return parse(url, false);

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/overlay" />
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<color android:color="@color/nextLbryGreenSemiTransparent" />
</item>
<item>
<color android:color="@android:color/transparent" />
</item>
</selector>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@android:color/holo_blue_bright"/>
<solid android:color="@color/overlay" />
</shape>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container_sdk_initializing"
android:background="@color/pageBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="36dp">
<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/sdk_still_initializing"
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>
</RelativeLayout>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/form_added_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/form_no_added_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:fontFamily="@font/inter"
android:text="@string/no_added_tags"
android:textSize="14sp"
android:textFontWeight="300"
android:visibility="gone" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/divider"
android:layout_marginTop="10dp"
android:layout_marginBottom="16dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/form_suggested_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/form_no_tag_results"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:fontFamily="@font/inter"
android:text="@string/form_no_tag_results"
android:textSize="14sp"
android:textFontWeight="300"
android:visibility="gone" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/form_tag_filter_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_for_more_tags"
android:fontFamily="@font/inter"
android:textSize="14sp"
android:textFontWeight="300" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View file

@ -0,0 +1,333 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pageBackground">
<LinearLayout
android:id="@+id/channel_form_display_area"
android:clipChildren="false"
android:elevation="4dp"
android:orientation="vertical"
android:layout_weight="10"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/channel_form_cover_edit_area"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="220dp">
<ImageView
android:id="@+id/channel_form_cover_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/default_channel_cover"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/channel_form_cover_edit_icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:layout_alignParentBottom="true"
android:src="@drawable/ic_edit"
android:tint="@color/white" />
<LinearLayout
android:id="@+id/channel_form_upload_progress"
android:background="@color/channelCoverBackground"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:visibility="gone">
<ProgressBar
android:layout_width="16dp"
android:layout_height="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:fontFamily="@font/inter"
android:text="@string/uploading"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="12sp" />
</LinearLayout>
<RelativeLayout
android:id="@+id/channel_form_icon_container"
android:foreground=" ?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="16dp"
android:layout_centerHorizontal="true">
<RelativeLayout
android:layout_centerHorizontal="true"
android:background="@drawable/bg_channel_icon"
android:id="@+id/channel_form_no_thumbnail"
android:layout_width="90dp"
android:layout_height="90dp">
<TextView
android:id="@+id/channel_form_icon_alpha"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textAllCaps="true"
android:textSize="48sp"
android:textColor="@color/white"
android:textFontWeight="300" />
</RelativeLayout>
<ImageView
android:id="@+id/channel_form_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginBottom="16dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_edit"
android:tint="@color/white" />
</RelativeLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_channel_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:hint="@string/channel_name"
android:singleLine="true"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/channel_form_at_prefix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:layout_marginLeft="12dp"
android:text="@string/at"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/channel_form_input_layout_deposit"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:hint="@string/deposit">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_deposit"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textSize="14sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/channel_form_input_currency"
android:layout_toRightOf="@id/channel_form_input_layout_deposit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="30dp"
android:fontFamily="@font/inter"
android:text="@string/lbc"
android:textAllCaps="true"
android:textSize="10sp"
android:textFontWeight="300" />
<LinearLayout
android:id="@+id/channel_form_inline_balance_container"
android:layout_toRightOf="@id/channel_form_input_currency"
android:layout_marginLeft="24dp"
android:layout_marginTop="28dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="invisible">
<io.lbry.browser.ui.controls.SolidIconView
android:layout_width="18dp"
android:layout_height="18dp"
android:text="@string/fa_coins" />
<TextView
android:id="@+id/channel_form_inline_balance_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textFontWeight="300"
android:layout_marginLeft="2dp" />
</LinearLayout>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/inter"
android:text="@string/deposit_remains_yours"
android:textFontWeight="300"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/channel_form_optional_fields_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical"
android:visibility="gone">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/description">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:inputType="textMultiLine"
android:singleLine="false"
android:scrollbars="vertical"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/website">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_website"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/email">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/channel_form_input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:fontFamily="@font/inter"
android:text="@string/tags"
android:textFontWeight="600"
android:textSize="14sp" />
<include layout="@layout/form_tag_search" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<TextView
android:id="@+id/channel_form_cancel_link"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:textSize="12sp"
android:text="@string/cancel" />
<TextView
android:id="@+id/channel_form_toggle_optional"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="24dp"
android:layout_toLeftOf="@id/channel_form_save_button"
android:fontFamily="@font/inter"
android:textSize="12sp"
android:text="@string/show_optional_fields" />
<ProgressBar
android:id="@+id/channel_form_save_progress"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_marginRight="24dp"
android:layout_toLeftOf="@+id/channel_form_save_button"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton
android:id="@+id/channel_form_save_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/save" />
</RelativeLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/channel_manager_list_big_loading"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_centerInParent="true"
android:visibility="gone" />
<ProgressBar
android:id="@+id/channel_manager_list_loading"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/channel_manager_list"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/channel_manager_empty_container"
android:background="@color/pageBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="36dp">
<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_channel_created"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_gravity="center_horizontal"
android:fontFamily="@font/inter"
android:textAlignment="center"
android:textSize="16sp"
android:textFontWeight="300"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/channel_manager_create_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:fontFamily="@font/inter"
android:text="@string/create_a_channel" />
</LinearLayout>
</RelativeLayout>
<include layout="@layout/container_sdk_initializing" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/channel_manager_fab_new_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_add"
android:visibility="gone" />
</RelativeLayout>

View file

@ -30,35 +30,7 @@
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<RelativeLayout
android:id="@+id/wallet_sdk_initializing_container"
android:background="@color/pageBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="36dp">
<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/sdk_still_initializing"
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>
</RelativeLayout>
<include layout="@layout/container_sdk_initializing" />
<RelativeLayout
android:id="@+id/wallet_account_recommended_container"

View file

@ -9,7 +9,8 @@
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:background="?attr/selectableItemBackground">
android:foreground="?attr/selectableItemBackground"
android:background="@drawable/bg_selected_list_item">
<LinearLayout
android:id="@+id/claim_repost_info"
android:layout_width="match_parent"
@ -106,6 +107,20 @@
android:textSize="14sp"
android:textFontWeight="300" />
</LinearLayout>
<RelativeLayout
android:layout_centerHorizontal="true"
android:background="@drawable/bg_channel_overlay_icon"
android:id="@+id/claim_selected_overlay"
android:layout_width="90dp"
android:layout_height="90dp"
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

View file

@ -117,6 +117,20 @@
android:textSize="14sp"
android:textFontWeight="300" />
</LinearLayout>
<RelativeLayout
android:layout_centerHorizontal="true"
android:background="@drawable/bg_stream_overlay_icon"
android:id="@+id/claim_selected_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
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

View file

@ -121,6 +121,20 @@
android:textSize="14sp"
android:textFontWeight="300" />
</LinearLayout>
<RelativeLayout
android:layout_centerHorizontal="true"
android:background="@drawable/bg_stream_overlay_icon"
android:id="@+id/claim_selected_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
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

View file

@ -0,0 +1,16 @@
<?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_edit"
android:icon="@drawable/ic_edit"
android:title="@string/edit"
app:iconTint="@color/actionBarForeground"
app:showAsAction="always" />
<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

@ -25,6 +25,7 @@
<color name="brighterLbryGreen">#40B887</color>
<color name="lbryGreen">#2F9176</color>
<color name="nextLbryGreen">#38D9A9</color>
<color name="nextLbryGreenSemiTransparent">#3338D9A9</color>
<color name="tagGreen">#329A7E</color>
<color name="tagGrape">#77D510B8</color>
@ -49,4 +50,5 @@
<color name="thumbnailPlaceholder">#D5D5D5</color>
<color name="caption">#CAEDB9</color>
<color name="overlay">#CC333333</color>
</resources>

View file

@ -24,6 +24,7 @@
<color name="brighterLbryGreen">#40B887</color>
<color name="lbryGreen">#2F9176</color>
<color name="nextLbryGreen">#38D9A9</color>
<color name="nextLbryGreenSemiTransparent">#3338D9A9</color>
<color name="tagGreen">#E3F6F1</color>
<color name="tagGrape">#77F255DA</color>
@ -48,4 +49,5 @@
<color name="thumbnailPlaceholder">#D5D5D5</color>
<color name="caption">#CAEDB9</color>
<color name="overlay">#CC333333</color>
</resources>

View file

@ -194,7 +194,7 @@
<string name="search_for_more_tags">Search for more tags</string>
<string name="no_followed_tags">You have not followed any tags yet. Get started by adding tags that you are interested in!</string>
<string name="no_tag_results">We could not find new tags that you\'re not following.</string>
<string name="tag_already_followed">The \'%1$s\' tag has already been added.</string>
<string name="tag_already_added">The \'%1$s\' tag has already been added.</string>
<string name="send_a_tip">Send a tip</string>
<string name="send_a_tip_to">Send a tip to %1$s</string>
<string name="send_tip_info_content">This will appear as a tip for %1$s, which will boost its ability to be discovered while active. &lt;a href="https://lbry.com/faq/tipping"&gt;Learn more&lt;/a&gt;.</string>
@ -225,6 +225,52 @@
<string name="enable_sync">Enable sync</string>
<string name="wallet_sync_op_failed">The wallet sync operation could not be completed at this time. Please try again later. If this problem persists, please send an email to hello@lbry.com.</string>
<!-- Forms -->
<string name="no_added_tags">You have not added any tags yet. Add tags to improve discovery.</string>
<string name="form_no_tag_results">We could not find new tags that have not been added yet.</string>
<!-- Channels -->
<string name="no_channel_created">You have not created a channel.\nStart now by creating a new channel!</string>
<string name="create_a_channel">Create a channel</string>
<string name="edit_channel">Edit channel</string>
<string name="delete_selection">Delete selection?</string>
<string name="description">Description</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="show_extra">Show extra</string>
<string name="hide_extra">Hide extra</string>
<string name="show_optional_fields">Show optional fields</string>
<string name="hide_optional_fields">Hide optional fields</string>
<string name="save">Save</string>
<string name="channel_name">Channel name</string>
<string name="title">Title</string>
<string name="at">\@</string>
<string name="deposit">Deposit</string>
<string name="deposit_remains_yours">This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.</string>
<string name="storage_permission_rationale_images">LBRY requires access to load images from your device storage.</string>
<string name="select_thumbnail">Select thumbnail</string>
<string name="select_cover">Select cover image</string>
<string name="undetermined_image_filepath">The file path could not be determined for the selected image. Please select an image in a different location.</string>
<string name="wait_for_upload">Please wait for the current upload to finish.</string>
<string name="image_upload_failed">The image upload request failed. Please try again.</string>
<string name="uploading">Uploading...</string>
<string name="please_enter_channel_name">Please enter a channel name.</string>
<string name="channel_name_invalid_characters">Your channel name contains invalid characters.</string>
<string name="channel_name_already_created">You have already created a channel with the same name.</string>
<string name="please_enter_valid_deposit">Please enter a valid deposit amount.</string>
<string name="deposit_more_than_balance">Deposit cannot be higher than your balance.</string>
<string name="channel_save_failed">The channel save request failed. Please try again.</string>
<string name="channel_save_successful">The channel was successfully saved.</string>
<string name="channel_pending_blockchain">The channel is pending publish on the blockchain. You will be able to access or edit the channel in a few moments.</string>
<plurals name="min_deposit_required">
<item quantity="one">A minimum deposit of %1$s credit is required.</item>
<item quantity="other">A minimum deposit of %1$s credits is required.</item>
</plurals>
<plurals name="confirm_delete_channels">
<item quantity="one">Are you sure you want to delete the selected channel?</item>
<item quantity="other">Are you sure you want to delete the selected channels?</item>
</plurals>
<!-- Font Awesome -->
<string name="fa_gift">&#xf06b;</string>
<string name="fa_lock">&#xf023;</string>