Image resizing

This commit is contained in:
Grishka 2022-03-24 12:39:28 +03:00
parent d47eb752a5
commit e087cf03cc
13 changed files with 341 additions and 116 deletions

View File

@ -10,7 +10,7 @@ android {
applicationId "org.joinmastodon.android"
minSdk 23
targetSdk 31
versionCode 15
versionCode 16
versionName "0.1"
}

View File

@ -2,31 +2,22 @@ package org.joinmastodon.android.api;
import android.database.Cursor;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.OpenableColumns;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
import okio.Source;
public class ContentUriRequestBody extends RequestBody{
public class ContentUriRequestBody extends CountingRequestBody{
private final Uri uri;
private final long length;
private ProgressListener progressListener;
public ContentUriRequestBody(Uri uri, ProgressListener progressListener){
super(progressListener);
this.uri=uri;
this.progressListener=progressListener;
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
cursor.moveToFirst();
length=cursor.getInt(0);
@ -39,40 +30,7 @@ public class ContentUriRequestBody extends RequestBody{
}
@Override
public long contentLength() throws IOException{
return length;
}
@Override
public void writeTo(BufferedSink sink) throws IOException{
if(progressListener!=null){
try(Source source=Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri))){
BufferedSink wrappedSink=Okio.buffer(new CountingSink(sink));
wrappedSink.writeAll(source);
wrappedSink.flush();
}
}else{
try(Source source=Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri))){
sink.writeAll(source);
}
}
}
private class CountingSink extends ForwardingSink{
private long bytesWritten=0;
private long lastCallbackTime;
public CountingSink(Sink delegate){
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException{
super.write(source, byteCount);
bytesWritten+=byteCount;
if(SystemClock.uptimeMillis()-lastCallbackTime>=100L || bytesWritten==length){
lastCallbackTime=SystemClock.uptimeMillis();
UiUtils.runOnUiThread(()->progressListener.onProgress(bytesWritten, length));
}
}
protected Source openSource() throws IOException{
return Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri));
}
}

View File

@ -0,0 +1,39 @@
package org.joinmastodon.android.api;
import java.io.IOException;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
abstract class CountingRequestBody extends RequestBody{
protected long length;
protected ProgressListener progressListener;
CountingRequestBody(ProgressListener progressListener){
this.progressListener=progressListener;
}
@Override
public long contentLength() throws IOException{
return length;
}
@Override
public void writeTo(BufferedSink sink) throws IOException{
if(progressListener!=null){
try(Source source=openSource()){
BufferedSink wrappedSink=Okio.buffer(new CountingSink(length, progressListener, sink));
wrappedSink.writeAll(source);
wrappedSink.flush();
}
}else{
try(Source source=openSource()){
sink.writeAll(source);
}
}
}
protected abstract Source openSource() throws IOException;
}

View File

@ -0,0 +1,34 @@
package org.joinmastodon.android.api;
import android.os.SystemClock;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.IOException;
import okio.Buffer;
import okio.ForwardingSink;
import okio.Sink;
class CountingSink extends ForwardingSink{
private long bytesWritten=0;
private long lastCallbackTime;
private final long length;
private final ProgressListener progressListener;
public CountingSink(long length, ProgressListener progressListener, Sink delegate){
super(delegate);
this.length=length;
this.progressListener=progressListener;
}
@Override
public void write(Buffer source, long byteCount) throws IOException{
super.write(source, byteCount);
bytesWritten+=byteCount;
if(SystemClock.uptimeMillis()-lastCallbackTime>=100L || bytesWritten==length){
lastCallbackTime=SystemClock.uptimeMillis();
UiUtils.runOnUiThread(()->progressListener.onProgress(bytesWritten, length));
}
}
}

View File

