Upload Photo: bottom sheet with the progress bar

Uplaod progress dialog
Upload complete dialog
This commit is contained in:
androiddevkotlin 2021-01-26 14:54:33 +02:00
parent cf1866d8d5
commit 959e7f411c
5 changed files with 393 additions and 132 deletions

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:minHeight="@dimen/bottom_sheet_selected_item_title_height"
android:orientation="vertical"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/measurement_tool_menu_title_padding_top"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="Some title" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/route_info_buttons_padding_left_right"
android:layout_marginBottom="@dimen/route_info_buttons_padding_left_right"
android:minHeight="0dp"
android:visibility="visible" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Some description" />
</LinearLayout>

View file

@ -11,7 +11,10 @@
Thx - Hardy
-->
<string name="uploaded_count">Uploaded %1$s of %2$d</string>
<string name="uploading_count">Uploading %1$s of %2$d</string>
<string name="upload_photo_completed">Upload completed</string>
<string name="upload_photo">Uploading</string>
<string name="copy_to_map_favorites">Copy to favorites</string>
<string name="copy_to_map_markers">Copy to map markers</string>
<string name="delete_waypoints">Delete waypoints</string>

View file

@ -0,0 +1,111 @@
package net.osmand.plus.dialogs;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem;
import net.osmand.plus.widgets.TextViewEx;
public class UploadPhotoWithProgressBarBottomSheet extends MenuBottomSheetDialogFragment {
public static final String TAG = UploadPhotoWithProgressBarBottomSheet.class.getSimpleName();
private ProgressBar progressBar;
private TextView uploadedPhotosCounter;
private TextViewEx uploadedPhotosTitle;
private int maxProgress;
@Override
public void createMenuItems(Bundle savedInstanceState) {
Context context = requireContext();
LayoutInflater inflater = UiUtilities.getInflater(context, nightMode);
View view = inflater.inflate(R.layout.bottom_sheet_with_progress_bar, null);
progressBar = view.findViewById(R.id.progress_bar);
progressBar.setMax(maxProgress);
uploadedPhotosCounter = view.findViewById(R.id.description);
uploadedPhotosTitle = view.findViewById(R.id.title);
BaseBottomSheetItem descriptionItem = new BottomSheetItemWithDescription.Builder()
.setDescription(getString(R.string.uploading_count, progressBar.getProgress(), maxProgress))
.setTitle(getString(R.string.upload_photo))
.setCustomView(view)
.create();
items.add(descriptionItem);
int padding = getResources().getDimensionPixelSize(R.dimen.content_padding_small);
items.add(new DividerSpaceItem(requireContext(), padding));
}
public void onProgressUpdate(int progress) {
progressBar.setProgress(progress);
uploadedPhotosCounter.setText((getString(R.string.uploading_count, progressBar.getProgress(), maxProgress)));
}
public void onUploadingFinished() {
TextViewEx dismissButtonText = dismissButton.findViewById(R.id.button_text);
setDismissButtonTextId(R.string.shared_string_close);
dismissButtonText.setText(R.string.shared_string_close);
uploadedPhotosTitle.setText(R.string.upload_photo_completed);
uploadedPhotosCounter.setText((getString(R.string.uploaded_count, progressBar.getProgress(), maxProgress)));
}
@Override
protected boolean useVerticalButtons() {
return true;
}
@Override
protected int getDismissButtonTextId() {
return R.string.shared_string_cancel;
}
@Override
protected DialogButtonType getRightBottomButtonType() {
return DialogButtonType.PRIMARY;
}
@Override
public int getSecondDividerHeight() {
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_icon_margin);
}
@Override
protected void onRightBottomButtonClick() {
dismiss();
}
@Nullable
public MapActivity getMapActivity() {
Activity activity = getActivity();
if (activity instanceof MapActivity) {
return (MapActivity) activity;
}
return null;
}
public static UploadPhotoWithProgressBarBottomSheet showInstance(@NonNull FragmentManager fragmentManager, int maxProgress) {
UploadPhotoWithProgressBarBottomSheet fragment = new UploadPhotoWithProgressBarBottomSheet();
fragment.maxProgress = maxProgress;
fragment.setRetainInstance(true);
fragmentManager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss();
return fragment;
}
}

View file

