mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-24 08:39:53 +01:00
Merge branch '1242-display-image-attachments-save' into 'master'
Allow the user to save image attachment outside of Briar See merge request briar/briar!1005
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ public interface RequestCodes {
|
|||||||
int REQUEST_UNLOCK = 11;
|
int REQUEST_UNLOCK = 11;
|
||||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||||
int REQUEST_ATTACH_IMAGE = 13;
|
int REQUEST_ATTACH_IMAGE = 13;
|
||||||
|
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.res.Resources;
|
|||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.media.ExifInterface;
|
import android.support.media.ExifInterface;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
@@ -127,12 +128,20 @@ class AttachmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate thumbnail size
|
// calculate thumbnail size
|
||||||
Size thumbnailSize = new Size(defaultSize, defaultSize);
|
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||||
if (!size.error) {
|
if (!size.error) {
|
||||||
thumbnailSize = getThumbnailSize(size.width, size.height);
|
thumbnailSize =
|
||||||
|
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||||
|
}
|
||||||
|
// get file extension
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
String extension = mimeTypeMap.getExtensionFromMimeType(size.mimeType);
|
||||||
|
if (extension == null) {
|
||||||
|
return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
|
||||||
}
|
}
|
||||||
return new AttachmentItem(messageId, size.width, size.height,
|
return new AttachmentItem(messageId, size.width, size.height,
|
||||||
thumbnailSize.width, thumbnailSize.height, size.error);
|
size.mimeType, extension, thumbnailSize.width, thumbnailSize.height,
|
||||||
|
size.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,9 +160,9 @@ class AttachmentController {
|
|||||||
orientation == ORIENTATION_TRANSVERSE ||
|
orientation == ORIENTATION_TRANSVERSE ||
|
||||||
orientation == ORIENTATION_TRANSPOSE) {
|
orientation == ORIENTATION_TRANSPOSE) {
|
||||||
//noinspection SuspiciousNameCombination
|
//noinspection SuspiciousNameCombination
|
||||||
return new Size(height, width);
|
return new Size(height, width, "image/jpeg");
|
||||||
}
|
}
|
||||||
return new Size(width, height);
|
return new Size(width, height, "image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,10 +174,11 @@ class AttachmentController {
|
|||||||
BitmapFactory.decodeStream(is, null, options);
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
if (options.outWidth < 1 || options.outHeight < 1)
|
if (options.outWidth < 1 || options.outHeight < 1)
|
||||||
return new Size();
|
return new Size();
|
||||||
return new Size(options.outWidth, options.outHeight);
|
return new Size(options.outWidth, options.outHeight,
|
||||||
|
options.outMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Size getThumbnailSize(int width, int height) {
|
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||||
float widthPercentage = maxWidth / (float) width;
|
float widthPercentage = maxWidth / (float) width;
|
||||||
float heightPercentage = maxHeight / (float) height;
|
float heightPercentage = maxHeight / (float) height;
|
||||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
@@ -184,24 +194,27 @@ class AttachmentController {
|
|||||||
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||||
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||||
}
|
}
|
||||||
return new Size(thumbnailWidth, thumbnailHeight);
|
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Size {
|
private static class Size {
|
||||||
|
|
||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
|
private final String mimeType;
|
||||||
private final boolean error;
|
private final boolean error;
|
||||||
|
|
||||||
private Size(int width, int height) {
|
private Size(int width, int height, String mimeType) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
this.error = false;
|
this.error = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Size() {
|
private Size() {
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
|
this.mimeType = "";
|
||||||
this.error = true;
|
this.error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class AttachmentItem implements Parcelable {
|
|||||||
|
|
||||||
private final MessageId messageId;
|
private final MessageId messageId;
|
||||||
private final int width, height;
|
private final int width, height;
|
||||||
|
private final String mimeType, extension;
|
||||||
private final int thumbnailWidth, thumbnailHeight;
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
private final boolean hasError;
|
private final boolean hasError;
|
||||||
|
|
||||||
@@ -30,11 +31,14 @@ public class AttachmentItem implements Parcelable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AttachmentItem(MessageId messageId, int width, int height,
|
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
|
||||||
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
|
String extension, int thumbnailWidth, int thumbnailHeight,
|
||||||
|
boolean hasError) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.extension = extension;
|
||||||
this.thumbnailWidth = thumbnailWidth;
|
this.thumbnailWidth = thumbnailWidth;
|
||||||
this.thumbnailHeight = thumbnailHeight;
|
this.thumbnailHeight = thumbnailHeight;
|
||||||
this.hasError = hasError;
|
this.hasError = hasError;
|
||||||
@@ -46,6 +50,8 @@ public class AttachmentItem implements Parcelable {
|
|||||||
messageId = new MessageId(messageIdByte);
|
messageId = new MessageId(messageIdByte);
|
||||||
width = in.readInt();
|
width = in.readInt();
|
||||||
height = in.readInt();
|
height = in.readInt();
|
||||||
|
mimeType = in.readString();
|
||||||
|
extension = in.readString();
|
||||||
thumbnailWidth = in.readInt();
|
thumbnailWidth = in.readInt();
|
||||||
thumbnailHeight = in.readInt();
|
thumbnailHeight = in.readInt();
|
||||||
hasError = in.readByte() != 0;
|
hasError = in.readByte() != 0;
|
||||||
@@ -63,6 +69,14 @@ public class AttachmentItem implements Parcelable {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getExtension() {
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
int getThumbnailWidth() {
|
int getThumbnailWidth() {
|
||||||
return thumbnailWidth;
|
return thumbnailWidth;
|
||||||
}
|
}
|
||||||
@@ -90,6 +104,8 @@ public class AttachmentItem implements Parcelable {
|
|||||||
dest.writeByteArray(messageId.getBytes());
|
dest.writeByteArray(messageId.getBytes());
|
||||||
dest.writeInt(width);
|
dest.writeInt(width);
|
||||||
dest.writeInt(height);
|
dest.writeInt(height);
|
||||||
|
dest.writeString(mimeType);
|
||||||
|
dest.writeString(extension);
|
||||||
dest.writeInt(thumbnailWidth);
|
dest.writeInt(thumbnailWidth);
|
||||||
dest.writeInt(thumbnailHeight);
|
dest.writeInt(thumbnailHeight);
|
||||||
dest.writeByte((byte) (hasError ? 1 : 0));
|
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
import android.support.design.widget.AppBarLayout;
|
import android.support.design.widget.AppBarLayout;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.transition.Fade;
|
import android.transition.Fade;
|
||||||
import android.transition.Transition;
|
import android.transition.Transition;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
@@ -26,8 +35,18 @@ import org.briarproject.briar.android.activity.BriarActivity;
|
|||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
import org.briarproject.briar.android.view.PullDownLayout;
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_CREATE_DOCUMENT;
|
||||||
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
|
import static android.content.Intent.EXTRA_TITLE;
|
||||||
import static android.graphics.Color.TRANSPARENT;
|
import static android.graphics.Color.TRANSPARENT;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
|
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||||
@@ -37,6 +56,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
|||||||
import static android.widget.ImageView.ScaleType.FIT_START;
|
import static android.widget.ImageView.ScaleType.FIT_START;
|
||||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
|
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
|
||||||
|
|
||||||
public class ImageActivity extends BriarActivity
|
public class ImageActivity extends BriarActivity
|
||||||
@@ -46,9 +66,14 @@ public class ImageActivity extends BriarActivity
|
|||||||
final static String NAME = "name";
|
final static String NAME = "name";
|
||||||
final static String DATE = "date";
|
final static String DATE = "date";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private ImageViewModel viewModel;
|
||||||
private PullDownLayout layout;
|
private PullDownLayout layout;
|
||||||
private AppBarLayout appBarLayout;
|
private AppBarLayout appBarLayout;
|
||||||
private PhotoView photoView;
|
private PhotoView photoView;
|
||||||
|
private AttachmentItem attachment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -67,6 +92,11 @@ public class ImageActivity extends BriarActivity
|
|||||||
setSceneTransitionAnimation(transition, null, transition);
|
setSceneTransitionAnimation(transition, null, transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get View Model
|
||||||
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
|
.get(ImageViewModel.class);
|
||||||
|
viewModel.getSaveState().observe(this, this::onImageSaveStateChanged);
|
||||||
|
|
||||||
// inflate layout
|
// inflate layout
|
||||||
setContentView(R.layout.activity_image);
|
setContentView(R.layout.activity_image);
|
||||||
layout = findViewById(R.id.layout);
|
layout = findViewById(R.id.layout);
|
||||||
@@ -88,7 +118,7 @@ public class ImageActivity extends BriarActivity
|
|||||||
TextView dateView = toolbar.findViewById(R.id.dateView);
|
TextView dateView = toolbar.findViewById(R.id.dateView);
|
||||||
|
|
||||||
// Intent Extras
|
// Intent Extras
|
||||||
AttachmentItem attachment = getIntent().getParcelableExtra(ATTACHMENT);
|
attachment = getIntent().getParcelableExtra(ATTACHMENT);
|
||||||
String name = getIntent().getStringExtra(NAME);
|
String name = getIntent().getStringExtra(NAME);
|
||||||
long time = getIntent().getLongExtra(DATE, 0);
|
long time = getIntent().getLongExtra(DATE, 0);
|
||||||
String date = formatDateAbsolute(this, time);
|
String date = formatDateAbsolute(this, time);
|
||||||
@@ -143,17 +173,34 @@ public class ImageActivity extends BriarActivity
|
|||||||
.into(photoView);
|
.into(photoView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.image_actions, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
onBackPressed();
|
onBackPressed();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_save_image:
|
||||||
|
showSaveImageDialog();
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int request, int result, Intent data) {
|
||||||
|
super.onActivityResult(request, result, data);
|
||||||
|
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) {
|
||||||
|
viewModel.saveImage(attachment, data.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPullStart() {
|
public void onPullStart() {
|
||||||
appBarLayout.animate()
|
appBarLayout.animate()
|
||||||
@@ -190,9 +237,8 @@ public class ImageActivity extends BriarActivity
|
|||||||
|
|
||||||
@RequiresApi(api = 16)
|
@RequiresApi(api = 16)
|
||||||
private void hideSystemUi(View decorView) {
|
private void hideSystemUi(View decorView) {
|
||||||
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE
|
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN |
|
||||||
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
| SYSTEM_UI_FLAG_FULLSCREEN
|
|
||||||
);
|
);
|
||||||
appBarLayout.animate()
|
appBarLayout.animate()
|
||||||
.translationYBy(-1 * appBarLayout.getHeight())
|
.translationYBy(-1 * appBarLayout.getHeight())
|
||||||
@@ -204,8 +250,7 @@ public class ImageActivity extends BriarActivity
|
|||||||
@RequiresApi(api = 16)
|
@RequiresApi(api = 16)
|
||||||
private void showSystemUi(View decorView) {
|
private void showSystemUi(View decorView) {
|
||||||
decorView.setSystemUiVisibility(
|
decorView.setSystemUiVisibility(
|
||||||
SYSTEM_UI_FLAG_LAYOUT_STABLE
|
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
);
|
);
|
||||||
appBarLayout.animate()
|
appBarLayout.animate()
|
||||||
.translationYBy(appBarLayout.getHeight())
|
.translationYBy(appBarLayout.getHeight())
|
||||||
@@ -230,4 +275,49 @@ public class ImageActivity extends BriarActivity
|
|||||||
drawableTop != appBarLayout.getTop();
|
drawableTop != appBarLayout.getTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showSaveImageDialog() {
|
||||||
|
OnClickListener okListener = (dialog, which) -> {
|
||||||
|
if (SDK_INT >= 19) {
|
||||||
|
Intent intent = getCreationIntent();
|
||||||
|
startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT);
|
||||||
|
} else {
|
||||||
|
viewModel.saveImage(attachment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
|
builder.setTitle(getString(R.string.dialog_title_save_image));
|
||||||
|
builder.setMessage(getString(R.string.dialog_message_save_image));
|
||||||
|
Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_security);
|
||||||
|
DrawableCompat.setTint(requireNonNull(icon),
|
||||||
|
ContextCompat.getColor(this, R.color.color_primary));
|
||||||
|
builder.setIcon(icon);
|
||||||
|
builder.setPositiveButton(R.string.save_image, okListener);
|
||||||
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 19)
|
||||||
|
private Intent getCreationIntent() {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||||
|
Locale.getDefault());
|
||||||
|
String fileName = sdf.format(new Date());
|
||||||
|
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||||
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
|
intent.setType(attachment.getMimeType());
|
||||||
|
intent.putExtra(EXTRA_TITLE, fileName);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onImageSaveStateChanged(@Nullable Boolean error) {
|
||||||
|
if (error == null) return;
|
||||||
|
int stringRes = error ?
|
||||||
|
R.string.save_image_error : R.string.save_image_success;
|
||||||
|
int colorRes = error ?
|
||||||
|
R.color.briar_red : R.color.briar_primary;
|
||||||
|
Snackbar s = Snackbar.make(layout, stringRes, LENGTH_LONG);
|
||||||
|
s.getView().setBackgroundResource(colorRes);
|
||||||
|
s.show();
|
||||||
|
viewModel.onSaveStateSeen();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.AndroidViewModel;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.media.MediaScannerConnection.scanFile;
|
||||||
|
import static android.os.Environment.DIRECTORY_PICTURES;
|
||||||
|
import static android.os.Environment.getExternalStoragePublicDirectory;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class ImageViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static Logger LOG = getLogger(ImageViewModel.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
@DatabaseExecutor
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
@IoExecutor
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
|
||||||
|
private MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ImageViewModel(Application application,
|
||||||
|
MessagingManager messagingManager,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
@IoExecutor Executor ioExecutor) {
|
||||||
|
super(application);
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LiveData that is true if the image was saved,
|
||||||
|
* false if there was an error and null otherwise.
|
||||||
|
*
|
||||||
|
* Call {@link #onSaveStateSeen()} after consuming an update.
|
||||||
|
*/
|
||||||
|
LiveData<Boolean> getSaveState() {
|
||||||
|
return saveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void onSaveStateSeen() {
|
||||||
|
saveState.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the attachment to a writeable {@link Uri}.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
saveState.setValue(false);
|
||||||
|
} else {
|
||||||
|
saveImage(attachment, () -> getOutputStream(uri), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the attachment on external storage,
|
||||||
|
* assuming the permission was granted during install time.
|
||||||
|
*/
|
||||||
|
void saveImage(AttachmentItem attachment) {
|
||||||
|
File file = getImageFile(attachment);
|
||||||
|
saveImage(attachment, () -> getOutputStream(file), () -> scanFile(
|
||||||
|
getApplication(), new String[] {file.toString()}, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||||
|
@Nullable Runnable afterCopy) {
|
||||||
|
MessageId messageId = attachment.getMessageId();
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
Attachment a = messagingManager.getAttachment(messageId);
|
||||||
|
copyImageFromDb(a, osp, afterCopy);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
saveState.postValue(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyImageFromDb(Attachment a, OutputStreamProvider osp,
|
||||||
|
@Nullable Runnable afterCopy) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
InputStream is = a.getStream();
|
||||||
|
OutputStream os = osp.getOutputStream();
|
||||||
|
copyAndClose(is, os);
|
||||||
|
if (afterCopy != null) afterCopy.run();
|
||||||
|
saveState.postValue(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
saveState.postValue(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFileName() {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||||
|
Locale.getDefault());
|
||||||
|
return sdf.format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getImageFile(AttachmentItem attachment) {
|
||||||
|
File path = getExternalStoragePublicDirectory(DIRECTORY_PICTURES);
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
path.mkdirs();
|
||||||
|
String fileName = getFileName();
|
||||||
|
String ext = "." + attachment.getExtension();
|
||||||
|
File file = new File(path, fileName + ext);
|
||||||
|
int i = 1;
|
||||||
|
while (file.exists()) {
|
||||||
|
file = new File(path, fileName + " (" + i + ")" + ext);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(File file) throws IOException {
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(Uri uri) throws IOException {
|
||||||
|
OutputStream os =
|
||||||
|
getApplication().getContentResolver().openOutputStream(uri);
|
||||||
|
if (os == null) throw new IOException();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface OutputStreamProvider {
|
||||||
|
OutputStream getOutputStream() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.arch.lifecycle.ViewModel;
|
|||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import org.briarproject.briar.android.conversation.ConversationViewModel;
|
import org.briarproject.briar.android.conversation.ConversationViewModel;
|
||||||
|
import org.briarproject.briar.android.conversation.ImageViewModel;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -20,6 +21,12 @@ public abstract class ViewModelModule {
|
|||||||
abstract ViewModel bindConversationViewModel(
|
abstract ViewModel bindConversationViewModel(
|
||||||
ConversationViewModel conversationViewModel);
|
ConversationViewModel conversationViewModel);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(ImageViewModel.class)
|
||||||
|
abstract ViewModel bindImageViewModel(
|
||||||
|
ImageViewModel imageViewModel);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract ViewModelProvider.Factory bindViewModelFactory(
|
abstract ViewModelProvider.Factory bindViewModelFactory(
|
||||||
|
|||||||
9
briar-android/src/main/res/drawable/ic_security.xml
Normal file
9
briar-android/src/main/res/drawable/ic_security.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
|
||||||
|
</vector>
|
||||||
10
briar-android/src/main/res/menu/image_actions.xml
Normal file
10
briar-android/src/main/res/menu/image_actions.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save_image"
|
||||||
|
android:title="@string/save_image"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
</menu>
|
||||||
@@ -139,6 +139,11 @@
|
|||||||
<string name="contact_deleted_toast">Contact deleted</string>
|
<string name="contact_deleted_toast">Contact deleted</string>
|
||||||
<!-- This is shown in the action bar when opening an image in fullscreen that the user sent -->
|
<!-- This is shown in the action bar when opening an image in fullscreen that the user sent -->
|
||||||
<string name="you">You</string>
|
<string name="you">You</string>
|
||||||
|
<string name="save_image">Save image</string>
|
||||||
|
<string name="dialog_title_save_image">Save Image?</string>
|
||||||
|
<string name="dialog_message_save_image">Saving this image will allow other apps to access it.\n\nAre you sure you want to save?</string>
|
||||||
|
<string name="save_image_success">Image was saved</string>
|
||||||
|
<string name="save_image_error">Could not save image</string>
|
||||||
|
|
||||||
<!-- Adding Contacts -->
|
<!-- Adding Contacts -->
|
||||||
<string name="add_contact_title">Add a Contact</string>
|
<string name="add_contact_title">Add a Contact</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user