@ -135,7 +135,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return method;
}
public RequestBody getRequestBody(){
public RequestBody getRequestBody() throws IOException{
return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
}

View File

@ -0,0 +1,128 @@
package org.joinmastodon.android.api;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.provider.OpenableColumns;
import org.joinmastodon.android.MastodonApp;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import androidx.annotation.NonNull;
import okhttp3.MediaType;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
public class ResizedImageRequestBody extends CountingRequestBody{
private File tempFile;
private Uri uri;
private String contentType;
public ResizedImageRequestBody(Uri uri, int maxSize, ProgressListener progressListener) throws IOException{
super(progressListener);
this.uri=uri;
contentType=MastodonApp.context.getContentResolver().getType(uri);
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inJustDecodeBounds=true;
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
BitmapFactory.decodeStream(in, null, opts);
}
if(opts.outWidth*opts.outHeight>maxSize){
Bitmap bitmap;
if(Build.VERSION.SDK_INT>=29){
bitmap=ImageDecoder.decodeBitmap(ImageDecoder.createSource(MastodonApp.context.getContentResolver(), uri), (decoder, info, source)->{
int targetWidth=Math.round((float)Math.sqrt((float)maxSize*((float)info.getSize().getWidth()/info.getSize().getHeight())));
int targetHeight=Math.round((float)Math.sqrt((float)maxSize*((float)info.getSize().getHeight()/info.getSize().getWidth())));
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
decoder.setTargetSize(targetWidth, targetHeight);
});
}else{
int targetWidth=Math.round((float)Math.sqrt((float)maxSize*((float)opts.outWidth/opts.outHeight)));
int targetHeight=Math.round((float)Math.sqrt((float)maxSize*((float)opts.outHeight/opts.outWidth)));
float factor=opts.outWidth/(float)targetWidth;
opts=new BitmapFactory.Options();
opts.inSampleSize=(int)factor;
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
bitmap=BitmapFactory.decodeStream(in, null, opts);
}
if(factor%1f!=0f){
Bitmap scaled=Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
new Canvas(scaled).drawBitmap(bitmap, null, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), new Paint(Paint.FILTER_BITMAP_FLAG));
bitmap=scaled;
}
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
int rotation;
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
ExifInterface exif=new ExifInterface(in);
int orientation=exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
rotation=switch(orientation){
case ExifInterface.ORIENTATION_ROTATE_90 -> 90;
case ExifInterface.ORIENTATION_ROTATE_180 -> 180;
case ExifInterface.ORIENTATION_ROTATE_270 -> 270;
default -> 0;
};
}
if(rotation!=0){
Matrix matrix=new Matrix();
matrix.setRotate(rotation);
bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
}
}
tempFile=new File(MastodonApp.context.getCacheDir(), "tmp_upload_image");
try(FileOutputStream out=new FileOutputStream(tempFile)){
if("image/png".equals(contentType)){
bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
}else{
bitmap.compress(Bitmap.CompressFormat.JPEG, 97, out);
contentType="image/jpeg";
}
}
length=tempFile.length();
}else{
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
cursor.moveToFirst();
length=cursor.getInt(0);
}
}
}
@Override
protected Source openSource() throws IOException{
if(tempFile==null){
return Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri));
}else{
return Okio.source(tempFile);
}
}
@Override
public MediaType contentType(){
return MediaType.get(contentType);
}
@Override
public void writeTo(BufferedSink sink) throws IOException{
try{
super.writeTo(sink);
}finally{
if(tempFile!=null){
tempFile.delete();
}
}
}
}

View File

