added media metadata support, bug fix

This commit is contained in:
nuclearfog 2023-07-15 15:21:10 +02:00
parent 5808274a1e
commit 1b9edd2a9e
No known key found for this signature in database
GPG Key ID: 03488A185C476379
27 changed files with 522 additions and 27 deletions

View File

@ -44,11 +44,13 @@ public class MastodonMedia implements Media {
private String description = ""; private String description = "";
private String blur; private String blur;
private int type = UNDEFINED; private int type = UNDEFINED;
private Meta meta;
/** /**
* @param json Mastodon status JSON format * @param json Mastodon status JSON format
*/ */
public MastodonMedia(JSONObject json) throws JSONException { public MastodonMedia(JSONObject json) throws JSONException {
JSONObject metaJson = json.optJSONObject("meta");
String typeStr = json.getString("type"); String typeStr = json.getString("type");
String url = json.getString("url"); String url = json.getString("url");
String preview = json.optString("preview_url", ""); String preview = json.optString("preview_url", "");
@ -83,6 +85,9 @@ public class MastodonMedia implements Media {
if (json.has("description") && !json.isNull("description")) { if (json.has("description") && !json.isNull("description")) {
description = json.getString("description"); description = json.getString("description");
} }
if (metaJson != null) {
meta = new MastodonMeta(metaJson);
}
} }
@ -122,6 +127,13 @@ public class MastodonMedia implements Media {
} }
@Nullable
@Override
public Meta getMeta() {
return meta;
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Media)) if (!(obj instanceof Media))
@ -154,4 +166,90 @@ public class MastodonMedia implements Media {
} }
return tostring + " url=\"" + getUrl() + "\""; return tostring + " url=\"" + getUrl() + "\"";
} }
/**
*
*/
private static final class MastodonMeta implements Meta {
private static final long serialVersionUID = 5103849502754551661L;
private double duration;
private int previewWidth;
private int previewHeight;
private int originalWidth;
private int originalHeight;
private int bitrate;
private float framerate;
/**
*
*/
public MastodonMeta(JSONObject json) {
JSONObject original = json.optJSONObject("original");
JSONObject small = json.optJSONObject("small");
if (small != null) {
previewWidth = small.optInt("width", 1);
previewHeight = small.optInt("height", 1);
}
if (original != null) {
String framerateStr = original.optString("frame_rate");
originalWidth = original.optInt("width", 1);
originalHeight = original.optInt("height", 1);
bitrate = original.optInt("bitrate", 0) / 1024;
duration = original.optDouble("duration", 0.0);
// calculate framerate if any
int split = framerateStr.indexOf("/");
if (split > 0) {
String upper = framerateStr.substring(0, split);
String down = framerateStr.substring(split + 1);
if (upper.matches("\\d+") && down.matches("\\d+") && !down.equals("0")) {
framerate = (float) (Integer.parseInt(upper) / Integer.parseInt(down));
}
}
}
}
@Override
public double getDuration() {
return duration;
}
@Override
public int getWidthPreview() {
return previewWidth;
}
@Override
public int getHeightPreview() {
return previewHeight;
}
@Override
public int getWidth() {
return originalWidth;
}
@Override
public int getHeight() {
return originalHeight;
}
@Override
public int getBitrate() {
return bitrate;
}
@Override
public float getFrameRate() {
return framerate;
}
}
} }

View File

@ -139,6 +139,13 @@ public class MediaV1 implements Media {
} }
@Nullable
@Override
public Meta getMeta() {
return null;
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Media)) if (!(obj instanceof Media))

View File

@ -145,6 +145,13 @@ public class MediaV2 implements Media {
} }
@Nullable
@Override
public Meta getMeta() {
return null;
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Media)) if (!(obj instanceof Media))

View File

