More ways to add images in compose

This commit is contained in:
Grishka 2022-04-08 01:19:31 +03:00
parent d14fc624f9
commit c6ea406a61
4 changed files with 144 additions and 54 deletions

View File

@ -5,13 +5,10 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Outline; import android.graphics.Outline;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.LayerDrawable;
import android.icu.text.BreakIterator; import android.icu.text.BreakIterator;
import android.net.Uri; import android.net.Uri;
@ -22,7 +19,6 @@ import android.provider.OpenableColumns;
import android.text.Editable; import android.text.Editable;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.Layout; import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -78,17 +74,15 @@ import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan; import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan; import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher; import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ComposeMediaLayout; import org.joinmastodon.android.ui.views.ComposeMediaLayout;
import org.joinmastodon.android.ui.views.ReorderableLinearLayout; import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
import org.joinmastodon.android.ui.views.SelectionListenerEditText; import org.joinmastodon.android.ui.views.ComposeEditText;
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout; import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
import org.parceler.Parcel; import org.parceler.Parcel;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -106,7 +100,7 @@ import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ComposeFragment extends ToolbarFragment implements OnBackPressedListener, SelectionListenerEditText.SelectionListener{ public class ComposeFragment extends ToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener{
private static final int MEDIA_RESULT=717; private static final int MEDIA_RESULT=717;
private static final int IMAGE_DESCRIPTION_RESULT=363; private static final int IMAGE_DESCRIPTION_RESULT=363;
@ -142,7 +136,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
private Account self; private Account self;
private String instanceDomain; private String instanceDomain;
private SelectionListenerEditText mainEditText; private ComposeEditText mainEditText;
private TextView charCounter; private TextView charCounter;
private String accountID; private String accountID;
private int charCount, charLimit, trimmedCharCount; private int charCount, charLimit, trimmedCharCount;
@ -705,28 +699,31 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
} }
} }
private void addMediaAttachment(Uri uri){ private boolean addMediaAttachment(Uri uri){
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){ if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS)); showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
return; return false;
} }
String type=getActivity().getContentResolver().getType(uri); String type=getActivity().getContentResolver().getType(uri);
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null){ if(instance.configuration!=null && instance.configuration.mediaAttachments!=null){
if(instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.contains(type)){ if(instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.contains(type)){
showMediaAttachmentError(getString(R.string.media_attachment_unsupported_type, UiUtils.getFileName(uri))); showMediaAttachmentError(getString(R.string.media_attachment_unsupported_type, UiUtils.getFileName(uri)));
return; return false;
} }
int sizeLimit=type.startsWith("image/") ? instance.configuration.mediaAttachments.imageSizeLimit : instance.configuration.mediaAttachments.videoSizeLimit; int sizeLimit=type.startsWith("image/") ? instance.configuration.mediaAttachments.imageSizeLimit : instance.configuration.mediaAttachments.videoSizeLimit;
int size; int size;
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){ try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
cursor.moveToFirst(); cursor.moveToFirst();
size=cursor.getInt(0); size=cursor.getInt(0);
}catch(Exception x){
Log.w("ComposeFragment", x);
return false;
} }
if(size>sizeLimit){ if(size>sizeLimit){
float mb=sizeLimit/(float)(1024*1024); float mb=sizeLimit/(float)(1024*1024);
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb); String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb);
showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb)); showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb));
return; return false;
} }
} }
pollBtn.setEnabled(false); pollBtn.setEnabled(false);
@ -747,6 +744,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
updatePublishButtonState(); updatePublishButtonState();
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS) if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
mediaBtn.setEnabled(false); mediaBtn.setEnabled(false);
return true;
} }
private void showMediaAttachmentError(String text){ private void showMediaAttachmentError(String text){
@ -1073,6 +1071,18 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
} }
} }
@Override
public String[] onGetAllowedMediaMimeTypes(){
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null)
return instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]);
return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"};
}
@Override
public boolean onAddMediaAttachmentFromEditText(Uri uri){
return addMediaAttachment(uri);
}
private void startAutocomplete(ComposeAutocompleteSpan span){ private void startAutocomplete(ComposeAutocompleteSpan span){
currentAutocompleteSpan=span; currentAutocompleteSpan=span;
Editable e=mainEditText.getText(); Editable e=mainEditText.getText();

View File

@ -0,0 +1,120 @@
package org.joinmastodon.android.ui.views;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputContentInfo;
import android.widget.EditText;
import androidx.annotation.RequiresApi;
public class ComposeEditText extends EditText{
private SelectionListener selectionListener;
private MediaAcceptingInputConnection inputConnectionWrapper=new MediaAcceptingInputConnection();
public ComposeEditText(Context context){
super(context);
}
public ComposeEditText(Context context, AttributeSet attrs){
super(context, attrs);
}
public ComposeEditText(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
public ComposeEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd){
super.onSelectionChanged(selStart, selEnd);
if(selectionListener!=null)
selectionListener.onSelectionChanged(selStart, selEnd);
}
public void setSelectionListener(SelectionListener selectionListener){
this.selectionListener=selectionListener;
}
// Support receiving images from keyboards
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
inputConnectionWrapper.setTarget(super.onCreateInputConnection(outAttrs));
return inputConnectionWrapper;
}
return super.onCreateInputConnection(outAttrs);
}
// Support pasting images
@Override
public boolean onTextContextMenuItem(int id){
if(id==android.R.id.paste){
ClipboardManager clipboard=getContext().getSystemService(ClipboardManager.class);
ClipData clip=clipboard.getPrimaryClip();
if(processClipData(clip))
return true;
}
return super.onTextContextMenuItem(id);
}
// Support drag-and-dropping images in multiwindow mode
@Override
public boolean onDragEvent(DragEvent event){
if(event.getAction()==DragEvent.ACTION_DROP && Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
if(((Activity) getContext()).requestDragAndDropPermissions(event)!=null)
return processClipData(event.getClipData());
}
return super.onDragEvent(event);
}
private boolean processClipData(ClipData clip){
if(clip==null)
return false;
boolean processedAny=false;
for(int i=0;i<clip.getItemCount();i++){
Uri uri=clip.getItemAt(i).getUri();
if(uri!=null){
processedAny=true;
selectionListener.onAddMediaAttachmentFromEditText(uri);
}
}
return processedAny;
}
public interface SelectionListener{
void onSelectionChanged(int start, int end);
String[] onGetAllowedMediaMimeTypes();
boolean onAddMediaAttachmentFromEditText(Uri uri);
}
private class MediaAcceptingInputConnection extends InputConnectionWrapper{
public MediaAcceptingInputConnection(){
super(null, true);
}
@RequiresApi(api=Build.VERSION_CODES.N_MR1)
@Override
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts){
Uri contentUri=inputContentInfo.getContentUri();
if(contentUri==null)
return false;
inputContentInfo.requestPermission();
return selectionListener.onAddMediaAttachmentFromEditText(contentUri);
}
}
}

View File

@ -1,40 +0,0 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
public class SelectionListenerEditText extends EditText{
private SelectionListener selectionListener;
public SelectionListenerEditText(Context context){
super(context);
}
public SelectionListenerEditText(Context context, AttributeSet attrs){
super(context, attrs);
}
public SelectionListenerEditText(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
public SelectionListenerEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd){
super.onSelectionChanged(selStart, selEnd);
if(selectionListener!=null)
selectionListener.onSelectionChanged(selStart, selEnd);
}
public void setSelectionListener(SelectionListener selectionListener){
this.selectionListener=selectionListener;
}
public interface SelectionListener{
void onSelectionChanged(int start, int end);
}
}

View File

@ -89,7 +89,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp"> android:layout_marginTop="10dp">
<org.joinmastodon.android.ui.views.SelectionListenerEditText <org.joinmastodon.android.ui.views.ComposeEditText
android:id="@+id/toot_text" android:id="@+id/toot_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"