added image description support, restructured image viewer activity

This commit is contained in:
nuclearfog 2023-07-06 22:09:52 +02:00
parent 4843c6d891
commit 5f9b6292d7
No known key found for this signature in database
GPG Key ID: 03488A185C476379
26 changed files with 430 additions and 176 deletions

View File

@ -44,8 +44,8 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
MediaStatus mediaUpdate = connection.downloadImage(request.uri.toString());
InputStream input = mediaUpdate.getStream();
String mimeType = mediaUpdate.getMimeType();
if (input == null) {
return null;
if (input == null || mimeType == null) {
return new ImageLoaderResult(null, null);
}
// create file
String ext = '.' + mimeType.substring(mimeType.indexOf('/') + 1);

View File

@ -36,8 +36,8 @@ public class MessageUpdater extends AsyncExecutor<MessageUpdate, MessageUpdater.
long id = connection.showUser(update.getReceiver()).getId();
// upload media if any
long mediaId = 0L;
if (update.getMediaUpdate() != null) {
mediaId = connection.updateMedia(update.getMediaUpdate());
if (update.getMediaStatus() != null) {
mediaId = connection.updateMedia(update.getMediaStatus());
}
// upload message and media ID
connection.sendDirectmessage(id, update.getMessage(), mediaId);

View File

@ -28,7 +28,7 @@ public class MediaStatus implements Serializable, Closeable {
/**
* indicates that the media file is a photo
*/
public static final int IMAGE = 10;
public static final int PHOTO = 10;
/**
* indicates that the media file is a video
@ -45,13 +45,17 @@ public class MediaStatus implements Serializable, Closeable {
*/
public static final int GIF = 13;
@Nullable
private transient InputStream inputStream = null;
public static final int INVALID = -1;
@Nullable
private transient InputStream inputStream;
@Nullable
private String mimeType;
@Nullable
private String path;
private String description;
@Nullable
private String key;
private String description = "";
private int type;
private boolean local;
@ -60,14 +64,12 @@ public class MediaStatus implements Serializable, Closeable {
*
* @param inputStream inputstream to fetch data from internet
* @param mimeType MIME type of the media
* @throws IllegalArgumentException when the file is invalid
*/
public MediaStatus(@Nullable InputStream inputStream, String mimeType, String key) throws IllegalArgumentException {
public MediaStatus(@Nullable InputStream inputStream, @NonNull String mimeType, @NonNull String key) {
this.inputStream = inputStream;
this.mimeType = mimeType;
this.key = key;
type = getType(mimeType);
description = "";
local = false;
}
@ -151,6 +153,7 @@ public class MediaStatus implements Serializable, Closeable {
/**
* @return MIME type of the stream
*/
@Nullable
public String getMimeType() {
return mimeType;
}
@ -158,10 +161,20 @@ public class MediaStatus implements Serializable, Closeable {
/**
* @return media description if any
*/
@NonNull
public String getDescription() {
return description;
}
/**
* set media description
*
* @param description media description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return remaining bytes of the stream
*/
@ -185,7 +198,7 @@ public class MediaStatus implements Serializable, Closeable {
/**
* get type of the media file
*
* @return media type {@link #VIDEO,#AUDIO,#IMAGE,#GIF}
* @return media type {@link #VIDEO,#AUDIO,#PHOTO ,#GIF}
*/
public int getMediaType() {
return type;
@ -206,6 +219,7 @@ public class MediaStatus implements Serializable, Closeable {
*
* @return path or url
*/
@Nullable
public String getPath() {
return path;
}
@ -218,19 +232,38 @@ public class MediaStatus implements Serializable, Closeable {
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof MediaStatus))
return false;
MediaStatus mediaStatus = (MediaStatus) obj;
return mediaStatus.getMediaType() == getMediaType() && ((mediaStatus.getPath() == null && getPath() == null) || mediaStatus.getPath().equals(getPath()));
}
/**
* get media type
*
* @param mimeType mime type of the media file
* @return media type {@link #GIF,#PHOTO ,#VIDEO,#AUDIO} or {@link #INVALID} if media file is not supported
*/
private int getType(String mimeType) throws IllegalArgumentException {
if (mimeType.equals("image/gif"))
return GIF;
if (mimeType.startsWith("image/"))
return IMAGE;
return PHOTO;
if (mimeType.startsWith("video/"))
return VIDEO;
if (mimeType.startsWith("audio/"))
return AUDIO;
throw new IllegalArgumentException("wrong format!");
return INVALID;
}
/**
* get media type
*
* @param mediaType media type {@link Media#AUDIO,Media#VIDEO,#Media#GIF,Media#PHOTO}
* @return media type {@link #GIF,#PHOTO ,#VIDEO,#AUDIO} or {@link #INVALID} if media file is not supported
*/
private int getType(int mediaType) {
switch (mediaType) {
case Media.AUDIO:
@ -243,10 +276,10 @@ public class MediaStatus implements Serializable, Closeable {
return GIF;
case Media.PHOTO:
return IMAGE;
return PHOTO;
default:
return 0;
return INVALID;
}
}
}

View File

@ -28,9 +28,7 @@ public class MessageUpdate implements Serializable, Closeable {
@Nullable
private Instance instance;
@Nullable
private String mediaUri;
@Nullable
private MediaStatus mediaUpdate;
private MediaStatus mediaStatus;
private String name = "";
private String text = "";
@ -42,8 +40,8 @@ public class MessageUpdate implements Serializable, Closeable {
*/
@Override
public void close() {
if (mediaUpdate != null) {
mediaUpdate.close();
if (mediaStatus != null) {
mediaStatus.close();
}
}
@ -86,18 +84,12 @@ public class MessageUpdate implements Serializable, Closeable {
* @return input stream
*/
@Nullable
public MediaStatus getMediaUpdate() {
return mediaUpdate;
public MediaStatus getMediaStatus() {
return mediaStatus;
}
/**
*
*/
@Nullable
public Uri getMediaUri() {
if (mediaUri != null)
return Uri.parse(mediaUri);
return null;
public void setMediaUpdate(MediaStatus mediaStatus) {
this.mediaStatus = mediaStatus;
}
/**
@ -114,9 +106,8 @@ public class MessageUpdate implements Serializable, Closeable {
if (mime == null || file == null || file.length() == 0 || !supportedFormats.contains(mime)) {
return false;
}
this.mediaUri = uri.toString();
try {
mediaUpdate = new MediaStatus(context, uri, "");
mediaStatus = new MediaStatus(context, uri, "");
} catch (IllegalArgumentException exception) {
return false;
}
@ -129,7 +120,7 @@ public class MessageUpdate implements Serializable, Closeable {
* @return true if initialization succeded
*/
public boolean prepare(ContentResolver resolver) {
return mediaUpdate == null || mediaUpdate.openStream(resolver);
return mediaStatus == null || mediaStatus.openStream(resolver);
}
/**
@ -154,6 +145,6 @@ public class MessageUpdate implements Serializable, Closeable {
@NonNull
@Override
public String toString() {
return "to=\"" + name + "\" text=\"" + text + "\" " + mediaUpdate;
return "to=\"" + name + "\" text=\"" + text + "\" " + mediaStatus;
}
}

View File

@ -1,9 +1,7 @@
package org.nuclearfog.twidda.backend.helper.update;
import android.content.ContentResolver;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -127,23 +125,21 @@ public class StatusUpdate implements Serializable, Closeable {
/**
* Add file uri and check if file is valid
*
* @param mediaUri uri to a local file
* @param mediaStatus meida to add
* @return number of media attached to this holder or {@link #MEDIA_ERROR} if an error occured
*/
public int addMedia(Context context, Uri mediaUri) {
String mime = context.getContentResolver().getType(mediaUri);
if (mime == null || instance == null || !supportedFormats.contains(mime) || attachmentLimitReached) {
public int addMedia(MediaStatus mediaStatus) {
if (instance == null || mediaStatus.getMimeType() == null || !supportedFormats.contains(mediaStatus.getMimeType()) || attachmentLimitReached) {
return MEDIA_ERROR;
}
try {
MediaStatus mediaStatus = new MediaStatus(context, mediaUri, "");
switch (mediaStatus.getMediaType()) {
case MediaStatus.IMAGE:
if (mediaStatuses.isEmpty() || mediaStatuses.get(0).getMediaType() == MediaStatus.IMAGE) {
case MediaStatus.PHOTO:
if (mediaStatuses.isEmpty() || mediaStatuses.get(0).getMediaType() == MediaStatus.PHOTO) {
mediaStatuses.add(mediaStatus);
if (mediaStatuses.size() == instance.getImageLimit())
attachmentLimitReached = true;
return MediaStatus.IMAGE;
return MediaStatus.PHOTO;
}
return MEDIA_ERROR;
@ -283,6 +279,18 @@ public class StatusUpdate implements Serializable, Closeable {
return new ArrayList<>(mediaStatuses);
}
/**
* update existing media status
*
* @param mediaStatus media status to update
*/
public void updateMediaStatus(MediaStatus mediaStatus) {
int index = mediaStatuses.indexOf(mediaStatus);
if (index >= 0) {
mediaStatuses.set(index, mediaStatus);
}
}
/**
* get attached poll if any
*

View File

@ -1,6 +1,8 @@
package org.nuclearfog.twidda.ui.activities;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
@ -13,55 +15,70 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import com.wolt.blurhashkt.BlurHashDecoder;
import org.nuclearfog.twidda.BuildConfig;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.ImageDownloader;
import org.nuclearfog.twidda.backend.async.ImageDownloader.ImageLoaderParam;
import org.nuclearfog.twidda.backend.async.ImageDownloader.ImageLoaderResult;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Media;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback;
import org.nuclearfog.twidda.ui.views.AnimatedImageView;
import org.nuclearfog.twidda.ui.views.DescriptionView;
import org.nuclearfog.zoomview.ZoomView;
import java.io.File;
import java.io.Serializable;
/**
* Activity to show online and local images
*
* @author nuclearfog
*/
public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoaderResult> {
public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoaderResult>, DescriptionCallback {
public static final int IMAGE_LOCAL = 900;
public static final int GIF_LOCAL = 901;
public static final int MEDIA_LOCAL = 902;
public static final int IMAGE_ONLINE = 903;
public static final int MEDIA_ONLINE = 904;
public static final int RETURN_MEDIA_STATUS_UPDATE = 0x5895;
/**
* indicates a default image (jpg, png, etc.)
* key to set image format (image or gif)
* value type is Integer {@link #IMAGE_LOCAL,#IMAGE_ONLINE,#GIF_LOCAL,#MEDIA_LOCAL}
*/
public static final int IMAGE_DEFAULT = 1;
/**
* indicates an animated image (gif)
*/
public static final int IMAGE_GIF = 2;
public static final String TYPE = "image-type";
/**
* key to add URI of the image (online or local)
* value type is {@link Uri}
*/
public static final String LINK = "image-uri";
public static final String KEY_MEDIA_URL = "image-url";
/**
* key to set image format (image or gif)
* value type is Integer {@link #IMAGE_DEFAULT,#IMAGE_GIF}
* key to add offline media
* value type is {@link MediaStatus}
*/
public static final String TYPE = "image-type";
public static final String KEY_MEDIA_LOCAL = "media-status";
/**
* key to set image description
* value type is String
* key to add online media
* value type is {@link Media}
*/
public static final String DESCRIPTION = "image-description";
public static final String KEY_MEDIA_ONLINE = "media-online";
/**
* name of the cache folder where online images will be stored
@ -70,16 +87,19 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
private static final String CACHE_FOLDER = "imagecache";
private ZoomView zoomImage;
private AnimatedImageView gifImage;
private ProgressBar loadingCircle;
private DescriptionView descriptionView;
private DescriptionDialog descriptionDialog;
@Nullable
private Uri cacheUri;
@Nullable
private MediaStatus mediaStatus;
@Nullable
private ImageDownloader imageAsync;
private GlobalSettings settings;
private File cacheFolder;
private boolean enableSave = false;
private int mode = 0;
@ -94,10 +114,10 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
super.onCreate(savedInstanceState);
setContentView(R.layout.page_image);
Toolbar toolbar = findViewById(R.id.page_image_toolbar);
DescriptionView descriptionView = findViewById(R.id.page_image_description);
AnimatedImageView gifImage = findViewById(R.id.page_image_gif);
descriptionView = findViewById(R.id.page_image_description);
loadingCircle = findViewById(R.id.page_image_progress);
zoomImage = findViewById(R.id.page_image_viewer);
gifImage = findViewById(R.id.page_image_gif);
settings = GlobalSettings.get(this);
AppStyles.setProgressColor(loadingCircle, settings.getHighlightColor());
@ -105,45 +125,88 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
toolbar.setBackgroundColor(settings.getBackgroundColor());
setSupportActionBar(toolbar);
imageAsync = new ImageDownloader(this);
descriptionDialog = new DescriptionDialog(this, this);
cacheFolder = new File(getExternalCacheDir(), ImageViewer.CACHE_FOLDER);
cacheFolder.mkdirs();
Uri data = getIntent().getParcelableExtra(LINK);
mode = getIntent().getIntExtra(TYPE, IMAGE_DEFAULT);
String description = getIntent().getStringExtra(DESCRIPTION);
boolean isLocalFile = !data.getScheme().startsWith("http");
mode = getIntent().getIntExtra(TYPE, IMAGE_LOCAL);
switch (mode) {
case IMAGE_DEFAULT:
case IMAGE_LOCAL:
zoomImage.setVisibility(View.VISIBLE);
gifImage.setVisibility(View.INVISIBLE);
if (isLocalFile) {
zoomImage.setImageURI(data);
Uri data = getIntent().getParcelableExtra(KEY_MEDIA_URL);
zoomImage.setImageURI(data);
break;
case IMAGE_ONLINE:
zoomImage.setVisibility(View.VISIBLE);
gifImage.setVisibility(View.INVISIBLE);
loadingCircle.setVisibility(View.VISIBLE);
data = getIntent().getParcelableExtra(KEY_MEDIA_URL);
ImageLoaderParam request = new ImageLoaderParam(data, cacheFolder);
imageAsync.execute(request, this);
break;
case GIF_LOCAL:
zoomImage.setVisibility(View.INVISIBLE);
gifImage.setVisibility(View.VISIBLE);
data = getIntent().getParcelableExtra(KEY_MEDIA_URL);
gifImage.setImageURI(data);
break;
case MEDIA_LOCAL:
Serializable serializedData = getIntent().getSerializableExtra(KEY_MEDIA_LOCAL);
if (serializedData instanceof MediaStatus) {
mediaStatus = (MediaStatus) serializedData;
if (!mediaStatus.getDescription().trim().isEmpty()) {
descriptionView.setVisibility(View.VISIBLE);
descriptionView.setDescription(mediaStatus.getDescription());
}
if (mediaStatus.getMediaType() == MediaStatus.PHOTO) {
zoomImage.setVisibility(View.VISIBLE);
gifImage.setVisibility(View.INVISIBLE);
zoomImage.setImageURI(Uri.parse(mediaStatus.getPath()));
} else if (mediaStatus.getMediaType() == MediaStatus.GIF) {
zoomImage.setVisibility(View.INVISIBLE);
gifImage.setVisibility(View.VISIBLE);
gifImage.setImageURI(Uri.parse(mediaStatus.getPath()));
}
}
break;
case IMAGE_GIF:
zoomImage.setVisibility(View.INVISIBLE);
gifImage.setVisibility(View.VISIBLE);
if (isLocalFile) {
gifImage.setImageURI(data);
case MEDIA_ONLINE:
serializedData = getIntent().getSerializableExtra(KEY_MEDIA_ONLINE);
if (serializedData instanceof Media) {
Media media = (Media) serializedData;
if (!media.getBlurHash().isEmpty()) {
Bitmap blur = BlurHashDecoder.INSTANCE.decode(media.getBlurHash(), 16, 16, 1f, true);
zoomImage.setImageBitmap(blur);
}
if (!media.getDescription().isEmpty()) {
descriptionView.setVisibility(View.VISIBLE);
descriptionView.setDescription(media.getDescription());
}
if (media.getMediaType() == Media.PHOTO) {
zoomImage.setVisibility(View.VISIBLE);
gifImage.setVisibility(View.INVISIBLE);
request = new ImageLoaderParam(Uri.parse(media.getUrl()), cacheFolder);
imageAsync.execute(request, this);
}
}
break;
}
if (!isLocalFile) {
ImageLoaderParam request = new ImageLoaderParam(data, cacheFolder);
imageAsync = new ImageDownloader(this);
imageAsync.execute(request, this);
enableSave = true;
} else {
loadingCircle.setVisibility(View.INVISIBLE);
toolbar.setVisibility(View.GONE);
}
if (description != null && !description.trim().isEmpty()) {
descriptionView.setDescription(description);
} else {
descriptionView.setVisibility(View.INVISIBLE);
}
@Override
public void onBackPressed() {
if (mediaStatus != null) {
Intent intent = new Intent();
intent.putExtra(KEY_MEDIA_LOCAL, mediaStatus);
setResult(RETURN_MEDIA_STATUS_UPDATE, intent);
}
super.onBackPressed();
}
@ -159,8 +222,11 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.image, menu);
MenuItem itemSave = menu.findItem(R.id.menu_image_save);
MenuItem itemDescription = menu.findItem(R.id.menu_image_add_description);
AppStyles.setMenuIconColor(menu, settings.getIconColor());
menu.findItem(R.id.menu_image_save).setVisible(enableSave);
itemSave.setVisible(mode == IMAGE_ONLINE || mode == MEDIA_ONLINE);
itemDescription.setVisible(mediaStatus != null);
return true;
}
@ -173,6 +239,9 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
return true;
}
}
else if (item.getItemId() == R.id.menu_image_add_description) {
descriptionDialog.show();
}
return super.onOptionsItemSelected(item);
}
@ -192,22 +261,32 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
if (result.uri != null) {
loadingCircle.setVisibility(View.INVISIBLE);
cacheUri = result.uri;
switch (mode) {
case IMAGE_DEFAULT:
zoomImage.reset();
zoomImage.setImageURI(cacheUri);
break;
case IMAGE_GIF:
gifImage.setImageURI(cacheUri);
break;
}
zoomImage.reset();
zoomImage.setImageURI(cacheUri);
} else {
ErrorUtils.showErrorMessage(getApplicationContext(), result.exception);
finish();
}
}
@Override
public void onDescriptionSet(String description) {
if (description != null && !description.trim().isEmpty()) {
descriptionView.setDescription(description);
descriptionView.setVisibility(View.VISIBLE);
if (mediaStatus != null) {
mediaStatus.setDescription(description);
}
} else {
descriptionView.setDescription("");
descriptionView.setVisibility(View.INVISIBLE);
if (mediaStatus != null) {
mediaStatus.setDescription("");
}
}
}
/**
* clear the image cache
*/

View File

@ -179,7 +179,7 @@ public abstract class MediaActivity extends AppCompatActivity implements Activit
@Override
public final void onActivityResult(ActivityResult result) {
public void onActivityResult(ActivityResult result) {
Intent intent = result.getData();
if (result.getResultCode() == RESULT_OK && intent != null && intent.getData() != null) {
onMediaFetched(requestCode, intent.getData());

View File

@ -128,7 +128,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
@Override
public void onBackPressed() {
if (receiver.getText().length() == 0 && message.getText().length() == 0 && messageUpdate.getMediaUri() == null) {
if (receiver.getText().length() == 0 && message.getText().length() == 0 && messageUpdate.getMediaStatus() == null) {
loadingCircle.dismiss();
super.onBackPressed();
} else {
@ -178,10 +178,10 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
}
// open media
else if (v.getId() == R.id.popup_message_preview) {
if (messageUpdate.getMediaUri() != null) {
if (messageUpdate.getMediaStatus() != null) {
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, messageUpdate.getMediaUri());
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_LOCAL, messageUpdate.getMediaStatus());
intent.putExtra(ImageViewer.TYPE, ImageViewer.MEDIA_LOCAL);
startActivity(intent);
}
}
@ -212,7 +212,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
private void sendMessage() {
String username = receiver.getText().toString();
String message = this.message.getText().toString();
if (!username.trim().isEmpty() && (!message.trim().isEmpty() || messageUpdate.getMediaUri() != null)) {
if (!username.trim().isEmpty() && (!message.trim().isEmpty() || messageUpdate.getMediaStatus() != null)) {
if (messageUpdate.prepare(getContentResolver())) {
messageUpdate.setReceiver(username);
messageUpdate.setText(message);

View File

@ -583,8 +583,8 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
else if (v.getId() == R.id.profile_img) {
if (!user.getOriginalProfileImageUrl().isEmpty()) {
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(user.getOriginalProfileImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_URL, Uri.parse(user.getOriginalProfileImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_ONLINE);
startActivity(intent);
}
}
@ -592,8 +592,8 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
else if (v.getId() == R.id.profile_banner) {
if (!user.getOriginalBannerImageUrl().isEmpty()) {
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(user.getOriginalBannerImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_URL, Uri.parse(user.getOriginalBannerImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_ONLINE);
startActivity(intent);
}
}

View File

@ -553,10 +553,12 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
@Override
public void onActivityResult(ActivityResult result) {
if (result.getData() != null && result.getResultCode() == StatusEditor.RETURN_STATUS_UPDATE) {
Serializable data = result.getData().getSerializableExtra(StatusEditor.KEY_DATA);
if (data instanceof Status) {
setStatus((Status) data);
if (result.getData() != null) {
if (result.getResultCode() == StatusEditor.RETURN_STATUS_UPDATE) {
Serializable data = result.getData().getSerializableExtra(StatusEditor.KEY_DATA);
if (data instanceof Status) {
setStatus((Status) data);
}
}
}
}
@ -726,8 +728,8 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
String imageUrl = card.getImageUrl();
if (!imageUrl.isEmpty()) {
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(card.getImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_URL, Uri.parse(card.getImageUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_ONLINE);
startActivity(intent);
}
}
@ -740,9 +742,8 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
switch (media.getMediaType()) {
case Media.PHOTO:
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, uri);
intent.putExtra(ImageViewer.DESCRIPTION, media.getDescription());
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_ONLINE, media);
intent.putExtra(ImageViewer.TYPE, ImageViewer.MEDIA_ONLINE);
startActivity(intent);
break;

View File

@ -14,6 +14,10 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -54,7 +58,7 @@ import java.io.Serializable;
*
* @author nuclearfog
*/
public class StatusEditor extends MediaActivity implements OnClickListener, OnProgressStopListener, OnConfirmListener,
public class StatusEditor extends MediaActivity implements ActivityResultCallback<ActivityResult>, OnClickListener, OnProgressStopListener, OnConfirmListener,
OnMediaClickListener, TextWatcher, PollUpdateCallback, OnEmojiSelectListener {
/**
@ -86,6 +90,7 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
*/
private static final String KEY_SAVE = "status_update";
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this);
private AsyncCallback<StatusUpdateResult> statusUpdateResult = this::onStatusUpdated;
private AsyncCallback<Instance> instanceResult = this::onInstanceResult;
@ -241,6 +246,20 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
}
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == ImageViewer.RETURN_MEDIA_STATUS_UPDATE && result.getData() != null) {
Serializable data = result.getData().getSerializableExtra(ImageViewer.KEY_MEDIA_LOCAL);
if (data instanceof MediaStatus) {
MediaStatus mediaStatus = (MediaStatus) data;
statusUpdate.updateMediaStatus(mediaStatus);
}
} else {
super.onActivityResult(result);
}
}
@Override
public void onClick(View v) {
// send status
@ -337,7 +356,8 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
@Override
protected void onMediaFetched(int resultType, @NonNull Uri uri) {
int mediaType = statusUpdate.addMedia(this, uri);
MediaStatus mediaStatus = new MediaStatus(getApplicationContext(), uri, "");
int mediaType = statusUpdate.addMedia(mediaStatus);
addMedia(mediaType);
}
@ -366,19 +386,15 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
if (statusUpdate.getMediaStatuses().isEmpty())
return;
MediaStatus media = statusUpdate.getMediaStatuses().get(index);
if (media.getPath() == null)
return;
switch (media.getMediaType()) {
case MediaStatus.IMAGE:
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(media.getPath()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
startActivity(intent);
break;
case MediaStatus.PHOTO:
case MediaStatus.GIF:
intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(media.getPath()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_GIF);
startActivity(intent);
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(ImageViewer.KEY_MEDIA_LOCAL, media);
intent.putExtra(ImageViewer.TYPE, ImageViewer.MEDIA_LOCAL);
activityResultLauncher.launch(intent);
break;
case MediaStatus.VIDEO:
@ -422,7 +438,7 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
private void addMedia(int mediaType) {
switch (mediaType) {
case Media.PHOTO:
case MediaStatus.IMAGE:
case MediaStatus.PHOTO:
adapter.addImageItem();
break;

View File

@ -0,0 +1,93 @@
package org.nuclearfog.twidda.ui.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
public class DescriptionDialog extends Dialog implements OnClickListener {
private static final String KEY_SAVE = " description-save";
private DescriptionCallback callback;
private EditText descriptionEdit;
public DescriptionDialog(Context context, DescriptionCallback callback) {
super(context, R.style.DefaultDialog);
this.callback = callback;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_description);
ViewGroup root = findViewById(R.id.dialog_description_root);
View applyButton = findViewById(R.id.dialog_description_apply);
descriptionEdit = findViewById(R.id.dialog_description_input);
AppStyles.setTheme(root);
applyButton.setOnClickListener(this);
}
@NonNull
@Override
public Bundle onSaveInstanceState() {
String description = descriptionEdit.getText().toString();
Bundle bundle = super.onSaveInstanceState();
bundle.putString(KEY_SAVE, description);
return bundle;
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
String description = savedInstanceState.getString(KEY_SAVE, "");
descriptionEdit.setText(description);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
public void show() {
if (!isShowing()) {
super.show();
}
}
@Override
public void dismiss() {
if (isShowing()) {
super.dismiss();
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_description_apply) {
String description = descriptionEdit.getText().toString();
callback.onDescriptionSet(description);
dismiss();
}
}
public interface DescriptionCallback {
void onDescriptionSet(String description);
}
}

View File

@ -51,7 +51,7 @@ public class FilterDialog extends Dialog implements OnClickListener, OnCheckedCh
*
*/
public FilterDialog(Activity activity, FilterDialogCallback callback) {
super(activity, R.style.FilterDialog);
super(activity, R.style.DefaultDialog);
this.callback = callback;
update = new FilterUpdate();
filterAction = new StatusFilterAction(activity);

View File

@ -34,7 +34,7 @@ public class MetricsDialog extends Dialog {
*
*/
public MetricsDialog(Activity activity) {
super(activity, R.style.MetricsDialog);
super(activity, R.style.DefaultDialog);
}

View File

@ -52,7 +52,7 @@ public class PollDialog extends Dialog implements OnClickListener {
*
*/
public PollDialog(Activity activity, PollUpdateCallback callback) {
super(activity, R.style.PollDialog);
super(activity, R.style.DefaultDialog);
this.callback = callback;
optionAdapter = new EditOptionsAdapter();
timeUnitAdapter = new DropdownAdapter(activity.getApplicationContext());

View File

@ -49,7 +49,7 @@ public class ReportDialog extends Dialog implements OnClickListener, AsyncCallba
*
*/
public ReportDialog(Activity activity) {
super(activity, R.style.ReportDialog);
super(activity, R.style.DefaultDialog);
adapter = new DropdownAdapter(activity.getApplicationContext());
reportUpdater = new ReportUpdater(activity.getApplicationContext());
adapter.setItems(R.array.reports);

View File

@ -41,7 +41,7 @@ public class StatusPreferenceDialog extends Dialog implements OnCheckedChangeLis
* @param statusUpdate status information from status editor
*/
public StatusPreferenceDialog(Activity activity, StatusUpdate statusUpdate) {
super(activity, R.style.StatusDialog);
super(activity, R.style.DefaultDialog);
this.statusUpdate = statusUpdate;
visibility_adapter = new DropdownAdapter(activity.getApplicationContext());
language_adapter = new DropdownAdapter(activity.getApplicationContext());

View File

@ -53,7 +53,7 @@ public class WebPushDialog extends Dialog implements OnCheckedChangeListener, On
*
*/
public WebPushDialog(Activity activity) {
super(activity, R.style.WebPushDialog);
super(activity, R.style.DefaultDialog);
adapter = new DropdownAdapter(activity.getApplicationContext());
settings = GlobalSettings.get(getContext());
pushUpdater = new PushUpdater(getContext());

View File

@ -142,8 +142,8 @@ public class MessageFragment extends ListFragment implements OnMessageClickListe
int mediaIndex = extras[0];
if (mediaIndex >= 0 && mediaIndex < message.getMedia().length) {
Intent intent = new Intent(requireContext(), ImageViewer.class);
intent.putExtra(ImageViewer.LINK, Uri.parse(message.getMedia()[mediaIndex].getUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_DEFAULT);
intent.putExtra(ImageViewer.KEY_MEDIA_URL, Uri.parse(message.getMedia()[mediaIndex].getUrl()));
intent.putExtra(ImageViewer.TYPE, ImageViewer.IMAGE_ONLINE);
startActivity(intent);
}
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_description_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_dexcription_title"
android:lines="1"
android:textSize="@dimen/dialog_description_textsize_title"
android:layout_margin="@dimen/dialog_description_layout_margin"/>
<EditText
android:id="@+id/dialog_description_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:lines="4"
android:gravity="top"
android:layout_margin="@dimen/dialog_description_layout_margin"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:scrollbarStyle="outsideInset" />
<Button
android:id="@+id/dialog_description_apply"
android:layout_width="wrap_content"
android:layout_height="@dimen/dialog_push_button_height"
android:padding="@dimen/dialog_description_button_padding"
android:text="@string/dialog_apply"
android:layout_margin="@dimen/dialog_description_layout_margin"
android:lines="1"
android:layout_gravity="end"
style="@style/FeedbackButton" />
</LinearLayout>

View File

@ -43,6 +43,7 @@
android:layout_width="@dimen/mediapage_circle_size"
android:layout_height="@dimen/mediapage_circle_size"
android:layout_marginTop="@dimen/mediapage_preview_margin"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -5,6 +5,10 @@
<item
android:id="@+id/menu_image_save"
android:icon="@drawable/save"
android:title="@string/item_image_save"
app:showAsAction="always" />
android:title="@string/item_image_save" />
<item
android:id="@+id/menu_image_add_description"
android:title="@string/menu_media_add_description"
android:visible="false"/>
</menu>

View File

@ -4,8 +4,11 @@
<item
android:id="@+id/menu_video_link"
android:icon="@drawable/share"
android:title="@string/button_share"
android:visible="true"
app:showAsAction="always" />
android:visible="true"/>
<item
android:id="@+id/menu_video_add_description"
android:title="@string/menu_media_add_description"
android:visible="false"/>
</menu>

View File

@ -358,4 +358,9 @@
<dimen name="dialog_report_button_padding">5dp</dimen>
<dimen name="dialog_report_button_height">24sp</dimen>
<!--dimens of dialog_description.xml-->
<dimen name="dialog_description_textsize_title">22sp</dimen>
<dimen name="dialog_description_button_padding">5dp</dimen>
<dimen name="dialog_description_layout_margin">5dp</dimen>
</resources>

View File

@ -191,6 +191,7 @@
<string name="menu_status_copy_link">Status link</string>
<string name="menu_toolbar_request">Follow requests</string>
<string name="menu_toolbar_excluded_users">Blocklists</string>
<string name="menu_media_add_description">add description</string>
<string name="menu_message">write message</string>
<string name="dialog_link_image_preview">Link preview image</string>
<string name="app_info_icons">svg icons from:</string>
@ -230,6 +231,7 @@
<string name="status_sent_from">"sent from: "</string>
<string name="username">Username</string>
<string name="dm_message">Message</string>
<string name="dialog_dexcription_title">add description</string>
<string name="confirm_cancel_message">discard message?</string>
<string name="settings_logout">log out</string>
<string name="dm_answer">answer</string>

View File

@ -18,6 +18,11 @@
<item name="android:backgroundDimEnabled">true</item>
</style>
<style name="DefaultDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">90%</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="AppInfoDialog" parent="Theme.AppCompat.Dialog">
<item name="android:background">@android:color/white</item>
<item name="android:textColor">@android:color/black</item>
@ -50,20 +55,6 @@
<item name="android:backgroundDimEnabled">true</item>
</style>
<style name="MetricsDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">80%</item>
</style>
<style name="PollDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">80%</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="WebPushDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">80%</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="AudioDialog" parent="Theme.AppCompat.Dialog">
<item name="android:background">@color/player_control</item>
<item name="android:windowMinWidthMinor">90%</item>
@ -71,20 +62,6 @@
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="StatusDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">80%</item>
</style>
<style name="FilterDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">90%</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="ReportDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowMinWidthMinor">90%</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
<style name="EmojiPickerDialog" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowContentOverlay">@null</item>