@ -1,22 +1,19 @@
package net.osmand.plus.mapcontextmenu;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@ -46,7 +43,6 @@ import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.osm.PoiCategory;
import net.osmand.osm.PoiType;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
@ -54,6 +50,7 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.Version;
import net.osmand.plus.activities.ActivityResultListener;
import net.osmand.plus.activities.ActivityResultListener.OnActivityResultListener;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard;
@ -80,12 +77,7 @@ import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -99,7 +91,6 @@ import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCa
public class MenuBuilder {
private static final int PICK_IMAGE = 1231;
private static final int MAX_IMAGE_LENGTH = 2048;
private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class);
public static final float SHADOW_HEIGHT_TOP_DP = 17f;
public static final int TITLE_LIMIT = 60;
@ -425,7 +416,7 @@ public class MenuBuilder {
if (false) {
AddPhotosBottomSheetDialogFragment.showInstance(mapActivity.getSupportFragmentManager());
} else {
registerResultListener(view);
registerResultListener();
final String baseUrl = OPRConstants.getBaseUrl(app);
final String name = app.getSettings().OPR_USERNAME.get();
final String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
@ -443,6 +434,9 @@ public class MenuBuilder {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT > 18) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
mapActivity.startActivityForResult(Intent.createChooser(intent,
mapActivity.getString(R.string.select_picture)), PICK_IMAGE);
}
@ -472,132 +466,33 @@ public class MenuBuilder {
false, null, false);
}
private void registerResultListener(final View view) {
mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new ActivityResultListener.
OnActivityResultListener() {
private void registerResultListener() {
mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new OnActivityResultListener() {
@Override
public void onResult(int resultCode, Intent resultData) {
if (resultData != null) {
handleSelectedImage(view, resultData.getData());
List<Uri> imagesUri = new ArrayList<>();
Uri data = resultData.getData();
if (data != null) {
imagesUri.add(data);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clipData = resultData.getClipData();
if (clipData != null) {
for (int i = 0; i < clipData.getItemCount(); i++) {
Uri uri = resultData.getClipData().getItemAt(i).getUri();
if (uri != null) {
imagesUri.add(uri);
}
}
}
}
execute(new UploadPhotosAsyncTask(mapActivity, imagesUri, getLatLon(), placeId, getAdditionalCardParams(), imageCardListener));
}
}
}));
}
private void handleSelectedImage(final View view, final Uri uri) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = app.getContentResolver().openInputStream(uri);
if (inputStream != null) {
uploadImageToPlace(inputStream);
}
} catch (Exception e) {
LOG.error(e);
String str = app.getString(R.string.cannot_upload_image);
showToastMessage(str);
} finally {
Algorithms.closeStream(inputStream);
}
}
});
t.start();
}
private void uploadImageToPlace(InputStream image) {
InputStream serverData = new ByteArrayInputStream(compressImageToJpeg(image));
final String baseUrl = OPRConstants.getBaseUrl(app);
// all these should be constant
String url = baseUrl + "api/ipfs/image";
String response = NetworkUtils.sendPostDataRequest(url, "file", "compressed.jpeg", serverData);
if (response != null) {
int res = 0;
try {
StringBuilder error = new StringBuilder();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
String username = app.getSettings().OPR_USERNAME.get();
res = openDBAPI.uploadImage(
placeId,
baseUrl,
privateKey,
username,
response, error);
if (res != 200) {
showToastMessage(error.toString());
} else {
//ok, continue
}
} catch (FailedVerificationException e) {
LOG.error(e);
checkTokenAndShowScreen();
}
if (res != 200) {
//image was uploaded but not added to blockchain
checkTokenAndShowScreen();
} else {
String str = app.getString(R.string.successfully_uploaded_pattern, 1, 1);
showToastMessage(str);
//refresh the image
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener));
}
} else {
checkTokenAndShowScreen();
}
}
private void showToastMessage(final String str) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(mapActivity.getBaseContext(), str, Toast.LENGTH_LONG).show();
}
});
}
//This method runs on non main thread
private void checkTokenAndShowScreen() {
final String baseUrl = OPRConstants.getBaseUrl(app);
final String name = app.getSettings().OPR_USERNAME.get();
final String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
String str = app.getString(R.string.cannot_upload_image);
showToastMessage(str);
} else {
app.runInUIThread(new Runnable() {
@Override
public void run() {
OprStartFragment.showInstance(mapActivity.getSupportFragmentManager());
}
});
}
}
private byte[] compressImageToJpeg(InputStream image) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(image);
Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
int h = bmp.getHeight();
int w = bmp.getWidth();
boolean scale = false;
while (w > MAX_IMAGE_LENGTH || h > MAX_IMAGE_LENGTH) {
w = w / 2;
h = h / 2;
scale = true;
}
if (scale) {
Matrix matrix = new Matrix();
matrix.postScale(w, h);
Bitmap resizedBitmap = Bitmap.createBitmap(
bmp, 0, 0, w, h, matrix, false);
bmp.recycle();
bmp = resizedBitmap;
}
bmp.compress(Bitmap.CompressFormat.JPEG, 90, os);
return os.toByteArray();
}
private void startLoadingImages() {
if (onlinePhotoCardsRow == null) {
return;

View file

@ -0,0 +1,203 @@
package net.osmand.plus.mapcontextmenu;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import androidx.fragment.app.FragmentActivity;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.data.LatLon;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.dialogs.UploadPhotoWithProgressBarBottomSheet;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener;
import net.osmand.plus.openplacereviews.OPRConstants;
import net.osmand.plus.openplacereviews.OprStartFragment;
import net.osmand.plus.osmedit.opr.OpenDBAPI;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
public class UploadPhotosAsyncTask extends AsyncTask<Void, Integer, Void> {
private static final Log LOG = PlatformUtil.getLog(UploadPhotosAsyncTask.class);
private static final int MAX_IMAGE_LENGTH = 2048;
private final OsmandApplication app;
private final WeakReference<MapActivity> activityRef;
private UploadPhotoWithProgressBarBottomSheet progress;
private final OpenDBAPI openDBAPI = new OpenDBAPI();
private final LatLon latLon;
private final List<Uri> data;
private final String[] placeId;
private final Map<String, String> params;
private final GetImageCardsListener imageCardListener;
public UploadPhotosAsyncTask(MapActivity activity, List<Uri> data, LatLon latLon, String[] placeId,
Map<String, String> params, GetImageCardsListener imageCardListener) {
app = (OsmandApplication) activity.getApplicationContext();
activityRef = new WeakReference<>(activity);
this.data = data;
this.latLon = latLon;
this.params = params;
this.placeId = placeId;
this.imageCardListener = imageCardListener;
}
@Override
protected void onPreExecute() {
FragmentActivity activity = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(activity)) {
progress = UploadPhotoWithProgressBarBottomSheet.showInstance(activity.getSupportFragmentManager(), data.size());
}
}
protected Void doInBackground(Void... uris) {
for (int i = 0; i < data.size(); i++) {
if (isCancelled()) {
break;
}
Uri uri = data.get(i);
handleSelectedImage(uri);
publishProgress(i);
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (progress != null) {
progress.onProgressUpdate(values[0]);
}
}
@Override
protected void onPostExecute(Void aVoid) {
if (progress != null) {
progress.onUploadingFinished();
}
}
private void handleSelectedImage(final Uri uri) {
InputStream inputStream = null;
try {
inputStream = app.getContentResolver().openInputStream(uri);
if (inputStream != null) {
uploadImageToPlace(inputStream);
}
} catch (Exception e) {
LOG.error(e);
app.showToastMessage(R.string.cannot_upload_image);
} finally {
Algorithms.closeStream(inputStream);
}
}
private void uploadImageToPlace(InputStream image) {
InputStream serverData = new ByteArrayInputStream(compressImageToJpeg(image));
final String baseUrl = OPRConstants.getBaseUrl(app);
// all these should be constant
String url = baseUrl + "api/ipfs/image";
String response = NetworkUtils.sendPostDataRequest(url, "file", "compressed.jpeg", serverData);
if (response != null) {
int res = 0;
try {
StringBuilder error = new StringBuilder();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
String username = app.getSettings().OPR_USERNAME.get();
res = openDBAPI.uploadImage(
placeId,
baseUrl,
privateKey,
username,
response, error);
if (res != 200) {
app.showToastMessage(error.toString());
} else {
//ok, continue
}
} catch (FailedVerificationException e) {
LOG.error(e);
checkTokenAndShowScreen();
}
if (res != 200) {
//image was uploaded but not added to blockchain
checkTokenAndShowScreen();
} else {
String str = app.getString(R.string.successfully_uploaded_pattern, 1, 1);
app.showToastMessage(str);
//refresh the image
MapActivity activity = activityRef.get();
if (activity != null) {
MenuBuilder.execute(new GetImageCardsTask(activity, latLon, params, imageCardListener));
}
}
} else {
checkTokenAndShowScreen();
}
}
//This method runs on non main thread
private void checkTokenAndShowScreen() {
final String baseUrl = OPRConstants.getBaseUrl(app);
final String name = app.getSettings().OPR_USERNAME.get();
final String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
String str = app.getString(R.string.cannot_upload_image);
app.showToastMessage(str);
} else {
app.runInUIThread(new Runnable() {
@Override
public void run() {
MapActivity activity = activityRef.get();
if (activity != null) {
OprStartFragment.showInstance(activity.getSupportFragmentManager());
}
}
});
}
}
private byte[] compressImageToJpeg(InputStream image) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(image);
Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
int h = bmp.getHeight();
int w = bmp.getWidth();
boolean scale = false;
while (w > MAX_IMAGE_LENGTH || h > MAX_IMAGE_LENGTH) {
w = w / 2;
h = h / 2;
scale = true;
}
if (scale) {
Matrix matrix = new Matrix();
matrix.postScale(w, h);
Bitmap resizedBitmap = Bitmap.createBitmap(
bmp, 0, 0, w, h, matrix, false);
bmp.recycle();
bmp = resizedBitmap;
}
bmp.compress(Bitmap.CompressFormat.JPEG, 90, os);
return os.toByteArray();
}
}