@ -9,7 +9,6 @@ import androidx.annotation.WorkerThread;
import org.nuclearfog.twidda.BuildConfig; import org.nuclearfog.twidda.BuildConfig;
import java.io.InterruptedIOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -64,7 +63,7 @@ public abstract class AsyncExecutor<Parameter, Result> {
try { try {
Result result = doInBackground(parameter); Result result = doInBackground(parameter);
onPostExecute(result, callbackReference); onPostExecute(result, callbackReference);
} catch (InterruptedException | InterruptedIOException e) { } catch (InterruptedException e) {
// caused by user interaction. No need to handle exception. // caused by user interaction. No need to handle exception.
} catch (Exception e) { } catch (Exception e) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -121,7 +120,7 @@ public abstract class AsyncExecutor<Parameter, Result> {
* @return result of the background task * @return result of the background task
*/ */
@WorkerThread @WorkerThread
protected abstract Result doInBackground(@NonNull Parameter param) throws InterruptedException, InterruptedIOException; protected abstract Result doInBackground(@NonNull Parameter param) throws InterruptedException;
/** /**
* Callback used to send task result to main thread * Callback used to send task result to main thread

View File

@ -11,6 +11,15 @@ import android.util.SparseArray;
*/ */
public class BlurHashDecoder { public class BlurHashDecoder {
/**
* use in memory cache for the calculated math, reused by images with same size.
* if the cache does not exist yet it will be created and populated with new calculations.
* By default it is true.
*/
private static final boolean USE_CACHE = true;
private static final int DEFAULT_SIZE = 16;
// cache Math.cos() calculations to improve performance. // cache Math.cos() calculations to improve performance.
// The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps // The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps
// the cache is enabled by default, it is recommended to disable it only when just a few images are displayed // the cache is enabled by default, it is recommended to disable it only when just a few images are displayed
@ -36,20 +45,40 @@ public class BlurHashDecoder {
} }
/** /**
* create blurred bitmap using hash string
* *
* @param blurHash hash string
* @return blurred bitmap
*/ */
public static Bitmap decode(String blurHash) { public static Bitmap decode(String blurHash) {
return decode(blurHash, 16, 16, 1f, true); return decode(blurHash, DEFAULT_SIZE, DEFAULT_SIZE, 1f);
} }
/** /**
* Decode a blur hash into a new bitmap. * create scaled bitmap using hash string
* *
* @param useCache use in memory cache for the calculated math, reused by images with same size. * @param blurHash hash string
* if the cache does not exist yet it will be created and populated with new calculations. * @param ratio ratio ob the bitmap to generate
* By default it is true. * @return blurred bitmap
*/ */
public static Bitmap decode(String blurHash, int width, int height, float punch, boolean useCache) { public static Bitmap decode(String blurHash, float ratio) {
if (ratio > 1.0f) {
return decode(blurHash, DEFAULT_SIZE, (int) (DEFAULT_SIZE / ratio), 1f);
} else if (ratio < 1.0f && ratio > 0.0f) {
return decode(blurHash, (int) (DEFAULT_SIZE * ratio), DEFAULT_SIZE, 1f);
}
return decode(blurHash, DEFAULT_SIZE, DEFAULT_SIZE, 1f);
}
/**
* create blurred bitmap with custom size
*
* @param blurHash hash string
* @param width bitmap width
* @param height bitmap height
* @return blurred bitmap
*/
public static Bitmap decode(String blurHash, int width, int height, float punch) {
if (blurHash == null || blurHash.length() < 6) if (blurHash == null || blurHash.length() < 6)
return null; return null;
int numCompEnc = decode83(blurHash, 0, 1); int numCompEnc = decode83(blurHash, 0, 1);
@ -66,7 +95,7 @@ public class BlurHashDecoder {
int colorEnc = decode83(blurHash, from, from + 2); int colorEnc = decode83(blurHash, from, from + 2);
colors[i] = decodeAc(colorEnc, maxAc * punch); colors[i] = decodeAc(colorEnc, maxAc * punch);
} }
return composeBitmap(width, height, numCompX, numCompY, colors, useCache); return composeBitmap(width, height, numCompX, numCompY, colors);
} }
/** /**
@ -120,12 +149,12 @@ public class BlurHashDecoder {
/** /**
* *
*/ */
private static Bitmap composeBitmap(int width, int height, int numCompX, int numCompY, float[][] colors, boolean useCache) { private static Bitmap composeBitmap(int width, int height, int numCompX, int numCompY, float[][] colors) {
// use an array for better performance when writing pixel colors // use an array for better performance when writing pixel colors
int[] imageArray = new int[width * height]; int[] imageArray = new int[width * height];
boolean calculateCosX = !useCache || cacheCosinesX.get(width * numCompX) == null; boolean calculateCosX = !USE_CACHE || cacheCosinesX.get(width * numCompX) == null;
double[] cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX); double[] cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX);
boolean calculateCosY = !useCache || cacheCosinesY.get(height * numCompY) == null; boolean calculateCosY = !USE_CACHE || cacheCosinesY.get(height * numCompY) == null;
double[] cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY); double[] cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {

View File

@ -89,6 +89,13 @@ public class DatabaseMedia implements Media, MediaTable {
} }
@Nullable
@Override
public Meta getMeta() {
return null; // todo implement this
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Media)) if (!(obj instanceof Media))

View File

@ -1,5 +1,7 @@
package org.nuclearfog.twidda.model; package org.nuclearfog.twidda.model;
import androidx.annotation.Nullable;
import java.io.Serializable; import java.io.Serializable;
/** /**
@ -64,9 +66,62 @@ public interface Media extends Serializable, Comparable<Media> {
*/ */
String getBlurHash(); String getBlurHash();
/**
* @return media information
*/
@Nullable
Meta getMeta();
@Override @Override
default int compareTo(Media o) { default int compareTo(Media o) {
return String.CASE_INSENSITIVE_ORDER.compare(getKey(), o.getKey()); return String.CASE_INSENSITIVE_ORDER.compare(getKey(), o.getKey());
} }
/**
* Media information
*/
interface Meta extends Serializable {
/**
* get duration if video
*
* @return video duration in seconds
*/
double getDuration();
/**
* @return image width of the thumbnail
*/
int getWidthPreview();
/**
* @return image height of the thumbnail
*/
int getHeightPreview();
/**
* @return image/video with
*/
int getWidth();
/**
* @return image/video height
*/
int getHeight();
/**
* get audio/video if any
*
* @return bitrate in kbit/s
*/
int getBitrate();
/**
* get video framerate if any
*
* @return frame rate
*/
float getFrameRate();
}
} }

