replaced direct media access, added image caching, bug fix

This commit is contained in:
nuclearfog 2022-01-13 20:51:31 +01:00
parent b27d77b937
commit 33260ae84e
No known key found for this signature in database
GPG Key ID: AA0271FBE406DB98
20 changed files with 530 additions and 403 deletions

View File

@ -21,8 +21,6 @@ import android.content.ActivityNotFoundException;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
@ -31,7 +29,6 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.provider.MediaStore;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -44,6 +41,7 @@ import org.nuclearfog.twidda.backend.ImageSaver;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
/** /**
@ -72,11 +70,6 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
*/ */
private static final String[] TYPE_ALL = {MIME_IMAGE_READ, MIME_VIDEO_READ}; private static final String[] TYPE_ALL = {MIME_IMAGE_READ, MIME_VIDEO_READ};
/**
* Cursor mode to get the full path to the image
*/
private static final String[] GET_MEDIA = {MediaStore.MediaColumns.DATA};
/** /**
* request code to check permissions * request code to check permissions
*/ */
@ -109,9 +102,9 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
@Nullable @Nullable
private ImageSaver imageTask; private ImageSaver imageTask;
private Bitmap image;
private String filename;
private boolean locationPending = false; private boolean locationPending = false;
private Uri selectedImage;
private String imageName;
@Override @Override
@ -153,17 +146,7 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
protected final void onActivityResult(int reqCode, int returnCode, @Nullable Intent intent) { protected final void onActivityResult(int reqCode, int returnCode, @Nullable Intent intent) {
super.onActivityResult(reqCode, returnCode, intent); super.onActivityResult(reqCode, returnCode, intent);
if (returnCode == RESULT_OK && intent != null && intent.getData() != null) { if (returnCode == RESULT_OK && intent != null && intent.getData() != null) {
Cursor cursor = getContentResolver().query(intent.getData(), GET_MEDIA, null, null, null); onMediaFetched(reqCode, intent.getData());
if (cursor != null) {
if (cursor.moveToFirst()) {
String path = cursor.getString(0);
if (path != null) {
onMediaFetched(reqCode, path);
// todo add error handling if no media returned
}
}
cursor.close();
}
} }
} }
@ -200,22 +183,23 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// store images directly // store images directly
File imageFolder = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES); File imageFolder = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES);
File imageFile = new File(imageFolder, filename); File imageFile = new File(imageFolder, imageName);
OutputStream fileStream = new FileOutputStream(imageFile); InputStream src = getContentResolver().openInputStream(selectedImage);
OutputStream dest = new FileOutputStream(imageFile);
imageTask = new ImageSaver(this); imageTask = new ImageSaver(this);
imageTask.execute(image, fileStream); imageTask.execute(src, dest);
} else { } else {
// use scoped storage // use scoped storage
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(DISPLAY_NAME, filename); values.put(DISPLAY_NAME, imageName);
values.put(DATE_TAKEN, System.currentTimeMillis()); values.put(DATE_TAKEN, System.currentTimeMillis());
values.put(RELATIVE_PATH, DIRECTORY_PICTURES); values.put(RELATIVE_PATH, DIRECTORY_PICTURES);
values.put(MIME_TYPE, MIME_IMAGE_WRITE); values.put(MIME_TYPE, MIME_IMAGE_WRITE);
Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values); Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
if (imageUri != null) { if (imageUri != null) {
OutputStream fileStream = getContentResolver().openOutputStream(imageUri); OutputStream dest = getContentResolver().openOutputStream(imageUri);
imageTask = new ImageSaver(this); imageTask = new ImageSaver(this);
imageTask.execute(image, fileStream); imageTask.execute(selectedImage, dest);
} }
} }
} }
@ -231,8 +215,7 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
Toast.makeText(this, R.string.info_image_saved, LENGTH_SHORT).show(); Toast.makeText(this, R.string.info_image_saved, LENGTH_SHORT).show();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// start media scanner to scan for new image // start media scanner to scan for new image
String[] fileName = {filename}; MediaScannerConnection.scanFile(this, new String[]{imageName}, null, null);
MediaScannerConnection.scanFile(this, fileName, null, null);
} }
} }
@ -283,15 +266,11 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
/** /**
* store image bitmap into storage * store image bitmap into storage
* *
* @param filename name of the file * @param uri Uri of the image
* @param image image bitmap to store
*/ */
protected void storeImage(Bitmap image, String filename) { protected void storeImage(Uri uri) {
this.image = image; selectedImage = uri;
this.filename = filename; imageName = "shitter_" + uri.getLastPathSegment() + "jpg";
if (!filename.endsWith(".jpg")) {
this.filename += ".jpg";
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|| checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) { || checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) {
saveImage(); saveImage();
@ -352,9 +331,8 @@ public abstract class MediaActivity extends AppCompatActivity implements Locatio
/** /**
* called when a media file path was successfully fetched * called when a media file path was successfully fetched
* * @param resultType type of media call
* @param resultType type of media call * @param uri Uri of the file
* @param path local path to the media file
*/ */
protected abstract void onMediaFetched(int resultType, @NonNull String path); protected abstract void onMediaFetched(int resultType, @NonNull Uri uri);
} }

View File

@ -3,21 +3,14 @@ package org.nuclearfog.twidda.activities;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.location.Location; import android.location.Location;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -37,26 +30,21 @@ import org.nuclearfog.twidda.adapter.ImageAdapter;
import org.nuclearfog.twidda.adapter.ImageAdapter.OnImageClickListener; import org.nuclearfog.twidda.adapter.ImageAdapter.OnImageClickListener;
import org.nuclearfog.twidda.backend.ImageLoader; import org.nuclearfog.twidda.backend.ImageLoader;
import org.nuclearfog.twidda.backend.SeekbarUpdater; import org.nuclearfog.twidda.backend.SeekbarUpdater;
import org.nuclearfog.twidda.backend.holder.ImageHolder;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.backend.utils.StringTools; import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.zoomview.ZoomView; import org.nuclearfog.zoomview.ZoomView;
import static android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN; import static android.media.MediaPlayer.*;
import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_END; import static android.os.AsyncTask.Status.*;
import static android.media.MediaPlayer.MEDIA_INFO_BUFFERING_START; import static android.view.MotionEvent.*;
import static android.media.MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START; import static android.view.View.*;
import static android.os.AsyncTask.Status.RUNNING; import static android.widget.Toast.*;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL; import static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
import java.io.File;
/** /**
* Media viewer activity for images and videos * Media viewer activity for images and videos
* *
@ -66,15 +54,25 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
OnPreparedListener, OnInfoListener, OnErrorListener, OnClickListener, OnTouchListener { OnPreparedListener, OnInfoListener, OnErrorListener, OnClickListener, OnTouchListener {
/** /**
* Key for the media URL, local or online, required * Key for online media files
*/ */
public static final String KEY_MEDIA_LINK = "media_link"; public static final String KEY_MEDIA_LINK = "media_link";
/**
* key for local media files
*/
public static final String KEY_MEDIA_URI = "media_uri";
/** /**
* Key for the media type, required * Key for the media type, required
* {@link #MEDIAVIEWER_IMAGE}, {@link #MEDIAVIEWER_VIDEO} or {@link #MEDIAVIEWER_ANGIF} * {@link #MEDIAVIEWER_IMAGE}, {@link #MEDIAVIEWER_VIDEO} or {@link #MEDIAVIEWER_ANGIF}
*/ */
public static final String KEY_MEDIA_TYPE = "media_type"; public static final String KEY_MEDIA_TYPE = "media_type";
/**
* cache folder name
*/
public static final String CACHE_FOLDER = "imagecache";
/** /**
* setup media viewer for images from twitter * setup media viewer for images from twitter
*/ */
@ -124,11 +122,12 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
private ZoomView zoomImage; private ZoomView zoomImage;
private ViewGroup controlPanel; private ViewGroup controlPanel;
private String[] mediaLinks; private Uri[] mediaLinks;
private int type; private int type;
private PlayStat playStat = PlayStat.IDLE; private PlayStat playStat = PlayStat.IDLE;
@Override @Override
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
super.attachBaseContext(AppStyles.setFontScale(newBase)); super.attachBaseContext(AppStyles.setFontScale(newBase));
@ -163,43 +162,55 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
GlobalSettings settings = GlobalSettings.getInstance(this); GlobalSettings settings = GlobalSettings.getInstance(this);
AppStyles.setProgressColor(loadingCircle, settings.getHighlightColor()); AppStyles.setProgressColor(loadingCircle, settings.getHighlightColor());
AppStyles.setTheme(controlPanel, settings.getBackgroundColor()); AppStyles.setTheme(controlPanel, settings.getBackgroundColor());
adapter = new ImageAdapter(settings, this); adapter = new ImageAdapter(getApplicationContext(), this);
// check if link is a local file
Parcelable[] links = getIntent().getParcelableArrayExtra(KEY_MEDIA_URI);
if (links != null) {
mediaLinks = new Uri[links.length];
for (int i = 0; i < mediaLinks.length ; i++) {
mediaLinks[i] = (Uri) links[i];
}
}
// check if links are http:// links
else {
String[] strLinks = getIntent().getStringArrayExtra(KEY_MEDIA_LINK);
mediaLinks = new Uri[strLinks.length];
for (int i = 0; i < mediaLinks.length ; i++) {
mediaLinks[i] = Uri.parse(strLinks[i]);
}
}
// get intent data and type
mediaLinks = getIntent().getStringArrayExtra(KEY_MEDIA_LINK);
type = getIntent().getIntExtra(KEY_MEDIA_TYPE, 0); type = getIntent().getIntExtra(KEY_MEDIA_TYPE, 0);
switch (type) {
if (mediaLinks != null && mediaLinks.length > 0) { case MEDIAVIEWER_IMAGE:
switch (type) { zoomImage.setVisibility(VISIBLE);
case MEDIAVIEWER_IMAGE: imageListContainer.setVisibility(VISIBLE);
zoomImage.setVisibility(VISIBLE); if (!mediaLinks[0].getScheme().startsWith("http")) {
imageListContainer.setVisibility(VISIBLE); adapter.disableSaveButton();
if (!mediaLinks[0].startsWith("http")) for (Uri uri : mediaLinks)
adapter.disableSaveButton(); setImage(uri);
imageList.setLayoutManager(new LinearLayoutManager(this, HORIZONTAL, false)); adapter.disableLoading();
imageList.setAdapter(adapter); } else {
imageAsync = new ImageLoader(this); imageAsync = new ImageLoader(this);
imageAsync.execute(mediaLinks); imageAsync.execute(mediaLinks);
break; }
imageList.setLayoutManager(new LinearLayoutManager(this, HORIZONTAL, false));
imageList.setAdapter(adapter);
break;
case MEDIAVIEWER_VIDEO: case MEDIAVIEWER_VIDEO:
controlPanel.setVisibility(VISIBLE); controlPanel.setVisibility(VISIBLE);
if (mediaLinks[0].startsWith("/")) if (mediaLinks[0].getScheme().startsWith("http"))
share.setVisibility(GONE); // local image share.setVisibility(GONE); // local image
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && mediaLinks[0].startsWith("https://")) { seekUpdate = new SeekbarUpdater(this, PROGRESS_UPDATE);
// for any reason VideoView ignores TLS 1.2 setup, so we have to use http:// instead // fall through
// todo find a solution to add TLS 1.2 support for pre lollipop devices case MEDIAVIEWER_ANGIF:
mediaLinks[0] = "http://" + mediaLinks[0].substring(8); videoView.setVisibility(VISIBLE);
} videoView.setZOrderMediaOverlay(true); // disable black background
seekUpdate = new SeekbarUpdater(this, PROGRESS_UPDATE); videoView.getHolder().setFormat(PixelFormat.TRANSPARENT);
// fall through videoView.setVideoURI(mediaLinks[0]);
case MEDIAVIEWER_ANGIF: break;
videoView.setVisibility(VISIBLE);
videoView.setZOrderMediaOverlay(true); // disable black background
videoView.getHolder().setFormat(PixelFormat.TRANSPARENT);
videoView.setVideoURI(Uri.parse(mediaLinks[0]));
break;
}
} }
share.setOnClickListener(this); share.setOnClickListener(this);
play.setOnClickListener(this); play.setOnClickListener(this);
@ -231,6 +242,7 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
imageAsync.cancel(true); imageAsync.cancel(true);
if (seekUpdate != null) if (seekUpdate != null)
seekUpdate.shutdown(); seekUpdate.shutdown();
clearCache();
super.onDestroy(); super.onDestroy();
} }
@ -254,7 +266,7 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
// open link with another app // open link with another app
else if (v.getId() == R.id.controller_share) { else if (v.getId() == R.id.controller_share) {
if (mediaLinks != null && mediaLinks.length > 0) { if (mediaLinks != null && mediaLinks.length > 0) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mediaLinks[0])); Intent intent = new Intent(Intent.ACTION_VIEW, mediaLinks[0]);
try { try {
startActivity(intent); startActivity(intent);
} catch (ActivityNotFoundException err) { } catch (ActivityNotFoundException err) {
@ -320,24 +332,20 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
@Override @Override
protected void onMediaFetched(int resultType, @NonNull String path) { protected void onMediaFetched(int resultType, @NonNull Uri uri) {
} }
@Override @Override
public void onImageClick(Bitmap image) { public void onImageClick(Uri uri) {
zoomImage.reset(); zoomImage.reset();
zoomImage.setImageBitmap(image); zoomImage.setImageURI(uri);
} }
@Override @Override
public void onImageSave(Bitmap image, int pos) { public void onImageSave(Uri uri) {
if (mediaLinks != null && pos < mediaLinks.length && mediaLinks[pos] != null) { storeImage(uri);
String link = mediaLinks[pos];
String name = "shitter_" + link.substring(link.lastIndexOf('/') + 1);
storeImage(image, name);
}
} }
@ -439,15 +447,15 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
/** /**
* set downloaded image into preview list * set downloaded image into preview list
* *
* @param image Image container * @param imageUri Image Uri
*/ */
public void setImage(ImageHolder image) { public void setImage(Uri imageUri) {
if (adapter.isEmpty()) { if (adapter.isEmpty()) {
zoomImage.reset(); zoomImage.reset();
zoomImage.setImageBitmap(image.reducedImage); zoomImage.setImageURI(imageUri);
loadingCircle.setVisibility(INVISIBLE); loadingCircle.setVisibility(INVISIBLE);
} }
adapter.addLast(image); adapter.addLast(imageUri);
} }
/** /**
@ -490,4 +498,15 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
break; break;
} }
} }
/**
* clear the image cache
*/
private void clearCache() {
File cacheFolder = new File(getExternalCacheDir(), CACHE_FOLDER);
File[] files = cacheFolder.listFiles();
for (File file : files) {
file.delete();
}
}
} }

View File

@ -1,20 +1,17 @@
package org.nuclearfog.twidda.activities; package org.nuclearfog.twidda.activities;
import static android.os.AsyncTask.Status.RUNNING; import static android.os.AsyncTask.Status.RUNNING;
import static android.view.View.GONE; import static android.view.View.*;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static org.nuclearfog.twidda.activities.MediaViewer.KEY_MEDIA_LINK; import static org.nuclearfog.twidda.activities.MediaViewer.*;
import static org.nuclearfog.twidda.activities.MediaViewer.KEY_MEDIA_TYPE;
import static org.nuclearfog.twidda.activities.MediaViewer.MEDIAVIEWER_IMAGE;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.location.Location; import android.location.Location;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
@ -26,6 +23,7 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.MessageUpdater; import org.nuclearfog.twidda.backend.MessageUpdater;
import org.nuclearfog.twidda.backend.holder.DirectmessageHolder;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.dialog.ConfirmDialog; import org.nuclearfog.twidda.dialog.ConfirmDialog;
@ -53,7 +51,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
private Dialog loadingCircle; private Dialog loadingCircle;
private ConfirmDialog errorDialog, leaveDialog; private ConfirmDialog errorDialog, leaveDialog;
@Nullable @Nullable
private String mediaPath; private Uri mediaUri;
@Override @Override
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
@ -95,7 +93,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (receiver.getText().length() == 0 && message.getText().length() == 0 && mediaPath == null) { if (receiver.getText().length() == 0 && message.getText().length() == 0 && mediaUri == null) {
super.onBackPressed(); super.onBackPressed();
} else if (!leaveDialog.isShowing()) { } else if (!leaveDialog.isShowing()) {
leaveDialog.show(); leaveDialog.show();
@ -117,11 +115,11 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
@Override @Override
protected void onMediaFetched(int resultType, @NonNull String path) { protected void onMediaFetched(int resultType, @NonNull Uri uri) {
if (resultType == REQUEST_IMAGE) { if (resultType == REQUEST_IMAGE) {
preview.setVisibility(VISIBLE); preview.setVisibility(VISIBLE);
media.setVisibility(GONE); media.setVisibility(GONE);
mediaPath = path; mediaUri = uri;
} }
} }
@ -140,9 +138,8 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
} }
// open media // open media
else if (v.getId() == R.id.dm_preview) { else if (v.getId() == R.id.dm_preview) {
String[] link = {mediaPath};
Intent image = new Intent(this, MediaViewer.class); Intent image = new Intent(this, MediaViewer.class);
image.putExtra(KEY_MEDIA_LINK, link); image.putExtra(KEY_MEDIA_URI, new Uri[]{mediaUri});
image.putExtra(KEY_MEDIA_TYPE, MEDIAVIEWER_IMAGE); image.putExtra(KEY_MEDIA_TYPE, MEDIAVIEWER_IMAGE);
startActivity(image); startActivity(image);
} }
@ -199,9 +196,12 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
private void sendMessage() { private void sendMessage() {
String username = receiver.getText().toString(); String username = receiver.getText().toString();
String message = this.message.getText().toString(); String message = this.message.getText().toString();
if (!username.trim().isEmpty() && (!message.trim().isEmpty() || mediaPath != null)) { if (!username.trim().isEmpty() && (!message.trim().isEmpty() || mediaUri != null)) {
messageAsync = new MessageUpdater(this); DirectmessageHolder holder = new DirectmessageHolder(username, message);
messageAsync.execute(username, message, mediaPath); if (mediaUri != null)
holder.addMedia(getApplicationContext(), mediaUri);
messageAsync = new MessageUpdater(this, holder);
messageAsync.execute();
if (!loadingCircle.isShowing()) { if (!loadingCircle.isShowing()) {
loadingCircle.show(); loadingCircle.show();
} }

View File

@ -12,10 +12,9 @@ import static org.nuclearfog.twidda.database.GlobalSettings.PROFILE_IMG_HIGH_RES
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point; import android.graphics.Point;
import android.location.Location; import android.location.Location;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Gravity; import android.view.Gravity;
import android.view.Menu; import android.view.Menu;
@ -38,6 +37,7 @@ import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.UserUpdater; import org.nuclearfog.twidda.backend.UserUpdater;
import org.nuclearfog.twidda.backend.holder.ProfileHolder;
import org.nuclearfog.twidda.model.User; import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
@ -49,8 +49,6 @@ import org.nuclearfog.twidda.dialog.ConfirmDialog.OnConfirmListener;
import org.nuclearfog.twidda.dialog.ProgressDialog; import org.nuclearfog.twidda.dialog.ProgressDialog;
import org.nuclearfog.twidda.dialog.ProgressDialog.OnProgressStopListener; import org.nuclearfog.twidda.dialog.ProgressDialog.OnProgressStopListener;
import java.io.File;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation; import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/** /**
@ -76,7 +74,8 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, OnP
private ConfirmDialog errorDialog, closeDialog; private ConfirmDialog errorDialog, closeDialog;
private User user; private User user;
private String profileLink, bannerLink; private Uri profileLink, bannerLink;
@Override @Override
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
@ -182,22 +181,20 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, OnP
@Override @Override
protected void onMediaFetched(int resultType, @NonNull String path) { protected void onMediaFetched(int resultType, @NonNull Uri uri) {
// Add image as profile image // Add image as profile image
if (resultType == REQUEST_PROFILE) { if (resultType == REQUEST_PROFILE) {
Bitmap image = BitmapFactory.decodeFile(path); profile_image.setImageURI(uri);
profile_image.setImageBitmap(image); profileLink = uri;
profileLink = path;
} }
// Add image as banner image // Add image as banner image
else if (resultType == REQUEST_BANNER) { else if (resultType == REQUEST_BANNER) {
File img = new File(path);
Point displaySize = new Point(); Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize); getWindowManager().getDefaultDisplay().getSize(displaySize);
picasso.load(img).resize(displaySize.x, displaySize.x / 3).centerCrop(Gravity.TOP).into(profile_banner, this); picasso.load(uri).resize(displaySize.x, displaySize.x / 3).centerCrop(Gravity.TOP).into(profile_banner, this);
addBannerBtn.setVisibility(INVISIBLE); addBannerBtn.setVisibility(INVISIBLE);
changeBannerBtn.setVisibility(VISIBLE); changeBannerBtn.setVisibility(VISIBLE);
bannerLink = path; bannerLink = uri;
} }
} }
@ -289,8 +286,13 @@ public class ProfileEditor extends MediaActivity implements OnClickListener, OnP
String errMsg = getString(R.string.error_invalid_link); String errMsg = getString(R.string.error_invalid_link);
link.setError(errMsg); link.setError(errMsg);
} else if (editorAsync == null || editorAsync.getStatus() != RUNNING) { } else if (editorAsync == null || editorAsync.getStatus() != RUNNING) {
editorAsync = new UserUpdater(this); ProfileHolder holder = new ProfileHolder(username, userLink, userBio, userLoc);
editorAsync.execute(username, userLink, userLoc, userBio, profileLink, bannerLink); if (profileLink != null)
holder.addImageUri(getApplicationContext(), profileLink);
if (bannerLink != null)
holder.addBannerUri(getApplicationContext(), bannerLink);
editorAsync = new UserUpdater(this, holder);
editorAsync.execute();
if (!loadingCircle.isShowing()) { if (!loadingCircle.isShowing()) {
loadingCircle.show(); loadingCircle.show();
} }

View File

@ -6,15 +6,13 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static org.nuclearfog.twidda.activities.MediaViewer.KEY_MEDIA_LINK; import static org.nuclearfog.twidda.activities.MediaViewer.*;
import static org.nuclearfog.twidda.activities.MediaViewer.KEY_MEDIA_TYPE;
import static org.nuclearfog.twidda.activities.MediaViewer.MEDIAVIEWER_IMAGE;
import static org.nuclearfog.twidda.activities.MediaViewer.MEDIAVIEWER_VIDEO;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.location.Location; import android.location.Location;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@ -70,6 +68,10 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
*/ */
public static final String KEY_TWEETPOPUP_TEXT = "tweet_text"; public static final String KEY_TWEETPOPUP_TEXT = "tweet_text";
private static final String MIME_GIF = "image/gif";
private static final String MIME_IMAGE_ALL = "image/";
private static final String MIME_VIDEO_ALL = "video/";
/** /**
* max amount of images (limited to 4 by twitter) * max amount of images (limited to 4 by twitter)
*/ */
@ -90,7 +92,7 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
private View locationPending; private View locationPending;
private Location location; private Location location;
private List<String> mediaPath = new LinkedList<>(); private List<Uri> mediaPath = new LinkedList<>();
private MediaType selectedFormat = MediaType.NONE; private MediaType selectedFormat = MediaType.NONE;
@Override @Override
@ -203,7 +205,8 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
// open media preview // open media preview
else if (v.getId() == R.id.tweet_prev_media) { else if (v.getId() == R.id.tweet_prev_media) {
Intent image = new Intent(this, MediaViewer.class); Intent image = new Intent(this, MediaViewer.class);
image.putExtra(KEY_MEDIA_LINK, mediaPath.toArray(new String[0])); Uri[] uris = mediaPath.toArray(new Uri[0]);
image.putExtra(KEY_MEDIA_URI, uris);
if (selectedFormat == MediaType.VIDEO) { if (selectedFormat == MediaType.VIDEO) {
image.putExtra(KEY_MEDIA_TYPE, MEDIAVIEWER_VIDEO); image.putExtra(KEY_MEDIA_TYPE, MEDIAVIEWER_VIDEO);
startActivity(image); startActivity(image);
@ -235,29 +238,32 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
@Override @Override
protected void onMediaFetched(int resultType, @NonNull String path) { protected void onMediaFetched(int resultType, @NonNull Uri uri) {
String mime = StringTools.getMimeType(path); String mime = getContentResolver().getType(uri);
if (mime == null) {
Toast.makeText(this, R.string.error_file_format, LENGTH_SHORT).show();
}
// check if file is a gif image // check if file is a gif image
if (mime.equals("image/gif")) { else if (mime.equals(MIME_GIF)) {
if (selectedFormat == MediaType.NONE) { if (selectedFormat == MediaType.NONE) {
selectedFormat = MediaType.GIF; selectedFormat = MediaType.GIF;
previewBtn.setImageResource(R.drawable.gif); previewBtn.setImageResource(R.drawable.gif);
AppStyles.setDrawableColor(previewBtn, settings.getIconColor()); AppStyles.setDrawableColor(previewBtn, settings.getIconColor());
previewBtn.setVisibility(VISIBLE); previewBtn.setVisibility(VISIBLE);
mediaBtn.setVisibility(GONE); mediaBtn.setVisibility(GONE);
mediaPath.add(path); mediaPath.add(uri);
} else { } else {
Toast.makeText(this, R.string.info_cant_add_video, LENGTH_SHORT).show(); Toast.makeText(this, R.string.info_cant_add_video, LENGTH_SHORT).show();
} }
} }
// check if file is an image with supported extension // check if file is an image with supported extension
else if (mime.startsWith("image")) { else if (mime.startsWith(MIME_IMAGE_ALL)) {
if (selectedFormat == MediaType.NONE) if (selectedFormat == MediaType.NONE)
selectedFormat = MediaType.IMAGE; selectedFormat = MediaType.IMAGE;
if (selectedFormat == MediaType.IMAGE) { if (selectedFormat == MediaType.IMAGE) {
// add up to 4 images // add up to 4 images
if (mediaPath.size() < MAX_IMAGES) { if (mediaPath.size() < MAX_IMAGES) {
mediaPath.add(path); mediaPath.add(uri);
previewBtn.setVisibility(VISIBLE); previewBtn.setVisibility(VISIBLE);
// if limit reached, remove mediaselect button // if limit reached, remove mediaselect button
if (mediaPath.size() == MAX_IMAGES) { if (mediaPath.size() == MAX_IMAGES) {
@ -269,14 +275,14 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
} }
} }
// check if file is a video with supported extension // check if file is a video with supported extension
else if (mime.startsWith("video")) { else if (mime.startsWith(MIME_VIDEO_ALL)) {
if (selectedFormat == MediaType.NONE) { if (selectedFormat == MediaType.NONE) {
selectedFormat = MediaType.VIDEO; selectedFormat = MediaType.VIDEO;
previewBtn.setImageResource(R.drawable.video); previewBtn.setImageResource(R.drawable.video);
AppStyles.setDrawableColor(previewBtn, settings.getIconColor()); AppStyles.setDrawableColor(previewBtn, settings.getIconColor());
previewBtn.setVisibility(VISIBLE); previewBtn.setVisibility(VISIBLE);
mediaBtn.setVisibility(GONE); mediaBtn.setVisibility(GONE);
mediaPath.add(path); mediaPath.add(uri);
} }
} }
// file type is not supported // file type is not supported
@ -349,9 +355,9 @@ public class TweetEditor extends MediaActivity implements OnClickListener, OnPro
TweetHolder tweet = new TweetHolder(tweetStr, inReplyId); TweetHolder tweet = new TweetHolder(tweetStr, inReplyId);
// add media // add media
if (selectedFormat == MediaType.IMAGE || selectedFormat == MediaType.GIF) if (selectedFormat == MediaType.IMAGE || selectedFormat == MediaType.GIF)
tweet.addMedia(mediaPath.toArray(new String[0]), TweetHolder.MediaType.IMAGE); tweet.addMedia(getApplicationContext(), mediaPath);
else if (selectedFormat == MediaType.VIDEO) else if (selectedFormat == MediaType.VIDEO)
tweet.addMedia(mediaPath.toArray(new String[0]), TweetHolder.MediaType.VIDEO); tweet.addMedia(getApplicationContext(), mediaPath);
// add location // add location
if (location != null) if (location != null)
tweet.addLocation(location); tweet.addLocation(location);

View File

@ -1,6 +1,7 @@
package org.nuclearfog.twidda.adapter; package org.nuclearfog.twidda.adapter;
import android.graphics.Bitmap; import android.content.Context;
import android.net.Uri;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -12,15 +13,17 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.adapter.holder.Footer; import org.nuclearfog.twidda.adapter.holder.Footer;
import org.nuclearfog.twidda.adapter.holder.ImageItem; import org.nuclearfog.twidda.adapter.holder.ImageItem;
import org.nuclearfog.twidda.backend.holder.ImageHolder; import org.nuclearfog.twidda.backend.utils.PicassoBuilder;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import com.squareup.picasso.Picasso;
/** /**
* Adapter class for image previews * Adapter class for image previews
* *
@ -42,29 +45,32 @@ public class ImageAdapter extends Adapter<ViewHolder> {
private GlobalSettings settings; private GlobalSettings settings;
private List<ImageHolder> images = new LinkedList<>(); private Picasso picasso;
private List<Uri> imageUri = new ArrayList<>(5);
private boolean loading = false; private boolean loading = false;
private boolean saveImg = true; private boolean saveImg = true;
/** /**
* @param itemClickListener click listener * @param itemClickListener click listener
*/ */
public ImageAdapter(GlobalSettings settings, OnImageClickListener itemClickListener) { public ImageAdapter(Context context, OnImageClickListener itemClickListener) {
this.itemClickListener = itemClickListener; this.itemClickListener = itemClickListener;
this.settings = settings; this.settings = GlobalSettings.getInstance(context);
picasso = PicassoBuilder.get(context);
} }
/** /**
* add new image at the last position * add new image at the last position
* *
* @param imageItem image to add * @param uri Uri of the image
*/ */
@MainThread @MainThread
public void addLast(@NonNull ImageHolder imageItem) { public void addLast(@NonNull Uri uri) {
int imagePos = images.size(); int imagePos = imageUri.size();
if (imagePos == 0) if (imagePos == 0)
loading = true; loading = true;
images.add(imageItem); imageUri.add(uri);
notifyItemInserted(imagePos); notifyItemInserted(imagePos);
} }
@ -74,7 +80,7 @@ public class ImageAdapter extends Adapter<ViewHolder> {
@MainThread @MainThread
public void disableLoading() { public void disableLoading() {
loading = false; loading = false;
int circlePos = images.size(); int circlePos = imageUri.size();
notifyItemRemoved(circlePos); notifyItemRemoved(circlePos);
} }
@ -91,13 +97,13 @@ public class ImageAdapter extends Adapter<ViewHolder> {
* @return true if there isn't any image * @return true if there isn't any image
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return images.isEmpty(); return imageUri.isEmpty();
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (loading && position == images.size()) if (loading && position == imageUri.size())
return LOADING; return LOADING;
return PICTURE; return PICTURE;
} }
@ -106,8 +112,8 @@ public class ImageAdapter extends Adapter<ViewHolder> {
@Override @Override
public int getItemCount() { public int getItemCount() {
if (loading) if (loading)
return images.size() + 1; return imageUri.size() + 1;
return images.size(); return imageUri.size();
} }
@ -121,8 +127,7 @@ public class ImageAdapter extends Adapter<ViewHolder> {
public void onClick(View v) { public void onClick(View v) {
int pos = item.getLayoutPosition(); int pos = item.getLayoutPosition();
if (pos != NO_POSITION) { if (pos != NO_POSITION) {
Bitmap img = images.get(pos).reducedImage; itemClickListener.onImageClick(imageUri.get(pos));
itemClickListener.onImageClick(img);
} }
} }
}); });
@ -133,8 +138,7 @@ public class ImageAdapter extends Adapter<ViewHolder> {
public void onClick(View v) { public void onClick(View v) {
int pos = item.getLayoutPosition(); int pos = item.getLayoutPosition();
if (pos != NO_POSITION) { if (pos != NO_POSITION) {
Bitmap img = images.get(pos).fullImage; itemClickListener.onImageSave(imageUri.get(pos));
itemClickListener.onImageSave(img, pos);
} }
} }
}); });
@ -150,8 +154,8 @@ public class ImageAdapter extends Adapter<ViewHolder> {
public void onBindViewHolder(@NonNull ViewHolder vh, int index) { public void onBindViewHolder(@NonNull ViewHolder vh, int index) {
if (vh instanceof ImageItem) { if (vh instanceof ImageItem) {
ImageItem item = (ImageItem) vh; ImageItem item = (ImageItem) vh;
Bitmap image = images.get(index).preview; Uri uri = imageUri.get(index);
item.preview.setImageBitmap(image); picasso.load(uri).into(item.preview);
} }
} }
@ -163,16 +167,13 @@ public class ImageAdapter extends Adapter<ViewHolder> {
/** /**
* called to select an image * called to select an image
* *
* @param image selected image bitmap * @param uri selected image uri
*/ */
void onImageClick(Bitmap image); void onImageClick(Uri uri);
/** /**
* called to save image to storage * called to save image to storage
*
* @param image selected image bitmap
* @param index current image index
*/ */
void onImageSave(Bitmap image, int index); void onImageSave(Uri uri);
} }
} }

View File

@ -1,7 +1,6 @@
package org.nuclearfog.twidda.backend; package org.nuclearfog.twidda.backend;
import android.graphics.Bitmap; import android.net.Uri;
import android.graphics.BitmapFactory;
import android.os.AsyncTask; import android.os.AsyncTask;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -10,25 +9,27 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.activities.MediaViewer; import org.nuclearfog.twidda.activities.MediaViewer;
import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.backend.api.TwitterException; import org.nuclearfog.twidda.backend.api.TwitterException;
import org.nuclearfog.twidda.backend.holder.ImageHolder;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.backend.utils.StringTools;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/** /**
* Background task to load images from twitter and storage * background async to download images to a cache folder
* todo: cache files to prevent out of memory error
* *
* @author nuclearfog * @author nuclearfog
* @see MediaViewer * @see MediaViewer
*/ */
public class ImageLoader extends AsyncTask<String, ImageHolder, Boolean> { public class ImageLoader extends AsyncTask<Uri, Uri, Boolean> {
@Nullable @Nullable
private ErrorHandler.TwitterError err; private ErrorHandler.TwitterError err;
private Twitter twitter; private Twitter twitter;
private WeakReference<MediaViewer> callback; private WeakReference<MediaViewer> callback;
private File cache;
/** /**
* initialize image loader * initialize image loader
@ -39,23 +40,32 @@ public class ImageLoader extends AsyncTask<String, ImageHolder, Boolean> {
super(); super();
callback = new WeakReference<>(activity); callback = new WeakReference<>(activity);
twitter = Twitter.get(activity); twitter = Twitter.get(activity);
cache = new File(activity.getExternalCacheDir(), MediaViewer.CACHE_FOLDER);
cache.mkdirs();
} }
@Override @Override
protected Boolean doInBackground(String[] links) { protected Boolean doInBackground(Uri[] links) {
try { try {
for (String link : links) { // create cache folder if not exists
Bitmap image; // download imaged to a local cache folder
if (link.startsWith("https://")) { for (Uri link : links) {
image = twitter.downloadImage(link); File file = new File(cache, StringTools.getRandomString());
} else { file.createNewFile();
image = BitmapFactory.decodeFile(link); FileOutputStream os = new FileOutputStream(file);
} InputStream input = twitter.downloadImage(link.toString());
if (image != null) {
ImageHolder images = new ImageHolder(image); // copy image to cache folder
publishProgress(images); int length;
byte[] buffer = new byte[4096];
while ((length = input.read(buffer)) > 0) {
os.write(buffer, 0, length);
} }
input.close();
os.close();
// create a new uri
publishProgress(Uri.fromFile(file));
} }
return true; return true;
} catch (TwitterException err) { } catch (TwitterException err) {
@ -68,9 +78,9 @@ public class ImageLoader extends AsyncTask<String, ImageHolder, Boolean> {
@Override @Override
protected void onProgressUpdate(ImageHolder[] images) { protected void onProgressUpdate(Uri[] uris) {
if (callback.get() != null) { if (callback.get() != null) {
callback.get().setImage(images[0]); callback.get().setImage(uris[0]);
} }
} }

View File

@ -1,10 +1,10 @@
package org.nuclearfog.twidda.backend; package org.nuclearfog.twidda.backend;
import android.graphics.Bitmap;
import android.os.AsyncTask; import android.os.AsyncTask;
import org.nuclearfog.twidda.activities.MediaActivity; import org.nuclearfog.twidda.activities.MediaActivity;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -16,10 +16,6 @@ import java.lang.ref.WeakReference;
*/ */
public class ImageSaver extends AsyncTask<Object, Void, Boolean> { public class ImageSaver extends AsyncTask<Object, Void, Boolean> {
/**
* Quality of the saved jpeg images
*/
private static final int JPEG_QUALITY = 90;
private WeakReference<MediaActivity> callback; private WeakReference<MediaActivity> callback;
@ -34,12 +30,19 @@ public class ImageSaver extends AsyncTask<Object, Void, Boolean> {
protected Boolean doInBackground(Object... data) { protected Boolean doInBackground(Object... data) {
try { try {
if (data != null && data.length == 2) { if (data != null && data.length == 2) {
if (data[0] instanceof Bitmap && data[1] instanceof OutputStream) { if (data[0] instanceof InputStream && data[1] instanceof OutputStream) {
Bitmap image = (Bitmap) data[0]; InputStream source = (InputStream) data[0];
OutputStream fileStream = (OutputStream) data[1]; OutputStream destiny = (OutputStream) data[1];
boolean imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
fileStream.close(); // copy file from cache to the destiny folder
return imageSaved; int length;
byte[] buffer = new byte[4096];
while ((length = source.read(buffer)) > 0) {
destiny.write(buffer, 0, length);
}
source.close();
destiny.close();
return true;
} }
} }
} catch (Exception err) { } catch (Exception err) {

View File

@ -72,7 +72,7 @@ public class ListAction extends AsyncTask<Long, Void, UserList> {
return twitter.unfollowUserlist(listId); return twitter.unfollowUserlist(listId);
case DELETE: case DELETE:
return twitter.destroyUserlist(listId); return twitter.deleteUserlist(listId);
} }
} catch (TwitterException err) { } catch (TwitterException err) {
this.err = err; this.err = err;

View File

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import org.nuclearfog.twidda.activities.MessageEditor; import org.nuclearfog.twidda.activities.MessageEditor;
import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.backend.api.TwitterException; import org.nuclearfog.twidda.backend.api.TwitterException;
import org.nuclearfog.twidda.backend.holder.DirectmessageHolder;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -17,38 +18,41 @@ import java.lang.ref.WeakReference;
* @author nuclearfog * @author nuclearfog
* @see MessageEditor * @see MessageEditor
*/ */
public class MessageUpdater extends AsyncTask<String, Void, Boolean> { public class MessageUpdater extends AsyncTask<Void, Void, Boolean> {
private ErrorHandler.TwitterError twException; private ErrorHandler.TwitterError twException;
private WeakReference<MessageEditor> callback; private WeakReference<MessageEditor> callback;
private Twitter twitter; private Twitter twitter;
private DirectmessageHolder message;
/** /**
* send direct message * send direct message
* *
* @param activity Activity context * @param activity Activity context
*/ */
public MessageUpdater(@NonNull MessageEditor activity) { public MessageUpdater(@NonNull MessageEditor activity, DirectmessageHolder message) {
super(); super();
twitter = Twitter.get(activity); twitter = Twitter.get(activity);
callback = new WeakReference<>(activity); callback = new WeakReference<>(activity);
this.message = message;
} }
@Override @Override
protected Boolean doInBackground(String[] param) { protected Boolean doInBackground(Void[] v) {
try { try {
// first check if user exists // first check if user exists
long id = twitter.showUser(param[0]).getId(); long id = twitter.showUser(message.getReceiver()).getId();
// upload media if any // upload media if any
long mediaId = -1; long mediaId = -1;
String mediaPath = param[2]; if (message.getMediaStream() != null) {
if (mediaPath != null && !mediaPath.isEmpty()) { mediaId = twitter.uploadMedia(message.getMediaStream(), message.getMimeType());
mediaId = twitter.uploadImage(param[2]); message.getMediaStream().close();
} }
// upload message and media ID if defined // upload message and media ID if defined
if (!isCancelled()) { if (!isCancelled()) {
twitter.sendDirectmessage(id, param[1], mediaId); twitter.sendDirectmessage(id, message.getText(), mediaId);
} }
return true; return true;
} catch (TwitterException twException) { } catch (TwitterException twException) {

View File

@ -8,6 +8,7 @@ import org.nuclearfog.twidda.backend.api.TwitterException;
import org.nuclearfog.twidda.backend.holder.TweetHolder; import org.nuclearfog.twidda.backend.holder.TweetHolder;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import java.io.InputStream;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/** /**
@ -18,9 +19,8 @@ import java.lang.ref.WeakReference;
*/ */
public class TweetUpdater extends AsyncTask<Void, Void, Boolean> { public class TweetUpdater extends AsyncTask<Void, Void, Boolean> {
private ErrorHandler.TwitterError twException;
private Twitter twitter; private Twitter twitter;
private ErrorHandler.TwitterError twException;
private WeakReference<TweetEditor> callback; private WeakReference<TweetEditor> callback;
private TweetHolder tweet; private TweetHolder tweet;
@ -40,25 +40,16 @@ public class TweetUpdater extends AsyncTask<Void, Void, Boolean> {
@Override @Override
protected Boolean doInBackground(Void[] v) { protected Boolean doInBackground(Void[] v) {
try { try {
long[] mediaIds = {}; String[] mimeTypes = tweet.getMimeTypes();
String[] mediaLinks = tweet.getMediaPaths(); InputStream[] mediaStreams = tweet.getMediaStreams();
if (mediaLinks != null && mediaLinks.length > 0) {
mediaIds = new long[mediaLinks.length];
// upload image // upload media first
if (tweet.getMediaType() == TweetHolder.MediaType.IMAGE) { long[] mediaIds = new long[mediaStreams.length];
for (int i = 0; i < mediaLinks.length; i++) { for (int pos = 0 ; pos < mediaStreams.length ; pos++) {
mediaIds[i] = twitter.uploadImage(mediaLinks[i]); mediaIds[pos] = twitter.uploadMedia(mediaStreams[pos], mimeTypes[pos]);
if (isCancelled()) { mediaStreams[pos].close();
break;
}
}
}
// upload video file
else if (tweet.getMediaType() == TweetHolder.MediaType.VIDEO) {// fixme
//mediaIds[0] = mTwitter.uploadVideo(mediaLinks[0]);
}
} }
// upload tweet // upload tweet
if (!isCancelled()) { if (!isCancelled()) {
double[] coordinates = null; double[] coordinates = null;

View File

@ -7,10 +7,12 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.activities.ProfileEditor; import org.nuclearfog.twidda.activities.ProfileEditor;
import org.nuclearfog.twidda.backend.api.Twitter; import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.backend.api.TwitterException; import org.nuclearfog.twidda.backend.api.TwitterException;
import org.nuclearfog.twidda.backend.holder.ProfileHolder;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.model.User; import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.database.AppDatabase; import org.nuclearfog.twidda.database.AppDatabase;
import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/** /**
@ -19,7 +21,7 @@ import java.lang.ref.WeakReference;
* @author nuclearfog * @author nuclearfog
* @see ProfileEditor * @see ProfileEditor
*/ */
public class UserUpdater extends AsyncTask<String, Void, User> { public class UserUpdater extends AsyncTask<Void, Void, User> {
@Nullable @Nullable
private ErrorHandler.TwitterError twException; private ErrorHandler.TwitterError twException;
@ -27,31 +29,36 @@ public class UserUpdater extends AsyncTask<String, Void, User> {
private Twitter twitter; private Twitter twitter;
private AppDatabase db; private AppDatabase db;
private ProfileHolder profile;
public UserUpdater(ProfileEditor activity) {
public UserUpdater(ProfileEditor activity, ProfileHolder profile) {
super(); super();
callback = new WeakReference<>(activity); callback = new WeakReference<>(activity);
twitter = Twitter.get(activity); twitter = Twitter.get(activity);
db = new AppDatabase(activity); db = new AppDatabase(activity);
this.profile = profile;
} }
@Override @Override
protected User doInBackground(String[] param) { protected User doInBackground(Void[] v) {
try { try {
String name = param[0]; if (profile.getProfileImageStream() != null) {
String link = param[1]; twitter.updateProfileImage(profile.getProfileImageStream());
String location = param[2]; profile.getProfileImageStream().close();
String bio = param[3]; }
String profileImg = param[4]; if (profile.getBannerImageStream() != null) {
String bannerImg = param[5]; twitter.updateProfileBanner(profile.getBannerImageStream());
twitter.updateProfileImage(profileImg); profile.getBannerImageStream().close();
twitter.updateProfileBanner(bannerImg); }
User user = twitter.updateProfile(name, link, location, bio); User user = twitter.updateProfile(profile.getName(), profile.getUrl(), profile.getLocation(), profile.getDescription());
db.storeUser(user); db.storeUser(user);
return user; return user;
} catch (TwitterException twException) { } catch (TwitterException twException) {
this.twException = twException; this.twException = twException;
} catch (IOException e) {
e.printStackTrace();
} }
return null; return null;
} }

View File

@ -1,8 +1,6 @@
package org.nuclearfog.twidda.backend.api; package org.nuclearfog.twidda.backend.api;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -27,7 +25,6 @@ import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.UserList; import org.nuclearfog.twidda.model.UserList;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
@ -50,6 +47,8 @@ import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
/** /**
* new API implementation to replace twitter4j and add version 2.0 support * new API implementation to replace twitter4j and add version 2.0 support
@ -258,6 +257,8 @@ public class Twitter {
*/ */
public User showUser(String name) throws TwitterException { public User showUser(String name) throws TwitterException {
List<String> params = new ArrayList<>(3); List<String> params = new ArrayList<>(3);
if (name.startsWith("@"))
name = name.substring(1);
params.add("screen_name=" + name); params.add("screen_name=" + name);
return getUser1(USER_LOOKUP, params); return getUser1(USER_LOOKUP, params);
} }
@ -940,7 +941,7 @@ public class Twitter {
* @param listId ID of the list * @param listId ID of the list
* @return removed userlist * @return removed userlist
*/ */
public UserList destroyUserlist(long listId) throws TwitterException { public UserList deleteUserlist(long listId) throws TwitterException {
List<String> params = new ArrayList<>(2); List<String> params = new ArrayList<>(2);
params.add("list_id=" + listId); params.add("list_id=" + listId);
return getUserlist(USERLIST_DESTROY, params); return getUserlist(USERLIST_DESTROY, params);
@ -1064,7 +1065,7 @@ public class Twitter {
params.add("id=" + messageId); params.add("id=" + messageId);
try { try {
Response response = delete(DIRECTMESSAGE_DELETE, params); Response response = delete(DIRECTMESSAGE_DELETE, params);
if (response.code() != 200) { if (response.code() >= 300) {
throw new TwitterException(response); throw new TwitterException(response);
} }
} catch (IOException err) { } catch (IOException err) {
@ -1130,20 +1131,18 @@ public class Twitter {
} }
/** /**
* upload image file to twitter and generate a media ID * upload medida file to twitter and generate a media ID
* *
* @param filePath path to the local file * @param uploadStream path to the local file
* @return media ID * @return media ID
*/ */
public long uploadImage(String filePath) throws TwitterException { public long uploadMedia(InputStream uploadStream, String mime) throws TwitterException {
File file = new File(filePath); List<String> params = new ArrayList<>(4);
String mime = StringTools.getMimeType(filePath);
try { try {
// step 1 INIT // step 1 INIT
List<String> params = new ArrayList<>();
params.add("command=INIT"); params.add("command=INIT");
params.add("media_type=" + mime); params.add("media_type=" + mime);
params.add("total_bytes=" + file.length()); params.add("total_bytes=" + uploadStream.available());
Response response = post(MEDIA_UPLOAD, params); Response response = post(MEDIA_UPLOAD, params);
if (response.code() < 200 || response.code() >= 300 || response.body() == null) if (response.code() < 200 || response.code() >= 300 || response.body() == null)
throw new TwitterException(response); throw new TwitterException(response);
@ -1155,7 +1154,7 @@ public class Twitter {
params.add("command=APPEND"); params.add("command=APPEND");
params.add("segment_index=0"); params.add("segment_index=0");
params.add("media_id=" + mediaId); params.add("media_id=" + mediaId);
response = post(MEDIA_UPLOAD, params, file, "media"); response = post(MEDIA_UPLOAD, params, uploadStream, "media");
if (response.code() < 200 || response.code() >= 300) if (response.code() < 200 || response.code() >= 300)
throw new TwitterException(response); throw new TwitterException(response);
@ -1181,14 +1180,13 @@ public class Twitter {
* @param link link to the image * @param link link to the image
* @return image bitmap * @return image bitmap
*/ */
public Bitmap downloadImage(String link) throws TwitterException { public InputStream downloadImage(String link) throws TwitterException {
try { try {
// this type of link requires authentication // this type of link requires authentication
if (link.startsWith("https://ton.twitter.com/")) { if (link.startsWith("https://ton.twitter.com/")) {
Response response = get(link, new ArrayList<>(0)); Response response = get(link, new ArrayList<>(0));
if (response.code() == 200) { if (response.code() == 200) {
InputStream is = response.body().byteStream(); return response.body().byteStream();
return BitmapFactory.decodeStream(is);
} else { } else {
throw new TwitterException(response); throw new TwitterException(response);
} }
@ -1196,8 +1194,7 @@ public class Twitter {
// public link, no authentication required // public link, no authentication required
else { else {
URL imageUrl = new URL(link); URL imageUrl = new URL(link);
InputStream is = imageUrl.openConnection().getInputStream(); return imageUrl.openConnection().getInputStream();
return BitmapFactory.decodeStream(is);
} }
} catch (IOException e) { } catch (IOException e) {
throw new TwitterException(e); throw new TwitterException(e);
@ -1225,19 +1222,19 @@ public class Twitter {
/** /**
* update current user's profile image * update current user's profile image
* *
* @param path local path to the image * @param inputStream inputstream of the local image file
*/ */
public void updateProfileImage(String path) throws TwitterException { public void updateProfileImage(InputStream inputStream) throws TwitterException {
updateImage(PROFILE_UPDATE_IMAGE, path, "image"); updateImage(PROFILE_UPDATE_IMAGE, inputStream, "image");
} }
/** /**
* update current user's profile banner image * update current user's profile banner image
* *
* @param path local path to the image * @param inputStream inputstream of the local image file
*/ */
public void updateProfileBanner(String path) throws TwitterException { public void updateProfileBanner(InputStream inputStream) throws TwitterException {
updateImage(PROFILE_UPDATE_BANNER, path, "banner"); updateImage(PROFILE_UPDATE_BANNER, inputStream, "banner");
} }
/** /**
@ -1552,15 +1549,15 @@ public class Twitter {
* update profile images * update profile images
* *
* @param endpoint endpoint to use * @param endpoint endpoint to use
* @param imagePath path to the local image * @param input inputstream of the image file
* @param key key name used to identify the type of image * @param key key name used to identify the type of image
*/ */
private void updateImage(String endpoint, String imagePath, String key) throws TwitterException { private void updateImage(String endpoint, InputStream input, String key) throws TwitterException {
try { try {
List<String> params = new ArrayList<>(3); List<String> params = new ArrayList<>(3);
params.add("skip_status=true"); params.add("skip_status=true");
params.add("include_entities=false"); params.add("include_entities=false");
Response response = post(endpoint, params, new File(imagePath), key); Response response = post(endpoint, params, input, key);
if (response.body() != null) { if (response.body() != null) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
if (response.code() != 200) { if (response.code() != 200) {
@ -1655,10 +1652,20 @@ public class Twitter {
* @param params additional http parameters * @param params additional http parameters
* @return http resonse * @return http resonse
*/ */
private Response post(String endpoint, List<String> params, File file, String addToKey) throws IOException { private Response post(String endpoint, List<String> params, InputStream is, String addToKey) throws IOException {
RequestBody data = RequestBody.create(MediaType.parse("application/octet-stream"), file); RequestBody data = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse("application/octet-stream");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeAll(Okio.buffer(Okio.source(is)));
}
};
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(addToKey, file.getName(), data).build(); .addFormDataPart(addToKey, "", data).build();
return post(endpoint, params, body); return post(endpoint, params, body);
} }

View File

@ -0,0 +1,81 @@
package org.nuclearfog.twidda.backend.holder;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
/**
* this class holds information about a directmessage
*
* @author nuclearfog
*/
public class DirectmessageHolder {
private String name;
private String text;
private String mimeType = "";
private InputStream fileStream;
public DirectmessageHolder(String name, String text) {
this.name = name;
this.text = text;
}
/**
* add media uri and create input stream
*
* @param context context used to create inputstream and mime type
* @param uri uri of a local media file
*/
public void addMedia(Context context, @NonNull Uri uri) {
ContentResolver resolver = context.getContentResolver();
try {
fileStream = resolver.openInputStream(uri);
mimeType = resolver.getType(uri);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* get name of the receiver
*
* @return screen name
*/
public String getReceiver() {
return name;
}
/**
* get message text
*
* @return message text
*/
public String getText() {
return text;
}
/**
* get inputstream of the media file
*
* @return input stream
*/
public InputStream getMediaStream() {
return fileStream;
}
/**
* get MIME type of the media file
*
* @return mime type string
*/
public String getMimeType() {
return mimeType;
}
}

View File

@ -1,63 +0,0 @@
package org.nuclearfog.twidda.backend.holder;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
/**
* Container class for Bitmap images and previews
*
* @author nuclearfog
*/
public class ImageHolder {
/**
* maximum height of the smallest preview in pixels
*/
private static final float PREVIEW_HEIGHT = 320.0f;
/**
* maximum height of the image preview in pixels
*/
private static final float REDUCED_HEIGHT = 1200.0f;
/**
* preview image bitmap
*/
public final Bitmap preview;
/**
* downscaled image bitmap
*/
public final Bitmap reducedImage;
/**
* full image bitmap
*/
public final Bitmap fullImage;
/**
* @param fullImage Full size image
*/
public ImageHolder(@NonNull Bitmap fullImage) {
this.fullImage = fullImage;
float reducedRatio = fullImage.getHeight() / REDUCED_HEIGHT;
float previewRatio = fullImage.getHeight() / PREVIEW_HEIGHT;
if (reducedRatio > 1.0f) {
int height = (int) REDUCED_HEIGHT;
int width = (int) (fullImage.getWidth() / reducedRatio);
reducedImage = Bitmap.createScaledBitmap(fullImage, width, height, false);
} else {
reducedImage = fullImage;
}
if (previewRatio > 1.0f) {
int height = (int) PREVIEW_HEIGHT;
int width = (int) (fullImage.getWidth() / previewRatio);
preview = Bitmap.createScaledBitmap(fullImage, width, height, false);
} else {
preview = fullImage;
}
}
}

View File

@ -0,0 +1,103 @@
package org.nuclearfog.twidda.backend.holder;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
/**
* holder class for profile update information
*
* @author nuclearfog
*/
public class ProfileHolder {
private String name;
private String url;
private String description;
private String location;
private InputStream profileImgStream;
private InputStream bannerImgStream;
public ProfileHolder(String name, String url, String description, String location) {
this.name = name;
this.url = url;
this.description = description;
this.location = location;
}
/**
* add profile image Uri
*
* @param context context used to resolve Uri
* @param profileImgUri Uri of the local image file
*/
public void addImageUri(Context context, @Nullable Uri profileImgUri) {
try {
profileImgStream = context.getContentResolver().openInputStream(profileImgUri);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* add banner image Uri
*
* @param context context used to resolve Uri
* @param bannerImgUri Uri of the local image file
*/
public void addBannerUri(Context context, @Nullable Uri bannerImgUri) {
try {
bannerImgStream = context.getContentResolver().openInputStream(bannerImgUri);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @return screen name of the user
*/
public String getName() {
return name;
}
/**
* @return profile description (bio)
*/
public String getDescription() {
return description;
}
/**
* @return location name
*/
public String getLocation() {
return location;
}
/**
* @return profile url
*/
public String getUrl() {
return url;
}
/**
* @return filestream of the profile image
*/
public InputStream getProfileImageStream() {
return profileImgStream;
}
/**
* @return filestream of the banner image
*/
public InputStream getBannerImageStream() {
return bannerImgStream;
}
}

View File

@ -1,9 +1,16 @@
package org.nuclearfog.twidda.backend.holder; package org.nuclearfog.twidda.backend.holder;
import android.content.ContentResolver;
import android.content.Context;
import android.location.Location; import android.location.Location;
import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/** /**
* TweetHolder keeps information about a written tweet such as text, media files and location * TweetHolder keeps information about a written tweet such as text, media files and location
* *
@ -11,19 +18,13 @@ import androidx.annotation.NonNull;
*/ */
public class TweetHolder { public class TweetHolder {
public enum MediaType {
IMAGE,
VIDEO,
NONE
}
private final String text; private final String text;
private final long replyId; private final long replyId;
private String[] mediaPaths;
private double longitude; private double longitude;
private double latitude; private double latitude;
private InputStream[] mediaStreams;
private MediaType mType = MediaType.NONE; private String[] mimeTypes = {};
private boolean hasLocation = false; private boolean hasLocation = false;
@ -41,12 +42,25 @@ public class TweetHolder {
/** /**
* Add media paths to the holder * Add media paths to the holder
* *
* @param mediaLinks array of media paths from storage * @param context context to resolve Uri links
* @param mType type of media * @param mediaUri array of media paths from storage
*/ */
public void addMedia(String[] mediaLinks, MediaType mType) { public void addMedia(Context context, List<Uri> mediaUri) {
this.mediaPaths = mediaLinks; if (!mediaUri.isEmpty()) {
this.mType = mType; List<InputStream> iss = new ArrayList<>();
List<String> mimeTypes = new ArrayList<>();
ContentResolver resolver = context.getContentResolver();
try {
for (Uri uri : mediaUri) {
iss.add(resolver.openInputStream(uri));
mimeTypes.add(resolver.getType(uri));
}
} catch (Exception e) {
e.printStackTrace();
}
this.mediaStreams = iss.toArray(new InputStream[0]);
this.mimeTypes = mimeTypes.toArray(new String[0]);
}
} }
/** /**
@ -83,8 +97,8 @@ public class TweetHolder {
* *
* @return media type * @return media type
*/ */
public MediaType getMediaType() { public String[] getMimeTypes() {
return mType; return mimeTypes;
} }
/** /**
@ -92,8 +106,8 @@ public class TweetHolder {
* *
* @return array of media paths * @return array of media paths
*/ */
public String[] getMediaPaths() { public InputStream[] getMediaStreams() {
return mediaPaths; return mediaStreams;
} }
/** /**
@ -123,15 +137,6 @@ public class TweetHolder {
return hasLocation; return hasLocation;
} }
/**
* return if tweet is a reply
*
* @return true if tweet is a reply
*/
public boolean isReply() {
return replyId > 0;
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {

View File

@ -2,7 +2,6 @@ package org.nuclearfog.twidda.backend.proxy;
import org.nuclearfog.twidda.database.GlobalSettings; import org.nuclearfog.twidda.database.GlobalSettings;
import java.io.IOException;
import java.net.Authenticator; import java.net.Authenticator;
import java.net.PasswordAuthentication; import java.net.PasswordAuthentication;

View File

@ -108,36 +108,6 @@ public final class StringTools {
return result; return result;
} }
/**
* get MIME type of a media file
* if file type is not supported, return {@link #MIME_ALL}
*
* @param filename file name or path with extension
* @return MIME type
*/
public static String getMimeType(String filename) {
int end = filename.lastIndexOf('.');
if (end < 0)
return MIME_ALL;
String extension = filename.substring(end + 1).toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
case "webp":
case "png":
case "gif":
return "image/" + extension;
case "mp4":
case "3gp":
return "video/" + extension;
default:
return MIME_ALL;
}
}
/** /**
* convert Twitter ISO 8601 date time to long format * convert Twitter ISO 8601 date time to long format
* *

View File

@ -5,7 +5,11 @@ import android.database.Cursor;
import org.nuclearfog.twidda.model.DirectMessage; import org.nuclearfog.twidda.model.DirectMessage;
import org.nuclearfog.twidda.model.User; import org.nuclearfog.twidda.model.User;
/**
* database implementation of a directmessage
*
* @author nuclearfog
*/
class DirectMessageImpl implements DirectMessage { class DirectMessageImpl implements DirectMessage {
private long id; private long id;