@ -8,31 +8,40 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.ContentUriRequestBody;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.ProgressListener;
import org.joinmastodon.android.api.ResizedImageRequestBody;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.IOException;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
public class UploadAttachment extends MastodonAPIRequest<Attachment>{
private Uri uri;
private ProgressListener progressListener;
private int maxImageSize;
public UploadAttachment(Uri uri){
super(HttpMethod.POST, "/media", Attachment.class);
this.uri=uri;
}
public UploadAttachment(Uri uri, int maxImageSize){
this(uri);
this.maxImageSize=maxImageSize;
}
public UploadAttachment setProgressListener(ProgressListener progressListener){
this.progressListener=progressListener;
return this;
}
@Override
public RequestBody getRequestBody(){
public RequestBody getRequestBody() throws IOException{
return new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", UiUtils.getFileName(uri), new ContentUriRequestBody(uri, progressListener))
.addFormDataPart("file", UiUtils.getFileName(uri), maxImageSize>0 ? new ResizedImageRequestBody(uri, maxImageSize, progressListener) : new ContentUriRequestBody(uri, progressListener))
.build();
}
}

View File

@ -12,25 +12,20 @@ public class AccountSession{
public Token token;
public Account self;
public String domain;
public int tootCharLimit;
public Application app;
public long infoLastUpdated;
public long instanceLastUpdated;
public Instance instance;
public boolean activated=true;
private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController;
private transient CacheController cacheController;
AccountSession(Token token, Account self, Application app, String domain, int tootCharLimit, Instance instance, boolean activated){
AccountSession(Token token, Account self, Application app, String domain, boolean activated){
this.token=token;
this.self=self;
this.domain=domain;
this.app=app;
this.tootCharLimit=tootCharLimit;
this.instance=instance;
this.activated=activated;
instanceLastUpdated=infoLastUpdated=System.currentTimeMillis();
infoLastUpdated=System.currentTimeMillis();
}
AccountSession(){}

View File

@ -1,7 +1,6 @@
package org.joinmastodon.android.api.session;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
@ -37,12 +36,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.browser.customtabs.CustomTabsIntent;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@ -56,13 +53,14 @@ public class AccountSessionManager{
private HashMap<String, AccountSession> sessions=new HashMap<>();
private HashMap<String, List<EmojiCategory>> customEmojis=new HashMap<>();
private HashMap<String, Long> customEmojisLastUpdated=new HashMap<>();
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
private HashMap<String, Instance> instances=new HashMap<>();
private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
private Instance authenticatingInstance;
private Application authenticatingApp;
private String lastActiveAccountID;
private SharedPreferences prefs;
private boolean loadedCustomEmojis;
private boolean loadedInstances;
public static AccountSessionManager getInstance(){
return instance;
@ -84,14 +82,16 @@ public class AccountSessionManager{
Log.e(TAG, "Error loading accounts", x);
}
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
MastodonAPIController.runInBackground(()->readCustomEmojis(domains));
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
}
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
AccountSession session=new AccountSession(token, self, app, instance.uri, instance.maxTootChars, instance, active);
instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, active);
sessions.put(session.getID(), session);
lastActiveAccountID=session.getID();
writeAccountsFile();
maybeUpdateLocalInfo();
}
private void writeAccountsFile(){
@ -155,7 +155,7 @@ public class AccountSessionManager{
writeAccountsFile();
String domain=session.domain.toLowerCase();
if(sessions.isEmpty() || !sessions.values().stream().map(s->s.domain.toLowerCase()).collect(Collectors.toSet()).contains(domain)){
getCustomEmojisFile(domain).delete();
getInstanceInfoFile(domain).delete();
}
}
@ -213,11 +213,11 @@ public class AccountSessionManager{
HashSet<String> domains=new HashSet<>();
for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase());
if(now-session.infoLastUpdated>24L*3600_000L || now-session.instanceLastUpdated>24L*360_000L*3L){
if(now-session.infoLastUpdated>24L*3600_000L){
updateSessionLocalInfo(session);
}
}
if(loadedCustomEmojis){
if(loadedInstances){
maybeUpdateCustomEmojis(domains);
}
}
@ -225,9 +225,9 @@ public class AccountSessionManager{
private void maybeUpdateCustomEmojis(Set<String> domains){
long now=System.currentTimeMillis();
for(String domain:domains){
Long lastUpdated=customEmojisLastUpdated.get(domain);
Long lastUpdated=instancesLastUpdated.get(domain);
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateCustomEmojis(domain);
updateInstanceInfo(domain);
}
}
}
@ -248,34 +248,33 @@ public class AccountSessionManager{
}
})
.exec(session.getID());
}
private void updateInstanceInfo(String domain){
new GetInstance()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Instance result){
session.instance=result;
session.instanceLastUpdated=System.currentTimeMillis();
writeAccountsFile();
}
public void onSuccess(Instance instance){
instances.put(domain, instance);
new GetCustomEmojis()
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Emoji> result){
InstanceInfoStorageWrapper emojis=new InstanceInfoStorageWrapper();
emojis.lastUpdated=System.currentTimeMillis();
emojis.emojis=result;
emojis.instance=instance;
customEmojis.put(domain, groupCustomEmojis(emojis));
instancesLastUpdated.put(domain, emojis.lastUpdated);
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(emojis, domain));
}
@Override
public void onError(ErrorResponse error){
@Override
public void onError(ErrorResponse error){
}
})
.exec(session.getID());
}
private void updateCustomEmojis(String domain){
new GetCustomEmojis()
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Emoji> result){
CustomEmojisStorageWrapper emojis=new CustomEmojisStorageWrapper();
emojis.lastUpdated=System.currentTimeMillis();
emojis.emojis=result;
customEmojis.put(domain, groupCustomEmojis(emojis));
customEmojisLastUpdated.put(domain, emojis.lastUpdated);
MastodonAPIController.runInBackground(()->writeCustomEmojisFile(emojis, domain));
}
})
.execNoAuth(domain);
}
@Override
@ -286,38 +285,39 @@ public class AccountSessionManager{
.execNoAuth(domain);
}
private File getCustomEmojisFile(String domain){
return new File(MastodonApp.context.getFilesDir(), "emojis_"+domain.replace('.', '_')+".json");
private File getInstanceInfoFile(String domain){
return new File(MastodonApp.context.getFilesDir(), "instance_"+domain.replace('.', '_')+".json");
}
private void writeCustomEmojisFile(CustomEmojisStorageWrapper emojis, String domain){
try(FileOutputStream out=new FileOutputStream(getCustomEmojisFile(domain))){
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(emojis, writer);
writer.flush();
}catch(IOException x){
Log.w(TAG, "Error writing emojis file for "+domain, x);
Log.w(TAG, "Error writing instance info file for "+domain, x);
}
}
private void readCustomEmojis(Set<String> domains){
private void readInstanceInfo(Set<String> domains){
for(String domain:domains){
try(FileInputStream in=new FileInputStream(getCustomEmojisFile(domain))){
try(FileInputStream in=new FileInputStream(getInstanceInfoFile(domain))){
InputStreamReader reader=new InputStreamReader(in, StandardCharsets.UTF_8);
CustomEmojisStorageWrapper emojis=MastodonAPIController.gson.fromJson(reader, CustomEmojisStorageWrapper.class);
InstanceInfoStorageWrapper emojis=MastodonAPIController.gson.fromJson(reader, InstanceInfoStorageWrapper.class);
customEmojis.put(domain, groupCustomEmojis(emojis));
customEmojisLastUpdated.put(domain, emojis.lastUpdated);
instances.put(domain, emojis.instance);
instancesLastUpdated.put(domain, emojis.lastUpdated);
}catch(IOException|JsonParseException x){
Log.w(TAG, "Error reading emojis file for "+domain, x);
Log.w(TAG, "Error reading instance info file for "+domain, x);
}
}
if(!loadedCustomEmojis){
loadedCustomEmojis=true;
if(!loadedInstances){
loadedInstances=true;
maybeUpdateCustomEmojis(domains);
}
}
private List<EmojiCategory> groupCustomEmojis(CustomEmojisStorageWrapper emojis){
private List<EmojiCategory> groupCustomEmojis(InstanceInfoStorageWrapper emojis){
return emojis.emojis.stream()
.filter(e->e.visibleInPicker)
.collect(Collectors.groupingBy(e->e.category==null ? "" : e.category))
@ -333,6 +333,10 @@ public class AccountSessionManager{
return r==null ? Collections.emptyList() : r;
}
public Instance getInstanceInfo(String domain){
return instances.get(domain);
}
public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id);
session.self=account;
@ -344,7 +348,8 @@ public class AccountSessionManager{
public List<AccountSession> accounts;
}
private static class CustomEmojisStorageWrapper{
private static class InstanceInfoStorageWrapper{
public Instance instance;
public List<Emoji> emojis;
public long lastUpdated;
}

View File

@ -18,6 +18,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -62,6 +63,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
@ -104,7 +106,6 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
private static final int MEDIA_RESULT=717;
private static final int IMAGE_DESCRIPTION_RESULT=363;
private static final int MAX_POLL_OPTIONS=4;
private static final int MAX_ATTACHMENTS=4;
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
@ -173,6 +174,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
private ComposeAutocompleteSpan currentAutocompleteSpan;
private FrameLayout mainEditTextWrap;
private ComposeAutocompleteViewController autocompleteViewController;
private Instance instance;
@Override
public void onCreate(Bundle savedInstanceState){
@ -181,12 +183,22 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
accountID=getArguments().getString("account");
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
charLimit=session.tootCharLimit;
if(charLimit==0)
charLimit=500;
self=session.self;
instanceDomain=session.domain;
customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain);
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
if(instance==null){
Nav.finish(this);
return;
}
if(instance.maxTootChars>0)
charLimit=instance.maxTootChars;
else if(instance.configuration!=null && instance.configuration.statuses!=null && instance.configuration.statuses.maxCharacters>0)
charLimit=instance.configuration.statuses.maxCharacters;
else
charLimit=500;
if(getArguments().containsKey("replyTo")){
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
statusVisibility=replyTo.visibility;
@ -647,7 +659,11 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
intent.putExtra(Intent.EXTRA_MIME_TYPES, instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]));
}else{
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(intent, MEDIA_RESULT);
}
@ -740,7 +756,12 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
rotationAnimator.setDuration(1500);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.start();
attachment.uploadRequest=(UploadAttachment) new UploadAttachment(attachment.uri)
int maxSize=0;
String contentType=getActivity().getContentResolver().getType(attachment.uri);
if(contentType!=null && contentType.startsWith("image/")){
maxSize=2_073_600; // TODO get this from instance configuration when it gets added there
}
attachment.uploadRequest=(UploadAttachment) new UploadAttachment(attachment.uri, maxSize)
.setProgressListener(new ProgressListener(){
@Override
public void onProgress(long transferred, long total){
@ -864,10 +885,11 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
return true;
});
option.edit.addTextChangedListener(new SimpleTextWatcher(e->updatePublishButtonState()));
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)});
pollOptionsView.addView(option.view);
pollOptions.add(option);
if(pollOptions.size()==MAX_POLL_OPTIONS)
if(pollOptions.size()==(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0 ? instance.configuration.polls.maxOptions : 4))
addPollOptionBtn.setVisibility(View.GONE);
return option;
}

View File

@ -132,7 +132,7 @@ public class AccountActivationFragment extends AppKitFragment{
AccountSessionManager mgr=AccountSessionManager.getInstance();
AccountSession session=mgr.getAccount(accountID);
mgr.removeAccount(accountID);
mgr.addAccount(session.instance, session.token, result, session.app, true);
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true);
String newID=mgr.getLastActiveAccountID();
Bundle args=new Bundle();
args.putString("account", newID);

View File

@ -21,7 +21,7 @@ public class ReportRuleChoiceFragment extends BaseReportChoiceFragment{
@Override
protected void populateItems(){
isMultipleChoice=true;
Instance inst=AccountSessionManager.getInstance().getAccount(accountID).instance;
Instance inst=AccountSessionManager.getInstance().getInstanceInfo(AccountSessionManager.getInstance().getAccount(accountID).domain);
if(inst!=null && inst.rules!=null){
for(Instance.Rule rule:inst.rules){
items.add(new Item(rule.text, null, rule.id));

View File

@ -76,8 +76,11 @@ public class Instance extends BaseModel{
public Account contactAccount;
public Stats stats;
public int maxTootChars;
public List<Rule> rules;
public Configuration configuration;
// non-standard field in some Mastodon forks
public int maxTootChars;
@Override
public void postprocess() throws ObjectValidationException{
@ -137,4 +140,36 @@ public class Instance extends BaseModel{
public int statusCount;
public int domainCount;
}
@Parcel
public static class Configuration{
public StatusesConfiguration statuses;
public MediaAttachmentsConfiguration mediaAttachments;
public PollsConfiguration polls;
}
@Parcel
public static class StatusesConfiguration{
public int maxCharacters;
public int maxMediaAttachments;
public int charactersReservedPerUrl;
}
@Parcel
public static class MediaAttachmentsConfiguration{
public List<String> supportedMimeTypes;
public int imageSizeLimit;
public int imageMatrixLimit;
public int videoSizeLimit;
public int videoFrameRateLimit;
public int videoMatrixLimit;
}
@Parcel
public static class PollsConfiguration{
public int maxOptions;
public int maxCharactersPerOption;
public int minExpiration;
public int maxExpiration;
}
}