View File

@ -29,6 +29,7 @@ import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Media; import org.nuclearfog.twidda.model.Media;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog; import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback; import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback;
import org.nuclearfog.twidda.ui.dialogs.MetaDialog;
import org.nuclearfog.twidda.ui.views.AnimatedImageView; import org.nuclearfog.twidda.ui.views.AnimatedImageView;
import org.nuclearfog.twidda.ui.views.DescriptionView; import org.nuclearfog.twidda.ui.views.DescriptionView;
import org.nuclearfog.zoomview.ZoomView; import org.nuclearfog.zoomview.ZoomView;
@ -65,6 +66,7 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
private DescriptionView descriptionView; private DescriptionView descriptionView;
private DescriptionDialog descriptionDialog; private DescriptionDialog descriptionDialog;
private MetaDialog metaDialog;
@Nullable @Nullable
private Uri cacheUri; private Uri cacheUri;
@ -74,6 +76,7 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
private ImageDownloader imageAsync; private ImageDownloader imageAsync;
private GlobalSettings settings; private GlobalSettings settings;
private File cacheFolder; private File cacheFolder;
private Media.Meta meta;
@Override @Override
@ -100,6 +103,8 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
imageAsync = new ImageDownloader(this); imageAsync = new ImageDownloader(this);
descriptionDialog = new DescriptionDialog(this, this); descriptionDialog = new DescriptionDialog(this, this);
metaDialog = new MetaDialog(this);
cacheFolder = new File(getExternalCacheDir(), ImageViewer.CACHE_FOLDER); cacheFolder = new File(getExternalCacheDir(), ImageViewer.CACHE_FOLDER);
cacheFolder.mkdirs(); cacheFolder.mkdirs();
@ -109,6 +114,7 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
String description = null; String description = null;
boolean animated = false; boolean animated = false;
boolean local = false; boolean local = false;
float ratio = 1.0f;
Serializable serializedData; Serializable serializedData;
if (savedInstanceState != null) { if (savedInstanceState != null) {
serializedData = savedInstanceState.getSerializable(KEY_IMAGE_DATA); serializedData = savedInstanceState.getSerializable(KEY_IMAGE_DATA);
@ -123,10 +129,14 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
description = mediaStatus.getDescription(); description = mediaStatus.getDescription();
} else if (serializedData instanceof Media) { } else if (serializedData instanceof Media) {
Media media = (Media) serializedData; Media media = (Media) serializedData;
meta = media .getMeta();
blurHash = media.getBlurHash(); blurHash = media.getBlurHash();
imageUrl = media.getUrl(); imageUrl = media.getUrl();
description = media.getDescription(); description = media.getDescription();
animated = media.getMediaType() == Media.GIF; animated = media.getMediaType() == Media.GIF;
if (meta != null) {
ratio = meta.getWidth() / (float) meta.getHeight();
}
} else if (serializedData instanceof String) { } else if (serializedData instanceof String) {
imageUrl = (String) serializedData; imageUrl = (String) serializedData;
} else { } else {
@ -161,7 +171,7 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
} }
// set image blur placeholder // set image blur placeholder
if (blurHash != null && !blurHash.trim().isEmpty()) { if (blurHash != null && !blurHash.trim().isEmpty()) {
Bitmap blur = BlurHashDecoder.decode(blurHash); Bitmap blur = BlurHashDecoder.decode(blurHash, ratio);
zoomImage.setImageBitmap(blur); zoomImage.setImageBitmap(blur);
zoomImage.setMovable(false); zoomImage.setMovable(false);
} }
@ -201,7 +211,9 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem itemSave = menu.findItem(R.id.menu_image_save); MenuItem itemSave = menu.findItem(R.id.menu_image_save);
MenuItem itemMeta = menu.findItem(R.id.menu_image_show_meta);
itemSave.setVisible(cacheUri != null); itemSave.setVisible(cacheUri != null);
itemMeta.setVisible(meta != null);
return true; return true;
} }
@ -216,6 +228,11 @@ public class ImageViewer extends MediaActivity implements AsyncCallback<ImageLoa
} else if (item.getItemId() == R.id.menu_image_add_description) { } else if (item.getItemId() == R.id.menu_image_add_description) {
descriptionDialog.show(); descriptionDialog.show();
return true; return true;
} else if (item.getItemId() == R.id.menu_image_show_meta) {
if (meta != null) {
metaDialog.show(meta);
}
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }

