Basic native mobile publishing
This commit is contained in:
parent
818fc80549
commit
4d2656f676
20 changed files with 1230 additions and 99 deletions
|
@ -85,7 +85,7 @@ dependencies {
|
|||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
implementation 'com.atlassian.commonmark:commonmark:0.14.0'
|
||||
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1.LTS'
|
||||
implementation 'com.arthenica:mobile-ffmpeg-full-gpl:4.3.1.LTS'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.10'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.10'
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.os.AsyncTask;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Base64;
|
||||
|
@ -29,7 +30,6 @@ import android.util.Log;
|
|||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.Menu;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
@ -57,6 +57,7 @@ import androidx.core.app.ActivityCompat;
|
|||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
|
@ -78,12 +79,14 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.ConnectException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -101,6 +104,7 @@ import io.lbry.browser.exceptions.LbryUriException;
|
|||
import io.lbry.browser.listener.CameraPermissionListener;
|
||||
import io.lbry.browser.listener.DownloadActionListener;
|
||||
import io.lbry.browser.listener.FetchChannelsListener;
|
||||
import io.lbry.browser.listener.FilePickerListener;
|
||||
import io.lbry.browser.listener.SdkStatusListener;
|
||||
import io.lbry.browser.listener.StoragePermissionListener;
|
||||
import io.lbry.browser.listener.WalletBalanceListener;
|
||||
|
@ -173,9 +177,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
public static boolean startingShareActivity = false;
|
||||
public static boolean startingPermissionRequest = false;
|
||||
public static boolean startingSignInFlowActivity = false;
|
||||
public static boolean startingCameraRequest = false;
|
||||
private boolean enteringPIPMode = false;
|
||||
private boolean fullSyncInProgress = false;
|
||||
private int queuedSyncCount = 0;
|
||||
private String cameraOutputFilename;
|
||||
|
||||
@Setter
|
||||
private BackPressInterceptor backPressInterceptor;
|
||||
|
@ -217,6 +223,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
public static final int REQUEST_REWARDS_VERIFY_SIGN_IN = 2003;
|
||||
|
||||
public static final int REQUEST_FILE_PICKER = 5001;
|
||||
public static final int REQUEST_VIDEO_CAPTURE = 5002;
|
||||
public static final int REQUEST_TAKE_PHOTO = 5003;
|
||||
|
||||
// broadcast action names
|
||||
public static final String ACTION_SDK_READY = "io.lbry.browser.Broadcast.SdkReady";
|
||||
|
@ -282,6 +290,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
private int selectedMenuItemId = -1;
|
||||
private List<CameraPermissionListener> cameraPermissionListeners;
|
||||
private List<DownloadActionListener> downloadActionListeners;
|
||||
private List<FilePickerListener> filePickerListeners;
|
||||
private List<SdkStatusListener> sdkStatusListeners;
|
||||
private List<StoragePermissionListener> storagePermissionListeners;
|
||||
private List<WalletBalanceListener> walletBalanceListeners;
|
||||
|
@ -399,6 +408,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
openNavFragments = new HashMap<>();
|
||||
cameraPermissionListeners = new ArrayList<>();
|
||||
downloadActionListeners = new ArrayList<>();
|
||||
filePickerListeners = new ArrayList<>();
|
||||
sdkStatusListeners = new ArrayList<>();
|
||||
storagePermissionListeners = new ArrayList<>();
|
||||
walletBalanceListeners = new ArrayList<>();
|
||||
|
@ -535,6 +545,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
downloadActionListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void addFilePickerListener(FilePickerListener listener) {
|
||||
if (!filePickerListeners.contains(listener)) {
|
||||
filePickerListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFilePickerListener(FilePickerListener listener) {
|
||||
filePickerListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void addSdkStatusListener(SdkStatusListener listener) {
|
||||
if (!sdkStatusListeners.contains(listener)) {
|
||||
sdkStatusListeners.add(listener);
|
||||
|
@ -641,6 +661,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
openFragment(ChannelFormFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS, params);
|
||||
}
|
||||
|
||||
public void openPublishesOnSuccessfulPublish() {
|
||||
// close publish form
|
||||
getSupportFragmentManager().popBackStack();
|
||||
openFragment(PublishesFragment.class, true, NavMenuItem.ID_ITEM_PUBLISHES);
|
||||
}
|
||||
|
||||
public void openPublishForm(Claim claim) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (claim != null) {
|
||||
|
@ -1355,7 +1381,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
syncWalletAndLoadPreferences();
|
||||
scheduleWalletBalanceUpdate();
|
||||
scheduleWalletSyncTask();
|
||||
fetchChannels();
|
||||
fetchOwnChannels();
|
||||
fetchOwnClaims();
|
||||
|
||||
initFloatingWalletBalance();
|
||||
}
|
||||
|
@ -1740,6 +1767,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
drawer.closeDrawer(GravityCompat.START);
|
||||
} else {
|
||||
boolean handled = false;
|
||||
// TODO: Refactor both forms as back press interceptors?
|
||||
ChannelFormFragment channelFormFragment = null;
|
||||
PublishFormFragment publishFormFragment = null;
|
||||
for (Fragment fragment : openNavFragments.values()) {
|
||||
|
@ -1756,7 +1784,13 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
handled = true;
|
||||
return;
|
||||
}
|
||||
//if (publishFormFragment != null && )
|
||||
if (publishFormFragment != null && (publishFormFragment.isSaveInProgress() || publishFormFragment.isTranscodeInProgress())) {
|
||||
if (publishFormFragment.isTranscodeInProgress()) {
|
||||
showMessage(R.string.transcode_in_progress);
|
||||
}
|
||||
handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
// check fragment and nav history
|
||||
|
@ -1829,24 +1863,15 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_FILE_PICKER) {
|
||||
startingFilePickerActivity = false;
|
||||
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);
|
||||
for (FilePickerListener listener : filePickerListeners) {
|
||||
listener.onFilePicked(filePath);
|
||||
}
|
||||
} else {
|
||||
if (channelFormFragment != null) {
|
||||
channelFormFragment.onFilePickerCancelled();
|
||||
for (FilePickerListener listener : filePickerListeners) {
|
||||
listener.onFilePickerCancelled();
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_SIMPLE_SIGN_IN || requestCode == REQUEST_WALLET_SYNC_SIGN_IN) {
|
||||
|
@ -1866,9 +1891,56 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
scheduleWalletSyncTask();
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_VIDEO_CAPTURE || requestCode == REQUEST_TAKE_PHOTO) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("directFilePath", cameraOutputFilename);
|
||||
openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params);
|
||||
}
|
||||
cameraOutputFilename = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void requestVideoCapture() {
|
||||
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
String outputPath = String.format("%s/record", Utils.getAppInternalStorageDir(this));
|
||||
File dir = new File(outputPath);
|
||||
if (!dir.isDirectory()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
cameraOutputFilename = String.format("%s/VID_%s.mp4", outputPath, Helper.FILESTAMP_FORMAT.format(new Date()));
|
||||
Uri outputUri = FileProvider.getUriForFile(this, String.format("%s.fileprovider", getPackageName()), new File(cameraOutputFilename));
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
|
||||
startActivityForResult(intent, REQUEST_VIDEO_CAPTURE);
|
||||
return;
|
||||
}
|
||||
|
||||
showError(getString(R.string.cannot_capture_video));
|
||||
}
|
||||
|
||||
public void requestTakePhoto() {
|
||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
String outputPath = String.format("%s/photos", Utils.getAppInternalStorageDir(this));
|
||||
File dir = new File(outputPath);
|
||||
if (!dir.isDirectory()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
cameraOutputFilename = String.format("%s/IMG_%s.jpg", outputPath, Helper.FILESTAMP_FORMAT.format(new Date()));
|
||||
Uri outputUri = FileProvider.getUriForFile(this, String.format("%s.fileprovider", getPackageName()), new File(cameraOutputFilename));
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
|
||||
startActivityForResult(intent, REQUEST_TAKE_PHOTO);
|
||||
return;
|
||||
}
|
||||
|
||||
showError(getString(R.string.cannot_take_photo));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void applyNavbarSigninPadding() {
|
||||
int statusBarHeight = getStatusBarHeight();
|
||||
|
||||
|
@ -2550,11 +2622,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
}
|
||||
}
|
||||
|
||||
public void fetchChannels() {
|
||||
public void fetchOwnChannels() {
|
||||
ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, null, new ClaimListResultHandler() {
|
||||
@Override
|
||||
public void onSuccess(List<Claim> claims) {
|
||||
Lbry.ownChannels = new ArrayList<>(claims);
|
||||
Lbry.ownChannels = Helper.filterDeletedClaims(new ArrayList<>(claims));
|
||||
for (FetchChannelsListener listener : fetchChannelsListeners) {
|
||||
listener.onChannelsFetched(claims);
|
||||
}
|
||||
|
@ -2568,6 +2640,19 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
|||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
public void fetchOwnClaims() {
|
||||
ClaimListTask task = new ClaimListTask(Arrays.asList(Claim.TYPE_STREAM, Claim.TYPE_REPOST), null, new ClaimListResultHandler() {
|
||||
@Override
|
||||
public void onSuccess(List<Claim> claims) {
|
||||
Lbry.ownClaims = Helper.filterDeletedClaims(new ArrayList<>(claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) { }
|
||||
});
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void checkSyncedWallet() {
|
||||
String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE);
|
||||
// Just check if the current user has a synced wallet, no need to do anything else here
|
||||
|
|
|
@ -11,14 +11,11 @@ import android.widget.TextView;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.lbry.browser.R;
|
||||
import io.lbry.browser.listener.ChannelItemSelectionListener;
|
||||
import io.lbry.browser.model.Claim;
|
||||
import io.lbry.browser.model.GalleryItem;
|
||||
import io.lbry.browser.utils.Helper;
|
||||
import lombok.Setter;
|
||||
|
@ -78,7 +75,7 @@ public class GalleryGridAdapter extends RecyclerView.Adapter<GalleryGridAdapter.
|
|||
@Override
|
||||
public void onBindViewHolder(GalleryGridAdapter.ViewHolder vh, int position) {
|
||||
GalleryItem item = items.get(position);
|
||||
String thumbnailUrl = item.getThumbnailUrl();
|
||||
String thumbnailUrl = item.getThumbnailPath();
|
||||
Glide.with(context.getApplicationContext()).load(thumbnailUrl).centerCrop().into(vh.thumbnailView);
|
||||
vh.durationView.setVisibility(item.getDuration() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
vh.durationView.setText(item.getDuration() > 0 ? Helper.formatDuration(Double.valueOf(item.getDuration() / 1000.0).longValue()) : null);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package io.lbry.browser.listener;
|
||||
|
||||
public interface FilePickerListener {
|
||||
void onFilePicked(String filePath);
|
||||
void onFilePickerCancelled();
|
||||
}
|
|
@ -8,6 +8,6 @@ public class GalleryItem {
|
|||
private String name;
|
||||
private String filePath;
|
||||
private String type;
|
||||
private String thumbnailUrl;
|
||||
private String thumbnailPath;
|
||||
private long duration;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
@ -47,14 +46,14 @@ public class UploadImageTask extends AsyncTask<Void, Void, String> {
|
|||
}
|
||||
String fileType = String.format("image/%s", extension);
|
||||
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM).
|
||||
addFormDataPart("name", makeid()).
|
||||
addFormDataPart("name", Helper.makeid(24)).
|
||||
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")) {
|
||||
if (json.has("success") && Helper.getJSONBoolean("success", false, json)) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
String url = Helper.getJSONString("url", null, data);
|
||||
if (Helper.isNullOrEmpty(url)) {
|
||||
|
@ -62,9 +61,15 @@ public class UploadImageTask extends AsyncTask<Void, Void, String> {
|
|||
}
|
||||
|
||||
thumbnailUrl = String.format("%s.%s", url, extension);
|
||||
} else if (json.has("error")) {
|
||||
JSONObject error = json.getJSONObject("error");
|
||||
String message = Helper.getJSONString("message", null, error);
|
||||
} else if (json.has("error") || json.has("message")) {
|
||||
JSONObject error = Helper.getJSONObject("error", json);
|
||||
String message = null;
|
||||
if (error != null) {
|
||||
message = Helper.getJSONString("message", null, error);
|
||||
}
|
||||
if (Helper.isNullOrEmpty(message)) {
|
||||
message = Helper.getJSONString("message", null, json);
|
||||
}
|
||||
throw new LbryResponseException(Helper.isNullOrEmpty(message) ? "The image failed to upload." : message);
|
||||
}
|
||||
} catch (IOException | JSONException | LbryResponseException ex) {
|
||||
|
@ -84,16 +89,6 @@ public class UploadImageTask extends AsyncTask<Void, Void, String> {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package io.lbry.browser.tasks;
|
||||
package io.lbry.browser.tasks.claim;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
|
@ -0,0 +1,99 @@
|
|||
package io.lbry.browser.tasks.claim;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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 PublishClaimTask extends AsyncTask<Void, Void, Claim> {
|
||||
private Claim claim;
|
||||
private String filePath;
|
||||
private boolean update;
|
||||
private View progressView;
|
||||
private ClaimResultHandler handler;
|
||||
private Exception error;
|
||||
public PublishClaimTask(Claim claim, String filePath, boolean update, View progressView, ClaimResultHandler handler) {
|
||||
this.claim = claim;
|
||||
this.filePath = filePath;
|
||||
this.update = update;
|
||||
this.progressView = progressView;
|
||||
this.handler = handler;
|
||||
}
|
||||
protected void onPreExecute() {
|
||||
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||
if (handler != null) {
|
||||
handler.beforeStart();
|
||||
}
|
||||
}
|
||||
protected Claim doInBackground(Void... params) {
|
||||
Claim.StreamMetadata metadata = (Claim.StreamMetadata) claim.getValue();
|
||||
DecimalFormat amountFormat = new DecimalFormat(Helper.SDK_AMOUNT_FORMAT);
|
||||
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("blocking", true);
|
||||
options.put("name", claim.getName());
|
||||
options.put("bid", amountFormat.format(new BigDecimal(claim.getAmount()).doubleValue()));
|
||||
options.put("title", Helper.isNullOrEmpty(claim.getTitle()) ? "" : claim.getTitle());
|
||||
options.put("description", Helper.isNullOrEmpty(claim.getDescription()) ? "" : claim.getDescription());
|
||||
options.put("thumbnail_url", Helper.isNullOrEmpty(claim.getThumbnailUrl()) ? "" : claim.getThumbnailUrl());
|
||||
|
||||
if (!Helper.isNullOrEmpty(filePath)) {
|
||||
options.put("file_path", filePath);
|
||||
}
|
||||
if (claim.getTags() != null && claim.getTags().size() > 0) {
|
||||
options.put("tags", new ArrayList<>(claim.getTags()));
|
||||
}
|
||||
if (metadata.getFee() != null) {
|
||||
options.put("fee_currency", metadata.getFee().getCurrency());
|
||||
options.put("fee_amount", amountFormat.format(new BigDecimal(metadata.getFee().getAmount()).doubleValue()));
|
||||
}
|
||||
if (claim.getSigningChannel() != null) {
|
||||
options.put("channel_id", claim.getSigningChannel().getClaimId());
|
||||
}
|
||||
|
||||
// TODO: license, license_url, languages
|
||||
|
||||
Claim claimResult = null;
|
||||
try {
|
||||
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_PUBLISH, options);
|
||||
if (result.has("outputs")) {
|
||||
JSONArray outputs = result.getJSONArray("outputs");
|
||||
for (int i = 0; i < outputs.length(); i++) {
|
||||
JSONObject output = outputs.getJSONObject(i);
|
||||
if (output.has("claim_id") && output.has("claim_op")) {
|
||||
claimResult = Claim.claimFromOutput(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ApiCallException | ClassCastException | JSONException ex) {
|
||||
error = ex;
|
||||
}
|
||||
|
||||
return claimResult;
|
||||
|
||||
}
|
||||
protected void onPostExecute(Claim result) {
|
||||
Helper.setViewVisibility(progressView, View.GONE);
|
||||
if (handler != null) {
|
||||
if (result != null) {
|
||||
handler.onSuccess(result);
|
||||
} else {
|
||||
handler.onError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -105,7 +105,7 @@ public class LoadGalleryItemsTask extends AsyncTask<Void, GalleryItem, List<Gall
|
|||
}
|
||||
|
||||
if (file.exists() && file.length() > 0) {
|
||||
item.setThumbnailUrl(Uri.fromFile(file).toString());
|
||||
item.setThumbnailPath(file.getAbsolutePath());
|
||||
itemsWithThumbnails.add(item);
|
||||
publishProgress(item);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import io.lbry.browser.BuildConfig;
|
|||
import io.lbry.browser.MainActivity;
|
||||
import io.lbry.browser.R;
|
||||
import io.lbry.browser.adapter.TagListAdapter;
|
||||
import io.lbry.browser.listener.FilePickerListener;
|
||||
import io.lbry.browser.listener.StoragePermissionListener;
|
||||
import io.lbry.browser.listener.WalletBalanceListener;
|
||||
import io.lbry.browser.model.Claim;
|
||||
|
@ -46,7 +47,7 @@ import io.lbry.browser.model.Tag;
|
|||
import io.lbry.browser.model.WalletBalance;
|
||||
import io.lbry.browser.tasks.UpdateSuggestedTagsTask;
|
||||
import io.lbry.browser.tasks.UploadImageTask;
|
||||
import io.lbry.browser.tasks.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.claim.ClaimResultHandler;
|
||||
import io.lbry.browser.tasks.lbryinc.LogPublishTask;
|
||||
import io.lbry.browser.ui.BaseFragment;
|
||||
|
@ -54,11 +55,10 @@ import io.lbry.browser.utils.Helper;
|
|||
import io.lbry.browser.utils.Lbry;
|
||||
import io.lbry.browser.utils.LbryAnalytics;
|
||||
import io.lbry.browser.utils.LbryUri;
|
||||
import io.lbry.browser.utils.Predefined;
|
||||
import lombok.Getter;
|
||||
|
||||
public class ChannelFormFragment extends BaseFragment implements
|
||||
StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener {
|
||||
FilePickerListener, StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener {
|
||||
|
||||
private static final int SUGGESTED_LIMIT = 8;
|
||||
|
||||
|
@ -289,16 +289,16 @@ public class ChannelFormFragment extends BaseFragment implements
|
|||
}
|
||||
|
||||
private void validateAndSaveClaim(Claim claim) {
|
||||
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 (!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;
|
||||
|
@ -368,7 +368,7 @@ public class ChannelFormFragment extends BaseFragment implements
|
|||
private void showError(String message) {
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show();
|
||||
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,9 +455,9 @@ public class ChannelFormFragment extends BaseFragment implements
|
|||
|
||||
@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();
|
||||
if (getContext() != null) {
|
||||
showError(getString(R.string.image_upload_failed));
|
||||
}
|
||||
if (coverFilePickerActive) {
|
||||
// cover selected
|
||||
imageCover.setImageResource(R.drawable.default_channel_cover);
|
||||
|
@ -498,6 +498,8 @@ public class ChannelFormFragment extends BaseFragment implements
|
|||
activity.showNavigationBackIcon();
|
||||
activity.lockDrawer();
|
||||
activity.hideFloatingWalletBalance();
|
||||
|
||||
activity.addFilePickerListener(this);
|
||||
activity.addWalletBalanceListener(this);
|
||||
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
@ -523,6 +525,7 @@ public class ChannelFormFragment extends BaseFragment implements
|
|||
activity.restoreToggle();
|
||||
activity.showFloatingWalletBalance();
|
||||
if (!MainActivity.startingFilePickerActivity) {
|
||||
activity.removeFilePickerListener(this);
|
||||
activity.removeNavFragment(ChannelFormFragment.class, NavMenuItem.ID_ITEM_CHANNELS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1249,7 +1249,7 @@ public class FileViewFragment extends BaseFragment implements
|
|||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("uri", currentUrl);
|
||||
bundle.putBoolean("paid", true);
|
||||
bundle.putString("paid", "true");
|
||||
bundle.putDouble("amount", Helper.parseDouble(fee.getAmount(), 0));
|
||||
bundle.putDouble("lbc_amount", cost);
|
||||
bundle.putString("currency", fee.getCurrency());
|
||||
|
@ -1333,6 +1333,7 @@ public class FileViewFragment extends BaseFragment implements
|
|||
// paid is handled differently
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("uri", currentUrl);
|
||||
bundle.putString("paid", "false");
|
||||
LbryAnalytics.logEvent(LbryAnalytics.EVENT_PURCHASE_URI, bundle);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
package io.lbry.browser.ui.publish;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -14,22 +20,37 @@ import android.view.ViewGroup;
|
|||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.AppCompatSpinner;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Config;
|
||||
import com.arthenica.mobileffmpeg.FFmpeg;
|
||||
import com.arthenica.mobileffmpeg.FFprobe;
|
||||
|
||||
import com.arthenica.mobileffmpeg.Statistics;
|
||||
import com.arthenica.mobileffmpeg.StatisticsCallback;
|
||||
import com.bumptech.glide.Glide;
|
||||
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.switchmaterial.SwitchMaterial;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -40,19 +61,23 @@ import io.lbry.browser.MainActivity;
|
|||
import io.lbry.browser.R;
|
||||
import io.lbry.browser.adapter.InlineChannelSpinnerAdapter;
|
||||
import io.lbry.browser.adapter.TagListAdapter;
|
||||
import io.lbry.browser.listener.FilePickerListener;
|
||||
import io.lbry.browser.listener.SdkStatusListener;
|
||||
import io.lbry.browser.listener.StoragePermissionListener;
|
||||
import io.lbry.browser.listener.WalletBalanceListener;
|
||||
import io.lbry.browser.model.Claim;
|
||||
import io.lbry.browser.model.Fee;
|
||||
import io.lbry.browser.model.GalleryItem;
|
||||
import io.lbry.browser.model.NavMenuItem;
|
||||
import io.lbry.browser.model.Tag;
|
||||
import io.lbry.browser.model.WalletBalance;
|
||||
import io.lbry.browser.tasks.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.UpdateSuggestedTagsTask;
|
||||
import io.lbry.browser.tasks.UploadImageTask;
|
||||
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
|
||||
import io.lbry.browser.tasks.claim.ClaimListTask;
|
||||
import io.lbry.browser.tasks.claim.ClaimResultHandler;
|
||||
import io.lbry.browser.tasks.claim.PublishClaimTask;
|
||||
import io.lbry.browser.tasks.lbryinc.LogPublishTask;
|
||||
import io.lbry.browser.ui.BaseFragment;
|
||||
import io.lbry.browser.utils.Helper;
|
||||
|
@ -60,15 +85,30 @@ import io.lbry.browser.utils.Lbry;
|
|||
import io.lbry.browser.utils.LbryAnalytics;
|
||||
import io.lbry.browser.utils.LbryUri;
|
||||
import io.lbry.browser.utils.Predefined;
|
||||
import io.lbry.lbrysdk.Utils;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
public class PublishFormFragment extends BaseFragment implements
|
||||
SdkStatusListener, StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener {
|
||||
FilePickerListener, SdkStatusListener, StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener {
|
||||
|
||||
private static final String H264_CODEC = "h264";
|
||||
private static final int MAX_VIDEO_DIMENSION = 1920;
|
||||
private static final int MAX_BITRATE = 5000000; // 5mbps
|
||||
|
||||
private static final int SUGGESTED_LIMIT = 8;
|
||||
|
||||
private boolean editMode;
|
||||
private boolean fetchingChannels;
|
||||
@Getter
|
||||
private boolean saveInProgress;
|
||||
private String currentFilter;
|
||||
private boolean publishFileChecked;
|
||||
private boolean fetchingChannels;
|
||||
private boolean launchPickerPending;
|
||||
@Getter
|
||||
private boolean transcodeInProgress;
|
||||
private long transcodeStartTime;
|
||||
private VideoTranscodeTask videoTranscodeTask;
|
||||
|
||||
private TextInputEditText inputTagFilter;
|
||||
private RecyclerView addedTagsList;
|
||||
|
@ -77,6 +117,7 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private TagListAdapter addedTagsAdapter;
|
||||
private TagListAdapter suggestedTagsAdapter;
|
||||
private TagListAdapter matureTagsAdapter;
|
||||
private ProgressBar progressPublish;
|
||||
private ProgressBar progressLoadingChannels;
|
||||
private View noTagsView;
|
||||
private View noTagResultsView;
|
||||
|
@ -93,6 +134,8 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private View textNoPrice;
|
||||
private View layoutPrice;
|
||||
private SwitchMaterial switchPrice;
|
||||
private ImageView imageThumbnail;
|
||||
private TextView linkGenerateAddress;
|
||||
|
||||
private TextInputEditText inputTitle;
|
||||
private TextInputEditText inputDescription;
|
||||
|
@ -102,7 +145,7 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private View inlineDepositBalanceContainer;
|
||||
private TextView inlineDepositBalanceValue;
|
||||
|
||||
private View linkCancel;
|
||||
private View linkPublishCancel;
|
||||
private MaterialButton buttonPublish;
|
||||
|
||||
private View inlineChannelCreator;
|
||||
|
@ -114,38 +157,67 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private View inlineChannelCreatorProgress;
|
||||
private MaterialButton inlineChannelCreatorCreateButton;
|
||||
|
||||
private boolean uploading;
|
||||
private String lastSelectedThumbnailFile;
|
||||
private String uploadedThumbnailUrl;
|
||||
private boolean editFieldsLoaded;
|
||||
private Claim currentClaim;
|
||||
private GalleryItem currentGalleryItem;
|
||||
private String currentFilePath;
|
||||
private String transcodedFilePath;
|
||||
private boolean fileLoaded;
|
||||
|
||||
private View mediaContainer;
|
||||
private View uploadProgress;
|
||||
private CardView cardVideoOptimization;
|
||||
private ProgressBar optimizationRealProgress;
|
||||
private ProgressBar optimizationProgress;
|
||||
private TextView textOptimizationProgress;
|
||||
private TextView textOptimizationStatus;
|
||||
private TextView textOptimizationElapsed;
|
||||
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_publish_form, container, false);
|
||||
|
||||
scrollView = root.findViewById(R.id.publish_form_scroll_view);
|
||||
progressLoadingChannels = root.findViewById(R.id.publish_form_loading_channels);
|
||||
progressPublish = root.findViewById(R.id.publish_form_publishing);
|
||||
channelSpinner = root.findViewById(R.id.publish_form_channel_spinner);
|
||||
mediaContainer = root.findViewById(R.id.publish_form_media_container);
|
||||
|
||||
inputTagFilter = root.findViewById(R.id.form_tag_filter_input);
|
||||
noTagsView = root.findViewById(R.id.form_no_added_tags);
|
||||
noTagResultsView = root.findViewById(R.id.form_no_tag_results);
|
||||
|
||||
inlineDepositBalanceContainer = root.findViewById(R.id.publish_form_inline_balance_container);
|
||||
inlineDepositBalanceValue = root.findViewById(R.id.publish_form_inline_balance_value);
|
||||
|
||||
cardVideoOptimization = root.findViewById(R.id.publish_form_video_opt_card);
|
||||
optimizationProgress = root.findViewById(R.id.publish_form_video_opt_progress);
|
||||
optimizationRealProgress = root.findViewById(R.id.publish_form_video_opt_real_progress);
|
||||
textOptimizationProgress = root.findViewById(R.id.publish_form_video_opt_progress_text);
|
||||
textOptimizationStatus = root.findViewById(R.id.publish_form_video_opt_status);
|
||||
textOptimizationElapsed = root.findViewById(R.id.publish_form_video_opt_elapsed);
|
||||
|
||||
layoutExtraFields = root.findViewById(R.id.publish_form_extra_options_container);
|
||||
linkShowExtraFields = root.findViewById(R.id.publish_form_toggle_extra);
|
||||
layoutPrice = root.findViewById(R.id.publish_form_price_container);
|
||||
textNoPrice = root.findViewById(R.id.publish_form_no_price);
|
||||
switchPrice = root.findViewById(R.id.publish_form_price_switch);
|
||||
uploadProgress = root.findViewById(R.id.publish_form_thumbnail_upload_progress);
|
||||
imageThumbnail = root.findViewById(R.id.publish_form_thumbnail_preview);
|
||||
linkGenerateAddress = root.findViewById(R.id.publish_form_generate_address);
|
||||
|
||||
inputTitle = root.findViewById(R.id.publish_form_input_title);
|
||||
inputDeposit = root.findViewById(R.id.publish_form_input_description);
|
||||
inputDescription = root.findViewById(R.id.publish_form_input_description);
|
||||
inputPrice = root.findViewById(R.id.publish_form_input_price);
|
||||
inputAddress = root.findViewById(R.id.publish_form_input_address);
|
||||
inputDeposit = root.findViewById(R.id.publish_form_input_deposit);
|
||||
priceCurrencySpinner = root.findViewById(R.id.publish_form_currency_spinner);
|
||||
|
||||
linkCancel = root.findViewById(R.id.publish_form_cancel);
|
||||
linkPublishCancel = root.findViewById(R.id.publish_form_cancel);
|
||||
buttonPublish = root.findViewById(R.id.publish_form_publish_button);
|
||||
|
||||
Context context = getContext();
|
||||
|
@ -192,7 +264,12 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
|
||||
private void initUi() {
|
||||
|
||||
inputAddress.setText(Helper.generateUrl());
|
||||
linkGenerateAddress.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
inputAddress.setText(Helper.generateUrl());
|
||||
}
|
||||
});
|
||||
|
||||
switchPrice.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
|
@ -228,13 +305,10 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
}
|
||||
});
|
||||
|
||||
linkCancel.setOnClickListener(new View.OnClickListener() {
|
||||
mediaContainer.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
((MainActivity) context).onBackPressed();
|
||||
}
|
||||
checkStoragePermissionAndLaunchFilePicker();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -278,10 +352,64 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
}
|
||||
});
|
||||
|
||||
linkPublishCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (transcodeInProgress) {
|
||||
// show alert confirming the user is sure, and then cancel
|
||||
FFmpeg.cancel();
|
||||
transcodeInProgress = false;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
((MainActivity) context).onBackPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
buttonPublish.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (uploading) {
|
||||
Snackbar.make(view, R.string.publish_no_thumbnail, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
} else if (Helper.isNullOrEmpty(uploadedThumbnailUrl)) {
|
||||
showError(getString(R.string.publish_thumbnail_in_progress));
|
||||
return;
|
||||
}
|
||||
|
||||
// check minimum deposit
|
||||
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 < Helper.MIN_DEPOSIT) {
|
||||
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;
|
||||
}
|
||||
|
||||
String priceString = Helper.getValue(inputPrice.getText());
|
||||
double priceAmount = Helper.parseDouble(priceString, 0);
|
||||
if (switchPrice.isChecked() && priceAmount == 0) {
|
||||
showError(getString(R.string.price_amount_not_set));
|
||||
return;
|
||||
}
|
||||
|
||||
Claim claim = buildPublishClaim();
|
||||
if (validatePublishClaim(claim)) {
|
||||
publishClaim(claim);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -306,6 +434,8 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
activity.showNavigationBackIcon();
|
||||
activity.lockDrawer();
|
||||
activity.hideFloatingWalletBalance();
|
||||
|
||||
activity.addFilePickerListener(this);
|
||||
activity.addWalletBalanceListener(this);
|
||||
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
@ -320,13 +450,18 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
MainActivity activity = (MainActivity) getContext();
|
||||
activity.removeWalletBalanceListener(this);
|
||||
activity.restoreToggle();
|
||||
activity.showFloatingWalletBalance();
|
||||
if (!MainActivity.startingFilePickerActivity) {
|
||||
activity.removeWalletBalanceListener(this);
|
||||
activity.removeFilePickerListener(this);
|
||||
activity.removeNavFragment(PublishFormFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH);
|
||||
if (transcodeInProgress) {
|
||||
FFmpeg.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
@ -352,6 +487,33 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
}
|
||||
}
|
||||
|
||||
private void checkStoragePermissionAndLaunchFilePicker() {
|
||||
Context context = getContext();
|
||||
if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) {
|
||||
launchPickerPending = false;
|
||||
launchFilePicker();
|
||||
} else {
|
||||
launchPickerPending = true;
|
||||
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) {
|
||||
MainActivity.startingFilePickerActivity = true;
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.setType("image/*");
|
||||
((MainActivity) context).startActivityForResult(
|
||||
Intent.createChooser(intent, getString(R.string.select_thumbnail)),
|
||||
MainActivity.REQUEST_FILE_PICKER);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFieldsFromCurrentClaim() {
|
||||
if (currentClaim != null && !editFieldsLoaded) {
|
||||
|
||||
|
@ -360,15 +522,20 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
}
|
||||
|
||||
private void checkPublishFile() {
|
||||
if (publishFileChecked) {
|
||||
return;
|
||||
}
|
||||
|
||||
String filePath = "";
|
||||
String thumbnailPath = null;
|
||||
if (currentGalleryItem != null) {
|
||||
// check gallery item type
|
||||
filePath = currentGalleryItem.getFilePath();
|
||||
thumbnailPath = currentGalleryItem.getThumbnailPath();
|
||||
} else if (currentFilePath != null) {
|
||||
filePath = currentFilePath;
|
||||
}
|
||||
|
||||
android.util.Log.d("#HELP", "filePath=" + filePath);
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
// file doesn't exist. although this shouldn't happen
|
||||
|
@ -376,19 +543,190 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check content type
|
||||
String type = null;
|
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
|
||||
if (extension != null) {
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
android.util.Log.d("#HELP", "fileType=" + type);
|
||||
|
||||
boolean isVideo = false;
|
||||
boolean isImage = !Helper.isNullOrEmpty(type) && type.startsWith("image");
|
||||
if (!Helper.isNullOrEmpty(type) && type.startsWith("video")) {
|
||||
// ffmpeg video handling
|
||||
isVideo = true;
|
||||
if (!transcodeInProgress) {
|
||||
probeVideo(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (isVideo || isImage) {
|
||||
checkAndUploadThumbnail(filePath, thumbnailPath, isVideo ? "video" : "image");
|
||||
}
|
||||
|
||||
Helper.setViewVisibility(cardVideoOptimization, isVideo ? View.VISIBLE : View.GONE);
|
||||
|
||||
publishFileChecked = true;
|
||||
}
|
||||
|
||||
private void checkAndUploadThumbnail(String filePath, String thumbnailPath, String type) {
|
||||
if (Helper.isNullOrEmpty(thumbnailPath)) {
|
||||
createAndUploadThumbnail(filePath, type);
|
||||
} else {
|
||||
uploadThumbnail(thumbnailPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndUploadThumbnail(String filePath, String type) {
|
||||
Context context = getContext();
|
||||
CreateThumbnailTask task = new CreateThumbnailTask(filePath, type, context, new CreateThumbnailTask.CreateThumbnailHandler() {
|
||||
@Override
|
||||
public void onSuccess(String thumbnailPath) {
|
||||
uploadThumbnail(thumbnailPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) {
|
||||
if (context != null) {
|
||||
showError(getString(R.string.thumbnail_creation_failed));
|
||||
}
|
||||
}
|
||||
});
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void uploadThumbnail(String thumbnailPath) {
|
||||
if (uploading) {
|
||||
Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
Glide.with(context.getApplicationContext()).load(thumbnailPath).centerCrop().into(imageThumbnail);
|
||||
}
|
||||
|
||||
uploading = true;
|
||||
uploadedThumbnailUrl = null;
|
||||
UploadImageTask task = new UploadImageTask(thumbnailPath, uploadProgress, new UploadImageTask.UploadThumbnailHandler() {
|
||||
@Override
|
||||
public void onSuccess(String url) {
|
||||
lastSelectedThumbnailFile = thumbnailPath;
|
||||
uploadedThumbnailUrl = url;
|
||||
uploading = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) {
|
||||
View view = getView();
|
||||
if (context != null && view != null) {
|
||||
showError(getString(R.string.image_upload_failed));
|
||||
}
|
||||
lastSelectedThumbnailFile = null;
|
||||
imageThumbnail.setImageDrawable(null);
|
||||
uploading = false;
|
||||
}
|
||||
});
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void probeVideo(String filePath) {
|
||||
VideoProbeTask task = new VideoProbeTask(filePath, new VideoProbeTask.VideoProbeHandler() {
|
||||
@Override
|
||||
public void onVideoProbed(VideoInformation result) {
|
||||
checkAndTranscodeVideo(filePath, result);
|
||||
}
|
||||
});
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
}
|
||||
|
||||
private void checkAndTranscodeVideo(String filePath, VideoInformation videoInformation) {
|
||||
boolean transcodeRequired = (videoInformation == null ||
|
||||
!H264_CODEC.equalsIgnoreCase(videoInformation.getCodecName()) ||
|
||||
MAX_VIDEO_DIMENSION < videoInformation.getWidth() || MAX_VIDEO_DIMENSION < videoInformation.getHeight() ||
|
||||
MAX_BITRATE < videoInformation.getBitrate());
|
||||
|
||||
String scalePart = "";
|
||||
if (videoInformation != null) {
|
||||
// check the max dimension that we need to scale
|
||||
int videoWidth = videoInformation.getWidth();
|
||||
int videoHeight = videoInformation.getHeight();
|
||||
// get the highest dimension
|
||||
int maxDimension = Math.max(videoWidth, videoHeight);
|
||||
if (maxDimension > MAX_VIDEO_DIMENSION) {
|
||||
scalePart = maxDimension == videoWidth ? String.format("-vf scale=%d:-2", MAX_VIDEO_DIMENSION) : String.format("-vf scale=-2:%d", MAX_VIDEO_DIMENSION);
|
||||
}
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
String outputPath = String.format("%s/videos", Utils.getAppInternalStorageDir(context));
|
||||
File dir = new File(outputPath);
|
||||
if (!dir.isDirectory()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
boolean hasFullDuration = videoInformation != null && videoInformation.getDurationSeconds() > 0;
|
||||
Helper.setViewVisibility(optimizationRealProgress, hasFullDuration ? View.VISIBLE : View.GONE);
|
||||
Helper.setViewVisibility(optimizationProgress, hasFullDuration ? View.GONE : View.VISIBLE);
|
||||
|
||||
File sourceFile = new File(filePath);
|
||||
String filename = sourceFile.getName();
|
||||
if (!filename.endsWith(".mp4")) {
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
filename = String.format("%s.mp4", lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename);
|
||||
}
|
||||
|
||||
String videoFilePath = String.format("%s/%s", outputPath, filename);
|
||||
File targetFile = new File(videoFilePath);
|
||||
if (targetFile.exists()) {
|
||||
targetFile.delete();
|
||||
}
|
||||
|
||||
transcodeInProgress = true;
|
||||
videoTranscodeTask = new VideoTranscodeTask(filePath, videoFilePath, scalePart, transcodeRequired, new VideoTranscodeTask.VideoTranscodeHandler() {
|
||||
@Override
|
||||
public void onProgress(int time) {
|
||||
if (context != null) {
|
||||
int currentDuration = Double.valueOf(time / 1000.0).intValue();
|
||||
int fullDuration = videoInformation != null ? videoInformation.getDurationSeconds() : 0;
|
||||
long elapsed = System.currentTimeMillis() - transcodeStartTime;
|
||||
String completedDurationText = Helper.formatDuration(currentDuration);
|
||||
if (fullDuration > 0) {
|
||||
completedDurationText = String.format("%s / %s", completedDurationText, Helper.formatDuration(fullDuration));
|
||||
int percentComplete = Double.valueOf(Math.ceil((double) currentDuration / (double) fullDuration * 100.0)).intValue();
|
||||
optimizationRealProgress.setProgress(percentComplete);
|
||||
}
|
||||
|
||||
|
||||
String text = context.getString(R.string.completed_video_duration, completedDurationText);
|
||||
Helper.setViewText(textOptimizationProgress, text);
|
||||
Helper.setViewText(textOptimizationElapsed, Helper.formatDuration(Double.valueOf(elapsed / 1000.0).longValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(String outputFilePath) {
|
||||
transcodedFilePath = outputFilePath;
|
||||
transcodeInProgress = false;
|
||||
Helper.setViewText(textOptimizationStatus, R.string.video_optimized);
|
||||
Helper.setViewVisibility(optimizationRealProgress, View.GONE);
|
||||
Helper.setViewVisibility(optimizationProgress, View.GONE);
|
||||
Helper.setViewVisibility(textOptimizationProgress, View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorOrCancelled() {
|
||||
transcodeInProgress = false;
|
||||
Helper.setViewText(textOptimizationStatus, R.string.video_optimize_failed);
|
||||
Helper.setViewVisibility(optimizationRealProgress, View.GONE);
|
||||
Helper.setViewVisibility(optimizationProgress, View.GONE);
|
||||
Helper.setViewVisibility(textOptimizationProgress, View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
transcodeStartTime = System.currentTimeMillis();
|
||||
videoTranscodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void cancelOnFatalCondition(String message) {
|
||||
|
@ -415,7 +753,7 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
checkParams();
|
||||
updateFieldsFromCurrentClaim();
|
||||
|
||||
if (currentClaim == null && (currentGalleryItem != null || (Helper.isNullOrEmpty(currentFilePath)))) {
|
||||
if (currentClaim == null && (currentGalleryItem != null || !Helper.isNullOrEmpty(currentFilePath))) {
|
||||
// load file information
|
||||
checkPublishFile();
|
||||
}
|
||||
|
@ -533,11 +871,128 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private Claim buildPublishClaim() {
|
||||
Claim claim = new Claim();
|
||||
|
||||
claim.setName(Helper.getValue(inputAddress.getText()));
|
||||
claim.setAmount(Helper.getValue(inputDeposit.getText()));
|
||||
|
||||
Claim.StreamMetadata metadata = new Claim.StreamMetadata();
|
||||
metadata.setTitle(Helper.getValue(inputTitle.getText()));
|
||||
metadata.setDescription(Helper.getValue(inputDescription.getText()));
|
||||
metadata.setTags(Helper.getTagsForTagObjects(addedTagsAdapter.getTags()));
|
||||
|
||||
Claim selectedChannel = (Claim) channelSpinner.getSelectedItem();
|
||||
if (selectedChannel != null && !selectedChannel.isPlaceholder() && !selectedChannel.isPlaceholderAnonymous()) {
|
||||
claim.setSigningChannel(selectedChannel);
|
||||
}
|
||||
if (switchPrice.isChecked()) {
|
||||
Fee fee = new Fee();
|
||||
fee.setCurrency((String) priceCurrencySpinner.getSelectedItem());
|
||||
fee.setAmount(Helper.getValue(inputPrice.getText()));
|
||||
metadata.setFee(fee);
|
||||
}
|
||||
|
||||
if (!Helper.isNullOrEmpty(uploadedThumbnailUrl)) {
|
||||
Claim.Resource thumbnail = new Claim.Resource();
|
||||
thumbnail.setUrl(uploadedThumbnailUrl);
|
||||
metadata.setThumbnail(thumbnail);
|
||||
}
|
||||
|
||||
// TODO: License, LicenseDescription, LicenseUrl, Language
|
||||
claim.setValueType(Claim.TYPE_STREAM);
|
||||
claim.setValue(metadata);
|
||||
|
||||
return claim;
|
||||
}
|
||||
|
||||
private boolean validatePublishClaim() {
|
||||
return false;
|
||||
private boolean validatePublishClaim(Claim claim) {
|
||||
if (Helper.isNullOrEmpty(claim.getTitle())) {
|
||||
showError(getString(R.string.please_provide_title));
|
||||
return false;
|
||||
}
|
||||
if (Helper.isNullOrEmpty(claim.getName())) {
|
||||
showError(getString(R.string.please_specify_address));
|
||||
return false;
|
||||
}
|
||||
if (!LbryUri.isNameValid(claim.getName())) {
|
||||
showError(getString(R.string.address_invalid_characters));
|
||||
return false;
|
||||
}
|
||||
if (Helper.claimNameExists(claim.getName())) {
|
||||
showError(getString(R.string.address_already_used));
|
||||
return false;
|
||||
}
|
||||
|
||||
String publishFilePath = currentGalleryItem != null ? currentGalleryItem.getFilePath() : currentFilePath;
|
||||
if (Helper.isNullOrEmpty(publishFilePath) && Helper.isNullOrEmpty(transcodedFilePath)) {
|
||||
showError(getString(R.string.no_file_selected));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void publishClaim(Claim claim) {
|
||||
String finalFilePath = transcodedFilePath;
|
||||
if (Helper.isNullOrEmpty(finalFilePath)) {
|
||||
finalFilePath = currentGalleryItem != null ? currentGalleryItem.getFilePath() : currentFilePath;
|
||||
}
|
||||
saveInProgress = true;
|
||||
PublishClaimTask task = new PublishClaimTask(claim, finalFilePath, editMode, progressPublish, new ClaimResultHandler() {
|
||||
@Override
|
||||
public void beforeStart() {
|
||||
preSave();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Claim claimResult) {
|
||||
postSave();
|
||||
|
||||
android.util.Log.d("#HELP", claimResult.toString());
|
||||
|
||||
// Run the logPublish task
|
||||
if (!BuildConfig.DEBUG) {
|
||||
LogPublishTask logPublish = new LogPublishTask(claimResult);
|
||||
logPublish.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
// publish done
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("claim_id", claimResult.getClaimId());
|
||||
bundle.putString("claim_name", claimResult.getName());
|
||||
LbryAnalytics.logEvent(LbryAnalytics.EVENT_PUBLISH, bundle);
|
||||
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
MainActivity activity = (MainActivity) context;
|
||||
activity.showMessage(R.string.publish_successful);
|
||||
activity.openPublishesOnSuccessfulPublish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) {
|
||||
showError(error.getMessage());
|
||||
postSave();
|
||||
}
|
||||
});
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void preSave() {
|
||||
saveInProgress = true;
|
||||
|
||||
// disable input views
|
||||
|
||||
Helper.setViewEnabled(linkShowExtraFields, false);
|
||||
Helper.setViewEnabled(linkPublishCancel, false);
|
||||
Helper.setViewEnabled(buttonPublish, false);
|
||||
}
|
||||
|
||||
private void postSave() {
|
||||
Helper.setViewEnabled(linkShowExtraFields, true);
|
||||
Helper.setViewEnabled(linkPublishCancel, true);
|
||||
Helper.setViewEnabled(buttonPublish, true);
|
||||
|
||||
saveInProgress = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -747,17 +1202,270 @@ public class PublishFormFragment extends BaseFragment implements
|
|||
private void showError(String message) {
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show();
|
||||
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).
|
||||
setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUploadButton() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoragePermissionGranted() {
|
||||
|
||||
if (launchPickerPending) {
|
||||
launchPickerPending = false;
|
||||
launchFilePicker();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoragePermissionRefused() {
|
||||
showError(getString(R.string.storage_permission_rationale_images));
|
||||
launchPickerPending = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
android.util.Log.d("#HELP", "FilePicked: " + filePath);
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
if (filePath.equalsIgnoreCase(lastSelectedThumbnailFile)) {
|
||||
// previous selected cover was uploaded successfully
|
||||
android.util.Log.d("#HELP", "lastSelectedThumbnailFile the same");
|
||||
return;
|
||||
}
|
||||
android.util.Log.d("#HELP", "PickedFilePath=" + filePath);
|
||||
|
||||
Uri fileUri = Uri.fromFile(new File(filePath));
|
||||
Glide.with(context.getApplicationContext()).load(fileUri).centerCrop().into(imageThumbnail);
|
||||
uploadThumbnail(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilePickerCancelled() {
|
||||
// nothing to do here
|
||||
// At some point in the future, allow file picking for publish file?
|
||||
}
|
||||
|
||||
private static class VideoProbeTask extends AsyncTask<Void, Void, VideoInformation> {
|
||||
private String filePath;
|
||||
private VideoProbeHandler handler;
|
||||
public VideoProbeTask(String filePath, VideoProbeHandler handler) {
|
||||
this.filePath = filePath;
|
||||
this.handler = handler;
|
||||
}
|
||||
protected VideoInformation doInBackground(Void... params) {
|
||||
try {
|
||||
int code = FFprobe.execute(String.format("-v quiet -show_streams -select_streams v -print_format json -i \"%s\"", filePath));
|
||||
if (code == Config.RETURN_CODE_SUCCESS) {
|
||||
String json = Config.getLastCommandOutput();
|
||||
JSONObject result = new JSONObject(json);
|
||||
if (result.has("streams")) {
|
||||
JSONArray streams = result.getJSONArray("streams");
|
||||
if (streams.length() > 0) {
|
||||
JSONObject stream = streams.getJSONObject(0);
|
||||
VideoInformation videoInformation = VideoInformation.fromJSONObject(stream);
|
||||
return videoInformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
// pass
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected void onPostExecute(VideoInformation result) {
|
||||
if (handler != null) {
|
||||
handler.onVideoProbed(result);
|
||||
}
|
||||
}
|
||||
|
||||
public interface VideoProbeHandler {
|
||||
void onVideoProbed(VideoInformation result);
|
||||
}
|
||||
}
|
||||
|
||||
private static class VideoTranscodeTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private String filePath;
|
||||
private String scaleFlag;
|
||||
private String outputFilePath;
|
||||
private boolean transcodeRequired;
|
||||
private VideoTranscodeHandler handler;
|
||||
|
||||
public VideoTranscodeTask(String filePath, String outputFilePath, String scaleFlag, boolean transcodeRequired, VideoTranscodeHandler handler) {
|
||||
this.handler = handler;
|
||||
this.filePath = filePath;
|
||||
this.outputFilePath = outputFilePath;
|
||||
this.scaleFlag = scaleFlag;
|
||||
this.transcodeRequired = transcodeRequired;
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
String movFlagsCommand = String.format("-i \"%s\" -movflags +faststart \"%s\"", filePath, outputFilePath);
|
||||
String command = transcodeRequired ? String.format(
|
||||
"-i \"%s\" " +
|
||||
"-c:v libx264 " +
|
||||
"-c:a aac -b:a 128k " +
|
||||
"%s " +
|
||||
"-crf 27 -preset ultrafast " +
|
||||
"-pix_fmt yuv420p " +
|
||||
"-maxrate 5000K -bufsize 5000K " +
|
||||
"-movflags +faststart \"%s\"", filePath, scaleFlag, outputFilePath) : movFlagsCommand;
|
||||
android.util.Log.d("#HELP", command);
|
||||
|
||||
Config.enableStatisticsCallback(new StatisticsCallback() {
|
||||
@Override
|
||||
public void apply(Statistics statistics) {
|
||||
publishProgress(statistics.getTime());
|
||||
}
|
||||
});
|
||||
int code = FFmpeg.execute(command);
|
||||
return code == Config.RETURN_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
protected void onProgressUpdate(Integer... times) {
|
||||
if (handler != null) {
|
||||
for (Integer time : times) {
|
||||
handler.onProgress(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (handler != null) {
|
||||
if (result) {
|
||||
handler.onSuccess(outputFilePath);
|
||||
} else {
|
||||
handler.onErrorOrCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface VideoTranscodeHandler {
|
||||
void onProgress(int time);
|
||||
void onSuccess(String outputFilePath);
|
||||
void onErrorOrCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class VideoInformation {
|
||||
private String codecName;
|
||||
private int width;
|
||||
private int height;
|
||||
private int durationSeconds;
|
||||
private long bitrate;
|
||||
|
||||
private static int tryParseDuration(JSONObject streamObject) {
|
||||
String durationString = Helper.getJSONString("duration", "0", streamObject);
|
||||
double parsedDuration = Helper.parseDouble(durationString, 0);
|
||||
if (parsedDuration > 0) {
|
||||
return Double.valueOf(parsedDuration).intValue();
|
||||
}
|
||||
|
||||
try {
|
||||
if (streamObject.has("tags") && !streamObject.isNull("tags")) {
|
||||
JSONObject tags = streamObject.getJSONObject("tags");
|
||||
String tagDurationString = Helper.getJSONString("DURATION", null, tags);
|
||||
if (Helper.isNull(tagDurationString)) {
|
||||
tagDurationString = Helper.getJSONString("duration", null, tags);
|
||||
}
|
||||
if (!Helper.isNullOrEmpty(tagDurationString) && tagDurationString.indexOf(':') > -1) {
|
||||
String[] parts = tagDurationString.split(":");
|
||||
if (parts.length == 3) {
|
||||
int hours = Helper.parseInt(parts[0], 0);
|
||||
int minutes = Helper.parseInt(parts[1], 0);
|
||||
int seconds = Helper.parseDouble(parts[2], 0).intValue();
|
||||
return (hours * 60 * 60) + (minutes * 60) + seconds;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static VideoInformation fromJSONObject(JSONObject streamObject) {
|
||||
VideoInformation info = new VideoInformation();
|
||||
info.setCodecName(Helper.getJSONString("codec_name", null, streamObject));
|
||||
info.setWidth(Helper.getJSONInt("width", 0, streamObject));
|
||||
info.setHeight(Helper.getJSONInt("height", 0, streamObject));
|
||||
info.setBitrate(Helper.getJSONLong("bit_rate", 0, streamObject));
|
||||
info.setDurationSeconds(tryParseDuration(streamObject));
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CreateThumbnailTask extends AsyncTask<Void, Void, String> {
|
||||
private Context context;
|
||||
private String filePath;
|
||||
private String type;
|
||||
private CreateThumbnailHandler handler;
|
||||
private Exception error;
|
||||
public CreateThumbnailTask(String filePath, String type, Context context, CreateThumbnailHandler handler) {
|
||||
this.context = context;
|
||||
this.type = type;
|
||||
this.filePath = filePath;
|
||||
this.handler = handler;
|
||||
}
|
||||
protected String doInBackground(Void... params) {
|
||||
String thumbnailPath = null;
|
||||
FileOutputStream os = null;
|
||||
Bitmap thumbnail = null;
|
||||
try {
|
||||
File cacheDir = context.getExternalCacheDir();
|
||||
File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath()));
|
||||
if (!thumbnailsDir.isDirectory()) {
|
||||
thumbnailsDir.mkdirs();
|
||||
}
|
||||
|
||||
// save the thumbnail to the path
|
||||
thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), Helper.makeid(8));
|
||||
if ("video".equals(type)) {
|
||||
thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||
} else {
|
||||
Bitmap source = BitmapFactory.decodeFile(filePath);
|
||||
// MINI_KIND dimensions
|
||||
thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false);
|
||||
}
|
||||
|
||||
os = new FileOutputStream(thumbnailPath);
|
||||
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||
} catch (Exception ex) {
|
||||
error = ex;
|
||||
return null;
|
||||
} finally {
|
||||
Helper.closeCloseable(os);
|
||||
}
|
||||
|
||||
return thumbnailPath;
|
||||
}
|
||||
protected void onPostExecute(String thumbnailPath) {
|
||||
if (handler != null) {
|
||||
if (!Helper.isNullOrEmpty(thumbnailPath)) {
|
||||
handler.onSuccess(thumbnailPath);
|
||||
} else {
|
||||
handler.onError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface CreateThumbnailHandler {
|
||||
void onSuccess(String thumbnailPath);
|
||||
void onError(Exception error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.lbry.browser.ui.publish;
|
|||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
|
@ -38,6 +39,7 @@ import io.lbry.browser.MainActivity;
|
|||
import io.lbry.browser.R;
|
||||
import io.lbry.browser.adapter.GalleryGridAdapter;
|
||||
import io.lbry.browser.listener.CameraPermissionListener;
|
||||
import io.lbry.browser.listener.FilePickerListener;
|
||||
import io.lbry.browser.listener.StoragePermissionListener;
|
||||
import io.lbry.browser.model.GalleryItem;
|
||||
import io.lbry.browser.model.NavMenuItem;
|
||||
|
@ -47,9 +49,9 @@ import io.lbry.browser.utils.Helper;
|
|||
import io.lbry.browser.utils.Lbry;
|
||||
import io.lbry.browser.utils.LbryAnalytics;
|
||||
|
||||
public class PublishFragment extends BaseFragment implements CameraPermissionListener, StoragePermissionListener {
|
||||
public class PublishFragment extends BaseFragment implements
|
||||
CameraPermissionListener, FilePickerListener, StoragePermissionListener {
|
||||
|
||||
private boolean loadGalleryItemsPending;
|
||||
private PreviewView cameraPreview;
|
||||
private RecyclerView galleryGrid;
|
||||
private GalleryGridAdapter adapter;
|
||||
|
@ -60,6 +62,8 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
private View buttonTakePhoto;
|
||||
private View buttonUpload;
|
||||
|
||||
private boolean loadGalleryItemsPending;
|
||||
private boolean launchFilePickerPending;
|
||||
private boolean recordPending;
|
||||
private boolean takePhotoPending;
|
||||
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
|
||||
|
@ -95,6 +99,12 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
checkCameraPermissionAndTakePhoto();
|
||||
}
|
||||
});
|
||||
buttonUpload.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
checkStoragePermissionAndLaunchFilePicker();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
@ -137,6 +147,11 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
}
|
||||
|
||||
private void checkCameraPermissionAndRecord() {
|
||||
if (!Lbry.SDK_READY) {
|
||||
Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (!MainActivity.hasPermission(Manifest.permission.CAMERA, context)) {
|
||||
recordPending = true;
|
||||
|
@ -147,11 +162,16 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
context,
|
||||
true);
|
||||
} else {
|
||||
// start video record intent
|
||||
record();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCameraPermissionAndTakePhoto() {
|
||||
if (!Lbry.SDK_READY) {
|
||||
Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (!MainActivity.hasPermission(Manifest.permission.CAMERA, context)) {
|
||||
takePhotoPending = true;
|
||||
|
@ -161,8 +181,56 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
getString(R.string.camera_permission_rationale_photo),
|
||||
context,
|
||||
true);
|
||||
} else {
|
||||
// start video record intent
|
||||
} else {
|
||||
takePhoto();
|
||||
}
|
||||
}
|
||||
|
||||
private void takePhoto() {
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
takePhotoPending = false;
|
||||
((MainActivity) context).requestTakePhoto();
|
||||
}
|
||||
}
|
||||
|
||||
private void record() {
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
recordPending = false;
|
||||
((MainActivity) context).requestVideoCapture();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStoragePermissionAndLaunchFilePicker() {
|
||||
if (!Lbry.SDK_READY) {
|
||||
Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) {
|
||||
launchFilePickerPending = false;
|
||||
launchFilePicker();
|
||||
} else {
|
||||
launchFilePickerPending = true;
|
||||
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) {
|
||||
MainActivity.startingFilePickerActivity = true;
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.setType("*/*");
|
||||
((MainActivity) context).startActivityForResult(
|
||||
Intent.createChooser(intent, getString(R.string.upload_file)),
|
||||
MainActivity.REQUEST_FILE_PICKER);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,6 +241,7 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
MainActivity activity = (MainActivity) context;
|
||||
LbryAnalytics.setCurrentScreen(activity, "Publish", "Publish");
|
||||
activity.addCameraPermissionListener(this);
|
||||
activity.addFilePickerListener(this);
|
||||
activity.addStoragePermissionListener(this);
|
||||
activity.hideFloatingWalletBalance();
|
||||
|
||||
|
@ -193,6 +262,9 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
activity.removeCameraPermissionListener(this);
|
||||
activity.removeStoragePermissionListener(this);
|
||||
activity.showFloatingWalletBalance();
|
||||
if (!MainActivity.startingFilePickerActivity) {
|
||||
activity.removeFilePickerListener(this);
|
||||
}
|
||||
}
|
||||
CameraX.unbindAll();
|
||||
super.onStop();
|
||||
|
@ -276,10 +348,10 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
public void onCameraPermissionGranted() {
|
||||
if (recordPending) {
|
||||
// record video
|
||||
recordPending = false;
|
||||
record();
|
||||
} else if (takePhotoPending) {
|
||||
// take a photo
|
||||
takePhotoPending = false;
|
||||
takePhoto();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +385,10 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
loadGalleryItemsPending = false;
|
||||
loadGalleryItems();
|
||||
}
|
||||
if (launchFilePickerPending) {
|
||||
launchFilePickerPending = false;
|
||||
launchFilePicker();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -331,4 +407,19 @@ public class PublishFragment extends BaseFragment implements CameraPermissionLis
|
|||
public boolean shouldSuspendGlobalPlayer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilePicked(String filePath) {
|
||||
Context context = getContext();
|
||||
if (context instanceof MainActivity) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("directFilePath", filePath);
|
||||
((MainActivity) context).openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilePickerCancelled() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ import io.lbry.browser.model.lbryinc.Invitee;
|
|||
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
|
||||
import io.lbry.browser.tasks.claim.ClaimListTask;
|
||||
import io.lbry.browser.tasks.GenericTaskHandler;
|
||||
import io.lbry.browser.tasks.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask;
|
||||
import io.lbry.browser.tasks.claim.ClaimResultHandler;
|
||||
import io.lbry.browser.tasks.lbryinc.LogPublishTask;
|
||||
import io.lbry.browser.tasks.lbryinc.FetchInviteStatusTask;
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.io.Closeable;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
@ -67,12 +68,13 @@ 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 final double MIN_DEPOSIT = 0.01;
|
||||
public static final String LBC_CURRENCY_FORMAT_PATTERN = "#,###.##";
|
||||
public static final String FILE_SIZE_FORMAT_PATTERN = "#,###.#";
|
||||
public static final DecimalFormat LBC_CURRENCY_FORMAT = new DecimalFormat(LBC_CURRENCY_FORMAT_PATTERN);
|
||||
public static final DecimalFormat FULL_LBC_CURRENCY_FORMAT = new DecimalFormat("#,###.########");
|
||||
public static final DecimalFormat SIMPLE_CURRENCY_FORMAT = new DecimalFormat("#,##0.00");
|
||||
public static final SimpleDateFormat FILESTAMP_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||
public static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx";
|
||||
|
||||
public static boolean isNull(String value) {
|
||||
|
@ -438,6 +440,14 @@ public final class Helper {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
public static boolean claimNameExists(String claimName) {
|
||||
for (Claim claim : Lbry.ownClaims) {
|
||||
if (claimName.equalsIgnoreCase(claim.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
|
||||
return getRealPathFromURI_API19(context, uri, false);
|
||||
|
@ -718,4 +728,14 @@ public final class Helper {
|
|||
rv.setAdapter(adapter);
|
||||
rv.scrollToPosition(prevScrollPosition > 0 ? prevScrollPosition : 0);
|
||||
}
|
||||
|
||||
public static String makeid(int length) {
|
||||
Random random = new Random();
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder id = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
id.append(chars.charAt(random.nextInt(chars.length())));
|
||||
}
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ public final class Lbry {
|
|||
public static final String METHOD_FILE_LIST = "file_list";
|
||||
public static final String METHOD_FILE_DELETE = "file_delete";
|
||||
public static final String METHOD_GET = "get";
|
||||
public static final String METHOD_PUBLISH = "publish";
|
||||
|
||||
public static final String METHOD_WALLET_BALANCE = "wallet_balance";
|
||||
public static final String METHOD_WALLET_ENCRYPT = "wallet_encrypt";
|
||||
|
|
|
@ -117,6 +117,82 @@
|
|||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/publish_form_video_opt_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp">
|
||||
<TextView
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/video_optimization" />
|
||||
<TextView
|
||||
android:id="@+id/publish_form_video_opt_elapsed"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textFontWeight="300"
|
||||
android:textSize="16sp" />
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/publish_form_video_opt_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter"
|
||||
android:text="@string/video_being_optimized"
|
||||
android:textFontWeight="300"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/publish_form_video_opt_real_progress"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone"
|
||||
style="?android:progressBarStyleHorizontal"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/publish_form_video_opt_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
<ProgressBar
|
||||
android:id="@+id/publish_form_video_opt_progress"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="16dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/publish_form_video_opt_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@id/publish_form_video_opt_progress"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textFontWeight="300"
|
||||
android:textSize="14sp" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -252,13 +328,31 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/content_address" />
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/content_address" />
|
||||
<TextView
|
||||
android:id="@+id/publish_form_generate_address"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/randomize"
|
||||
android:textFontWeight="300" />
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -449,9 +543,17 @@
|
|||
android:text="@string/cancel"
|
||||
android:textFontWeight="300" />
|
||||
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/publish_form_publishing"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_toLeftOf="@id/publish_form_publish_button"
|
||||
android:visibility="gone" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/publish_form_publish_button"
|
||||
android:enabled="false"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
<string name="price">Price</string>
|
||||
<string name="free_publish">Your content will be free. Press the toggle to set a price.</string>
|
||||
<string name="content_address">Content address</string>
|
||||
<string name="randomize">Randomize</string>
|
||||
<string name="address">Address</string>
|
||||
<string name="content_address_desc">The address where people can find your content (ex. lbry://myvideo)</string>
|
||||
<string name="license">License</string>
|
||||
|
@ -115,7 +116,26 @@
|
|||
<string name="show_extra_fields">Show extra fields</string>
|
||||
<string name="hide_extra_fields">Hide extra fields</string>
|
||||
<string name="no_file_found">No file found to publish.</string>
|
||||
<string name="sdk_initializing_publish">You cannot publish content right now beceause the background service is still initializing.</string>
|
||||
<string name="video_optimization">Video optimization</string>
|
||||
<string name="thumbnail_creation_failed">A thumbnail could not be automatically created from your content file.</string>
|
||||
<string name="video_being_optimized">Your video is being optimized for better support on a wide range of devices. You can fill out the remaining fields below while this is in progress.</string>
|
||||
<string name="video_optimized">Your video was successfully optimized for better playback across as many devices as possible. Please proceed to publish your content.</string>
|
||||
<string name="video_optimize_failed">Your video could not be optimized. The file will be uploaded with no changes.</string>
|
||||
<string name="completed_video_duration">Completed Video Duration: %1$s</string>
|
||||
<string name="sdk_initializing_publish">You cannot publish content right now because the background service is still initializing.</string>
|
||||
<string name="publish_successful">Your content was successfully published. It may take a few moments to appear on the blockchain.</string>
|
||||
<string name="transcode_in_progress">Video optimization is in progress. If you wish to cancel, press Cancel at the bottom of the page.</string>
|
||||
<string name="cannot_capture_video">There is no camera app available to record videos on this device.</string>
|
||||
<string name="cannot_take_photo">There is no camera app available to take photos on this device.</string>
|
||||
|
||||
<string name="please_provide_title">Please provide a title.</string>
|
||||
<string name="please_specify_address">Please specify an address where people can find your content.</string>
|
||||
<string name="address_invalid_characters">Your content address contains invalid characters.</string>
|
||||
<string name="address_already_used">You have already published to the specified content address. Please enter a new address.</string>
|
||||
<string name="no_file_selected">No file selected. Please choose a video or take a photo, or select a file before publishing.</string>
|
||||
<string name="price_amount_not_set">Please enter a price or turn off the toggle to make your content free.</string>
|
||||
<string name="publish_no_thumbnail">Please select a thumbnail to upload before publishing.</string>
|
||||
<string name="publish_thumbnail_in_progress">Please wait for the thumbnail to finish uploading before publishing.</string>
|
||||
|
||||
<string name="language">Language</string>
|
||||
<string name="english">English</string>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-files-path path="lbrynet/" name="lbrynet" />
|
||||
<external-files-path path="record/" name="record" />
|
||||
<external-files-path path="photos/" name="photos" />
|
||||
</paths>
|
Loading…
Reference in a new issue