View File

@ -39,7 +39,7 @@ import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.config.Configuration; import org.nuclearfog.twidda.config.Configuration;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import org.nuclearfog.twidda.ui.dialogs.ConnectionDialog; import org.nuclearfog.twidda.ui.dialogs.ConnectionDialog;
import java.io.Serializable; import java.io.Serializable;

View File

@ -51,7 +51,7 @@ import org.nuclearfog.twidda.config.Configuration;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Location; import org.nuclearfog.twidda.model.Location;
import org.nuclearfog.twidda.notification.PushSubscription; import org.nuclearfog.twidda.notification.PushSubscription;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog; import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener; import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
import org.nuclearfog.twidda.ui.dialogs.InfoDialog; import org.nuclearfog.twidda.ui.dialogs.InfoDialog;

View File

@ -46,6 +46,7 @@ import org.nuclearfog.twidda.backend.utils.LinkUtils;
import org.nuclearfog.twidda.model.Media; import org.nuclearfog.twidda.model.Media;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog; import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog;
import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback; import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback;
import org.nuclearfog.twidda.ui.dialogs.MetaDialog;
import org.nuclearfog.twidda.ui.views.DescriptionView; import org.nuclearfog.twidda.ui.views.DescriptionView;
import java.io.Serializable; import java.io.Serializable;
@ -83,6 +84,7 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener, D
private PlayerView playerView; private PlayerView playerView;
private DescriptionDialog descriptionDialog; private DescriptionDialog descriptionDialog;
private MetaDialog metaDialog;
private ExoPlayer player; private ExoPlayer player;
@Nullable @Nullable
@ -105,6 +107,7 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener, D
toolbar = findViewById(R.id.page_video_toolbar); toolbar = findViewById(R.id.page_video_toolbar);
descriptionView = findViewById(R.id.page_video_description); descriptionView = findViewById(R.id.page_video_description);
descriptionDialog = new DescriptionDialog(this, this); descriptionDialog = new DescriptionDialog(this, this);
metaDialog = new MetaDialog(this);
player = new ExoPlayer.Builder(this, this).build(); player = new ExoPlayer.Builder(this, this).build();
toolbar.setTitle(""); toolbar.setTitle("");
@ -233,9 +236,11 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener, D
getMenuInflater().inflate(R.menu.video, menu); getMenuInflater().inflate(R.menu.video, menu);
MenuItem menuOpenUrl = menu.findItem(R.id.menu_video_link); MenuItem menuOpenUrl = menu.findItem(R.id.menu_video_link);
MenuItem menuDescription = menu.findItem(R.id.menu_video_add_description); MenuItem menuDescription = menu.findItem(R.id.menu_video_add_description);
MenuItem menuMeta = menu.findItem(R.id.menu_video_show_meta);
AppStyles.setMenuIconColor(menu, Color.WHITE); AppStyles.setMenuIconColor(menu, Color.WHITE);
menuOpenUrl.setVisible(media != null); menuOpenUrl.setVisible(media != null);
menuDescription.setVisible(mediaStatus != null); menuDescription.setVisible(mediaStatus != null);
menuMeta.setVisible(media != null && media.getMeta() != null);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@ -246,9 +251,11 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener, D
if (media != null) { if (media != null) {
LinkUtils.openMediaLink(this, Uri.parse(media.getUrl())); LinkUtils.openMediaLink(this, Uri.parse(media.getUrl()));
} }
} else if (item.getItemId() == R.id.menu_video_show_meta) {
if (media != null && media.getMeta() != null) {
metaDialog.show(media.getMeta());
} }
// } else if (item.getItemId() == R.id.menu_video_add_description) {
else if (item.getItemId() == R.id.menu_video_add_description) {
descriptionDialog.show(); descriptionDialog.show();
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View File

@ -1,4 +1,4 @@
package org.nuclearfog.twidda.ui.adapter.recyclerview; package org.nuclearfog.twidda.ui.adapter.listview;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
@ -60,7 +60,7 @@ public class DropdownAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
TextView textItem; TextView textItem;
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext()); LayoutInflater inflater = LayoutInflater.from(context);
convertView = inflater.inflate(R.layout.item_dropdown, parent, false); convertView = inflater.inflate(R.layout.item_dropdown, parent, false);
textItem = convertView.findViewById(R.id.dropdown_textitem); textItem = convertView.findViewById(R.id.dropdown_textitem);
textItem.setTextColor(settings.getTextColor()); textItem.setTextColor(settings.getTextColor());

View File

@ -0,0 +1,119 @@
package org.nuclearfog.twidda.ui.adapter.listview;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.StringUtils;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Media;
import java.util.ArrayList;
import java.util.List;
/**
* ListView adapter used to show Meta items
*
* @see org.nuclearfog.twidda.ui.dialogs.MetaDialog
* @author nuclearfog
*/
public class MetaAdapter extends BaseAdapter {
private List<String> keys = new ArrayList<>();
private List<String> values = new ArrayList<>();
private Context context;
private GlobalSettings settings;
/**
*
*/
public MetaAdapter(Context context) {
this.context = context;
settings = GlobalSettings.get(context);
}
@Override
public int getCount() {
return Math.min(keys.size(), values.size());
}
@Override
public Object getItem(int position) {
return new String[] {keys.get(position), values.get(position)};
}
@Override
public long getItemId(int position) {
return keys.get(position).hashCode();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView key_text;
TextView value_text;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_meta_field, parent, false);
key_text = convertView.findViewById(R.id.item_meta_field_key);
value_text = convertView.findViewById(R.id.item_meta_field_value);
key_text.setTextColor(settings.getTextColor());
value_text.setTextColor(settings.getTextColor());
} else {
key_text = convertView.findViewById(R.id.item_meta_field_key);
value_text = convertView.findViewById(R.id.item_meta_field_value);
}
key_text.setText(keys.get(position));
value_text.setText(values.get(position));
return convertView;
}
/**
* set adapter items
*
* @param meta media meta information
*/
public void setItems(Media.Meta meta) {
if (meta.getWidth() > 1 && meta.getHeight() > 1) {
keys.add(context.getString(R.string.dialog_meta_width));
values.add(StringUtils.NUMBER_FORMAT.format(meta.getWidth()));
keys.add(context.getString(R.string.dialog_meta_height));
values.add(StringUtils.NUMBER_FORMAT.format(meta.getHeight()));
}
if (meta.getBitrate() > 0) {
keys.add(context.getString(R.string.dialog_meta_bitrate));
values.add(StringUtils.NUMBER_FORMAT.format(meta.getBitrate()) + " kbit/s");
}
if (meta.getFrameRate() > 0) {
keys.add(context.getString(R.string.dialog_meta_framerate));
values.add(StringUtils.NUMBER_FORMAT.format(meta.getFrameRate()) + " fps");
}
if (meta.getDuration() > 0) {
StringBuilder durationValue = new StringBuilder();
keys.add(context.getString(R.string.dialog_meta_duration));
long hours = Math.round(meta.getDuration() / 3600);
long mins = Math.round(meta.getDuration() / 60) % 60L;
long sec = Math.round(meta.getDuration() % 60.0);
if (hours > 0) {
if (hours < 10)
durationValue.append('0');
durationValue.append(hours).append(':');
}
if (mins < 10)
durationValue.append('0');
durationValue.append(mins).append(':');
if (sec < 10)
durationValue.append('0');
durationValue.append(sec).append('\n');
values.add(durationValue.toString());
}
notifyDataSetChanged();
}
}

View File

@ -28,7 +28,7 @@ import org.nuclearfog.twidda.backend.helper.update.FilterUpdate;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.model.Filter; import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import java.io.Serializable; import java.io.Serializable;

View File

@ -0,0 +1,68 @@
package org.nuclearfog.twidda.ui.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.ListView;
import androidx.annotation.NonNull;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.model.Media.Meta;
import org.nuclearfog.twidda.ui.adapter.listview.MetaAdapter;
/**
* Dialog to show media information
*
* @author nuclearfog
*/
public class MetaDialog extends Dialog {
private MetaAdapter adapter;
/**
*
*/
public MetaDialog(Activity activity) {
super(activity, R.style.MetaDialog);
adapter = new MetaAdapter(activity.getApplicationContext());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_meta);
ViewGroup root = findViewById(R.id.dialog_meta_root);
ListView list = findViewById(R.id.dialog_meta_list);
list.setAdapter(adapter);
AppStyles.setTheme(root);
}
@Override
public void show() {
// using show(Meta) instead
}
@Override
public void dismiss() {
if (isShowing()) {
super.dismiss();
}
}
/**
*
*/
public void show(@NonNull Meta meta) {
if (!isShowing()) {
super.show();
adapter.setItems(meta);
}
}
}

View File

@ -21,7 +21,7 @@ import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.helper.update.PollUpdate; import org.nuclearfog.twidda.backend.helper.update.PollUpdate;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.model.Instance; import org.nuclearfog.twidda.model.Instance;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import org.nuclearfog.twidda.ui.adapter.recyclerview.EditOptionsAdapter; import org.nuclearfog.twidda.ui.adapter.recyclerview.EditOptionsAdapter;
import java.io.Serializable; import java.io.Serializable;

View File

@ -22,7 +22,7 @@ import org.nuclearfog.twidda.backend.async.ReportUpdater.ReportResult;
import org.nuclearfog.twidda.backend.helper.update.ReportUpdate; import org.nuclearfog.twidda.backend.helper.update.ReportUpdate;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import java.io.Serializable; import java.io.Serializable;

View File

@ -18,7 +18,7 @@ import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Status; import org.nuclearfog.twidda.model.Status;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;

View File

@ -27,7 +27,7 @@ import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.WebPush; import org.nuclearfog.twidda.model.WebPush;
import org.nuclearfog.twidda.ui.adapter.recyclerview.DropdownAdapter; import org.nuclearfog.twidda.ui.adapter.listview.DropdownAdapter;
import java.io.Serializable; import java.io.Serializable;

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialog_meta_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.dialogs.MetaDialog">
<TextView
android:id="@+id/dialog_meta_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
android:text="@string/dialog_meta_title"
android:textSize="@dimen/dialog_meta_textsize_title"
android:layout_margin="@dimen/dialog_meta_margin_layout" />
<ListView
android:id="@+id/dialog_meta_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dialog_meta_margin_layout" />
</LinearLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/item_meta_field_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="2"
android:layout_marginEnd="@dimen/dialog_media_field_layout_margin"/>
<TextView
android:id="@+id/item_meta_field_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="top|end"
android:maxLines="2" />
</LinearLayout>

View File

@ -3,9 +3,13 @@
<item <item
android:id="@+id/menu_image_save" android:id="@+id/menu_image_save"
android:icon="@drawable/save"
android:title="@string/item_image_save" /> android:title="@string/item_image_save" />
<item
android:id="@+id/menu_image_show_meta"
android:title="@string/menu_media_show_meta"
android:visible="false" />
<item <item
android:id="@+id/menu_image_add_description" android:id="@+id/menu_image_add_description"
android:title="@string/menu_media_add_description" android:title="@string/menu_media_add_description"

View File

@ -6,6 +6,11 @@
android:title="@string/button_share" android:title="@string/button_share"
android:visible="true" /> android:visible="true" />
<item
android:id="@+id/menu_video_show_meta"
android:title="@string/menu_media_show_meta"
android:visible="false" />
<item <item
android:id="@+id/menu_video_add_description" android:id="@+id/menu_video_add_description"
android:title="@string/menu_media_add_description" android:title="@string/menu_media_add_description"

View File

@ -268,6 +268,7 @@
<string name="popup_status_text_hint">Status</string> <string name="popup_status_text_hint">Status</string>
<string name="info_account_selected">%1$s ausgewählt</string> <string name="info_account_selected">%1$s ausgewählt</string>
<string name="menu_media_add_description">Beschreibung hinzufügen</string> <string name="menu_media_add_description">Beschreibung hinzufügen</string>
<string name="menu_media_show_meta">zeige Medieninformationen</string>
<string name="menu_message">Nachricht schreiben</string> <string name="menu_message">Nachricht schreiben</string>
<string name="status_replyname_empty">Antwort</string> <string name="status_replyname_empty">Antwort</string>
<string name="status_metrics_title">Statusmetriken</string> <string name="status_metrics_title">Statusmetriken</string>
@ -334,6 +335,12 @@
<string name="dialog_report_category_label">Meldegrund</string> <string name="dialog_report_category_label">Meldegrund</string>
<string name="dialog_report_title_status">Status melden</string> <string name="dialog_report_title_status">Status melden</string>
<string name="dialog_report_apply">melden</string> <string name="dialog_report_apply">melden</string>
<string name="dialog_meta_title">Medieninformationen</string>
<string name="dialog_meta_width">Breite</string>
<string name="dialog_meta_height">Höhe</string>
<string name="dialog_meta_bitrate">Bitrate</string>
<string name="dialog_meta_framerate">Framerate</string>
<string name="dialog_meta_duration">Dauer</string>
<string name="settings_enable_push_label">aktiviere Push-Benachrichtigung\n(ntfy-App erforderlich)</string> <string name="settings_enable_push_label">aktiviere Push-Benachrichtigung\n(ntfy-App erforderlich)</string>
<string name="notification_favorite">%1$s hat einen Status favorisiert</string> <string name="notification_favorite">%1$s hat einen Status favorisiert</string>
<string name="notification_repost">%1$s hat einen Status geteilt</string> <string name="notification_repost">%1$s hat einen Status geteilt</string>

View File

@ -373,4 +373,11 @@
<dimen name="item_field_textsize_time">10sp</dimen> <dimen name="item_field_textsize_time">10sp</dimen>
<dimen name="item_field_icon_size">18sp</dimen> <dimen name="item_field_icon_size">18sp</dimen>
<!--dimens of dialog_meta.xml-->
<dimen name="dialog_meta_textsize_title">22sp</dimen>
<dimen name="dialog_meta_margin_layout">5dp</dimen>
<!--dimens of item_media_field.xml-->
<dimen name="dialog_media_field_layout_margin">5dp</dimen>
</resources> </resources>

View File

@ -192,6 +192,7 @@
<string name="menu_toolbar_request">Follow requests</string> <string name="menu_toolbar_request">Follow requests</string>
<string name="menu_toolbar_excluded_users">Blocklists</string> <string name="menu_toolbar_excluded_users">Blocklists</string>
<string name="menu_media_add_description">add description</string> <string name="menu_media_add_description">add description</string>
<string name="menu_media_show_meta">show metadata</string>
<string name="menu_message">write message</string> <string name="menu_message">write message</string>
<string name="dialog_link_image_preview">Link preview image</string> <string name="dialog_link_image_preview">Link preview image</string>
<string name="app_info_icons">svg icons from:</string> <string name="app_info_icons">svg icons from:</string>
@ -358,6 +359,12 @@
<string name="dialog_report_category_label">what to report</string> <string name="dialog_report_category_label">what to report</string>
<string name="dialog_report_title_status">report status</string> <string name="dialog_report_title_status">report status</string>
<string name="dialog_report_apply">report</string> <string name="dialog_report_apply">report</string>
<string name="dialog_meta_title">Media information</string>
<string name="dialog_meta_width">width</string>
<string name="dialog_meta_height">height</string>
<string name="dialog_meta_bitrate">bitrate</string>
<string name="dialog_meta_framerate">framerate</string>
<string name="dialog_meta_duration">duration</string>
<string name="settings_enable_push_label">enable push notification\n(requires ntfy app)</string> <string name="settings_enable_push_label">enable push notification\n(requires ntfy app)</string>
<string name="notification_favorite">%1$s favorited your status</string> <string name="notification_favorite">%1$s favorited your status</string>
<string name="notification_repost">%1$s reposted your status</string> <string name="notification_repost">%1$s reposted your status</string>

View File

@ -23,6 +23,10 @@
<item name="android:windowCloseOnTouchOutside">false</item> <item name="android:windowCloseOnTouchOutside">false</item>
</style> </style>
<style name="MetaDialog" parent="DefaultDialog">
<item name="android:windowMinWidthMinor">60%</item>
</style>
<style name="AppInfoDialog" parent="DefaultDialog"> <style name="AppInfoDialog" parent="DefaultDialog">
<item name="android:background">@android:color/white</item> <item name="android:background">@android:color/white</item>
<item name="android:textColor">@android:color/black</item> <item name="android:textColor">@android:color/black</item>