Compose things
This commit is contained in:
parent
b2588fbb6e
commit
8c5d6cd4a6
|
@ -10,7 +10,7 @@ android {
|
|||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 12
|
||||
versionCode 13
|
||||
versionName "0.1"
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
|
||||
public class UpdateAttachment extends MastodonAPIRequest<Attachment>{
|
||||
public UpdateAttachment(String id, String description){
|
||||
super(HttpMethod.PUT, "/media/"+id, Attachment.class);
|
||||
setRequestBody(new Body(description));
|
||||
}
|
||||
|
||||
private static class Body{
|
||||
public String description;
|
||||
|
||||
public Body(String description){
|
||||
this.description=description;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import android.icu.text.BreakIterator;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
|
@ -33,6 +34,7 @@ import android.widget.LinearLayout;
|
|||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
|
@ -58,8 +60,11 @@ import org.joinmastodon.android.ui.PopupKeyboard;
|
|||
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
||||
import org.parceler.Parcel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -80,7 +85,9 @@ import me.grishka.appkit.utils.V;
|
|||
public class ComposeFragment extends ToolbarFragment implements OnBackPressedListener{
|
||||
|
||||
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);
|
||||
|
||||
|
@ -115,7 +122,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
|
||||
private Button publishButton;
|
||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn;
|
||||
private LinearLayout attachmentsView;
|
||||
private ComposeMediaLayout attachmentsView;
|
||||
private TextView replyText;
|
||||
private ReorderableLinearLayout pollOptionsView;
|
||||
private View pollWrap;
|
||||
|
@ -124,7 +131,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
|
||||
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
|
||||
|
||||
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>();
|
||||
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>(), allAttachments=new ArrayList<>();
|
||||
private DraftMediaAttachment uploadingAttachment;
|
||||
|
||||
private List<EmojiCategory> customEmojis;
|
||||
|
@ -244,7 +251,20 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
|
||||
// TODO save and restore media attachments (when design is ready)
|
||||
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
|
||||
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
|
||||
for(Parcelable a:serializedAttachments){
|
||||
DraftMediaAttachment att=Parcels.unwrap(a);
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
attachments.add(att);
|
||||
}
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
}else if(!allAttachments.isEmpty()){
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
for(DraftMediaAttachment att:allAttachments){
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
@ -261,6 +281,13 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
outState.putInt("pollDuration", pollDuration);
|
||||
outState.putString("pollDurationStr", pollDurationStr);
|
||||
outState.putBoolean("hasSpoiler", hasSpoiler);
|
||||
if(!attachments.isEmpty()){
|
||||
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
serializedAttachments.add(Parcels.wrap(att));
|
||||
}
|
||||
outState.putParcelableArrayList("attachments", serializedAttachments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,6 +500,21 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
if(reqCode==IMAGE_DESCRIPTION_RESULT && success){
|
||||
Attachment updated=Parcels.unwrap(result.getParcelable("attachment"));
|
||||
for(DraftMediaAttachment att:attachments){
|
||||
if(att.serverAttachment.id.equals(updated.id)){
|
||||
att.serverAttachment=updated;
|
||||
att.description=updated.description;
|
||||
att.descriptionView.setText(att.description);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmDiscardDraftAndFinish(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.discard_draft)
|
||||
|
@ -506,26 +548,61 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
}
|
||||
|
||||
private void addMediaAttachment(Uri uri){
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
|
||||
return;
|
||||
pollBtn.setEnabled(false);
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, V.dp(250), V.dp(250)));
|
||||
attachmentsView.addView(thumb);
|
||||
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.uri=uri;
|
||||
draft.view=thumb;
|
||||
draft.progressBar=thumb.findViewById(R.id.progress);
|
||||
Button btn=thumb.findViewById(R.id.remove_btn);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
|
||||
attachmentsView.addView(createMediaAttachmentView(draft));
|
||||
allAttachments.add(draft);
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
if(uploadingAttachment==null){
|
||||
uploadMediaAttachment(draft);
|
||||
}else{
|
||||
queuedAttachments.add(draft);
|
||||
}
|
||||
updatePublishButtonState();
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
|
||||
mediaBtn.setEnabled(false);
|
||||
}
|
||||
|
||||
private View createMediaAttachmentView(DraftMediaAttachment draft){
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
TextView fileName=thumb.findViewById(R.id.file_name);
|
||||
fileName.setText(UiUtils.getFileName(draft.uri));
|
||||
|
||||
draft.view=thumb;
|
||||
draft.progressBar=thumb.findViewById(R.id.progress);
|
||||
draft.infoBar=thumb.findViewById(R.id.info_bar);
|
||||
draft.errorOverlay=thumb.findViewById(R.id.error_overlay);
|
||||
draft.descriptionView=thumb.findViewById(R.id.description);
|
||||
ImageButton btn=thumb.findViewById(R.id.remove_btn);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
btn=thumb.findViewById(R.id.remove_btn2);
|
||||
btn.setTag(draft);
|
||||
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
|
||||
Button retry=thumb.findViewById(R.id.retry_upload);
|
||||
retry.setTag(draft);
|
||||
retry.setOnClickListener(this::onRetryMediaUploadClick);
|
||||
draft.infoBar.setTag(draft);
|
||||
draft.infoBar.setOnClickListener(this::onEditMediaDescriptionClick);
|
||||
|
||||
if(!TextUtils.isEmpty(draft.description))
|
||||
draft.descriptionView.setText(draft.description);
|
||||
|
||||
if(uploadingAttachment!=draft && !queuedAttachments.contains(draft)){
|
||||
draft.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
if(failedAttachments.contains(draft)){
|
||||
draft.infoBar.setVisibility(View.GONE);
|
||||
draft.errorOverlay.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||
|
@ -561,8 +638,11 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
attachment.uploadRequest=null;
|
||||
uploadingAttachment=null;
|
||||
failedAttachments.add(attachment);
|
||||
error.showToast(getActivity());
|
||||
// TODO show the error state in the attachment view
|
||||
// error.showToast(getActivity());
|
||||
Toast.makeText(getActivity(), R.string.image_upload_failed, Toast.LENGTH_SHORT).show();
|
||||
|
||||
V.setVisibilityAnimated(attachment.errorOverlay, View.VISIBLE);
|
||||
V.setVisibilityAnimated(attachment.infoBar, View.GONE);
|
||||
|
||||
if(!queuedAttachments.isEmpty())
|
||||
uploadMediaAttachment(queuedAttachments.remove(0));
|
||||
|
@ -584,9 +664,38 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
queuedAttachments.remove(att);
|
||||
failedAttachments.remove(att);
|
||||
}
|
||||
allAttachments.remove(att);
|
||||
attachmentsView.removeView(att.view);
|
||||
if(getMediaAttachmentsCount()==0)
|
||||
attachmentsView.setVisibility(View.GONE);
|
||||
updatePublishButtonState();
|
||||
pollBtn.setEnabled(attachments.isEmpty() && queuedAttachments.isEmpty() && failedAttachments.isEmpty() && uploadingAttachment==null);
|
||||
mediaBtn.setEnabled(true);
|
||||
}
|
||||
|
||||
private void onRetryMediaUploadClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(failedAttachments.remove(att)){
|
||||
V.setVisibilityAnimated(att.errorOverlay, View.GONE);
|
||||
V.setVisibilityAnimated(att.infoBar, View.VISIBLE);
|
||||
V.setVisibilityAnimated(att.progressBar, View.VISIBLE);
|
||||
if(uploadingAttachment==null)
|
||||
uploadMediaAttachment(att);
|
||||
else
|
||||
queuedAttachments.add(att);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEditMediaDescriptionClick(View v){
|
||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||
if(att.serverAttachment==null)
|
||||
return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("attachment", att.serverAttachment.id);
|
||||
args.putParcelable("uri", att.uri);
|
||||
args.putString("existingDescription", att.description);
|
||||
Nav.goForResult(getActivity(), ComposeImageDescriptionFragment.class, args, IMAGE_DESCRIPTION_RESULT, this);
|
||||
}
|
||||
|
||||
private void togglePoll(){
|
||||
|
@ -680,13 +789,22 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
}
|
||||
}
|
||||
|
||||
private static class DraftMediaAttachment{
|
||||
private int getMediaAttachmentsCount(){
|
||||
return allAttachments.size();
|
||||
}
|
||||
|
||||
@Parcel
|
||||
static class DraftMediaAttachment{
|
||||
public Attachment serverAttachment;
|
||||
public Uri uri;
|
||||
public UploadAttachment uploadRequest;
|
||||
public transient UploadAttachment uploadRequest;
|
||||
public String description;
|
||||
|
||||
public View view;
|
||||
public ProgressBar progressBar;
|
||||
public transient View view;
|
||||
public transient ProgressBar progressBar;
|
||||
public transient TextView descriptionView;
|
||||
public transient View errorOverlay;
|
||||
public transient View infoBar;
|
||||
}
|
||||
|
||||
private static class DraftPollOption{
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.UpdateAttachment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeImageDescriptionFragment extends ToolbarFragment{
|
||||
private String accountID, attachmentID;
|
||||
private EditText edit;
|
||||
private Button saveButton;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
attachmentID=getArguments().getString("attachment");
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.edit_image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
|
||||
|
||||
edit=view.findViewById(R.id.edit);
|
||||
ImageView image=view.findViewById(R.id.photo);
|
||||
Uri uri=getArguments().getParcelable("uri");
|
||||
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
||||
edit.setText(getArguments().getString("existingDescription"));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
edit.requestFocus();
|
||||
view.postDelayed(()->getActivity().getSystemService(InputMethodManager.class).showSoftInput(edit, 0), 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
TypedArray ta=getActivity().obtainStyledAttributes(new int[]{R.attr.secondaryButtonStyle});
|
||||
int buttonStyle=ta.getResourceId(0, 0);
|
||||
ta.recycle();
|
||||
saveButton=new Button(getActivity(), null, 0, buttonStyle);
|
||||
saveButton.setText(R.string.save);
|
||||
saveButton.setOnClickListener(this::onSaveClick);
|
||||
FrameLayout wrap=new FrameLayout(getActivity());
|
||||
wrap.addView(saveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
|
||||
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
||||
wrap.setClipToPadding(false);
|
||||
MenuItem item=menu.add(R.string.publish);
|
||||
item.setActionView(wrap);
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSaveClick(View v){
|
||||
new UpdateAttachment(attachmentID, edit.getText().toString().trim())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Attachment result){
|
||||
Bundle r=new Bundle();
|
||||
r.putParcelable("attachment", Parcels.wrap(result));
|
||||
setResult(true, r);
|
||||
Nav.finish(ComposeImageDescriptionFragment.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.saving, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeMediaLayout extends ViewGroup{
|
||||
private static final int MAX_WIDTH_DP=400;
|
||||
private static final int GAP_DP=8;
|
||||
private static final float ASPECT_RATIO=0.5625f;
|
||||
|
||||
public ComposeMediaLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ComposeMediaLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
int mode=MeasureSpec.getMode(widthMeasureSpec);
|
||||
@SuppressLint("SwitchIntDef")
|
||||
int width=switch(mode){
|
||||
case MeasureSpec.AT_MOST -> Math.min(V.dp(MAX_WIDTH_DP), MeasureSpec.getSize(widthMeasureSpec));
|
||||
case MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec);
|
||||
default -> throw new IllegalArgumentException("unsupported measure mode");
|
||||
};
|
||||
int height=Math.round(width*ASPECT_RATIO);
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
// We don't really need this, but some layouts will freak out if you don't measure them
|
||||
int childWidth, firstChildHeight, otherChildrenHeight=0;
|
||||
int gap=V.dp(GAP_DP);
|
||||
switch(getChildCount()){
|
||||
case 0 -> {
|
||||
return;
|
||||
}
|
||||
case 1 -> {
|
||||
childWidth=width;
|
||||
firstChildHeight=height;
|
||||
}
|
||||
case 2 -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=otherChildrenHeight=height;
|
||||
}
|
||||
case 3 -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=height;
|
||||
otherChildrenHeight=(height-gap)/2;
|
||||
}
|
||||
default -> {
|
||||
childWidth=(width-gap)/2;
|
||||
firstChildHeight=otherChildrenHeight=(height-gap)/2;
|
||||
}
|
||||
}
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
getChildAt(i).measure(childWidth | MeasureSpec.EXACTLY, (i==0 ? firstChildHeight : otherChildrenHeight) | MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||
int gap=V.dp(GAP_DP);
|
||||
int width=r-l;
|
||||
int height=b-t;
|
||||
int halfWidth=(width-gap)/2;
|
||||
int halfHeight=(height-gap)/2;
|
||||
switch(getChildCount()){
|
||||
case 0 -> {}
|
||||
case 1 -> getChildAt(0).layout(0, 0, width, height);
|
||||
case 2 -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, height);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, height);
|
||||
}
|
||||
case 3 -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, height);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, halfHeight);
|
||||
getChildAt(2).layout(halfWidth+gap, halfHeight+gap, width, height);
|
||||
}
|
||||
default -> {
|
||||
getChildAt(0).layout(0, 0, halfWidth, halfHeight);
|
||||
getChildAt(1).layout(halfWidth+gap, 0, width, halfHeight);
|
||||
getChildAt(2).layout(0, halfHeight+gap, halfWidth, height);
|
||||
getChildAt(3).layout(halfWidth+gap, halfHeight+gap, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M11.5 4c0-0.828-0.672-1.5-1.5-1.5S8.5 3.172 8.5 4h-1c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5H17c0.276 0 0.5 0.224 0.5 0.5S17.276 5 17 5h-0.554L15.15 16.23C15.033 17.237 14.179 18 13.163 18H6.837c-1.016 0-1.87-0.762-1.987-1.77L3.553 5H3C2.755 5 2.55 4.823 2.508 4.59L2.5 4.5C2.5 4.224 2.724 4 3 4h8.5zm3.938 1H4.561l1.282 11.115C5.902 16.619 6.33 17 6.837 17h6.326c0.508 0 0.935-0.38 0.993-0.885L15.438 5zM8.5 7.5c0.245 0 0.45 0.155 0.492 0.359L9 7.938v6.125c0 0.24-0.224 0.437-0.5 0.437-0.245 0-0.45-0.155-0.492-0.359L8 14.062V7.939C8 7.696 8.224 7.5 8.5 7.5zm3 0c0.245 0 0.45 0.155 0.492 0.359L12 7.938v6.125c0 0.241-0.224 0.437-0.5 0.437-0.245 0-0.45-0.155-0.492-0.359L11 14.062V7.939C11 7.696 11.224 7.5 11.5 7.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M14 2.25c2.004 0 3.641 1.572 3.745 3.55L17.75 6h5.5C23.664 6 24 6.336 24 6.75c0 0.38-0.282 0.694-0.648 0.743L23.25 7.5h-1.059l-1.22 15.053C20.813 24.5 19.187 26 17.233 26h-6.466c-1.954 0-3.58-1.5-3.738-3.447L5.808 7.5H4.75c-0.38 0-0.694-0.282-0.743-0.648L4 6.75c0-0.38 0.282-0.694 0.648-0.743L4.75 6h5.5c0-2.071 1.679-3.75 3.75-3.75zm6.687 5.25H7.313l1.211 14.932C8.619 23.6 9.594 24.5 10.767 24.5h6.466c1.172 0 2.148-0.9 2.243-2.068L20.686 7.5zm-8.937 3.75c0.38 0 0.694 0.282 0.743 0.648L12.5 12v8c0 0.414-0.336 0.75-0.75 0.75-0.38 0-0.694-0.282-0.743-0.648L11 20v-8c0-0.414 0.336-0.75 0.75-0.75zm4.5 0c0.38 0 0.694 0.282 0.743 0.648L17 12v8c0 0.414-0.336 0.75-0.75 0.75-0.38 0-0.694-0.282-0.743-0.648L15.5 20v-8c0-0.414 0.336-0.75 0.75-0.75zM14 3.75c-1.19 0-2.166 0.925-2.245 2.096L11.75 6h4.5l-0.005-0.154C16.165 4.676 15.19 3.75 14 3.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="150dp">
|
||||
|
||||
|
@ -7,20 +8,93 @@
|
|||
android:id="@+id/thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|right"
|
||||
android:text="X"/>
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_gravity="top"
|
||||
style="?android:progressBarStyleHorizontal"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/info_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="#f2000000"
|
||||
android:backgroundTint="?colorWindowBackground">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/remove_btn"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="#D92C2C"
|
||||
android:src="@drawable/ic_fluent_delete_20_regular"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_toStartOf="@id/remove_btn"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="middle"
|
||||
tools:text="asdf.jpg"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/file_name"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/add_image_description"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/error_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#cc000000"
|
||||
android:backgroundTint="?colorWindowBackground"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:visibility="gone">
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry_upload"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|bottom"
|
||||
style="?secondaryButtonStyle"
|
||||
android:text="@string/retry_upload"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/remove_btn2"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="#D92C2C"
|
||||
android:src="@drawable/ic_fluent_delete_20_regular"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true">
|
||||
|
@ -137,11 +137,12 @@
|
|||
tools:text="Duration: 7 days"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
<org.joinmastodon.android.ui.views.ComposeMediaLayout
|
||||
android:id="@+id/attachments"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ComposeMediaLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.ComposeMediaLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_headline_medium"
|
||||
android:minHeight="36dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/add_alt_text"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/alt_text_subtitle"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:minHeight="212dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:hint="@string/alt_text_hint"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -205,4 +205,13 @@
|
|||
<string name="resent_email">Confirmation email sent</string>
|
||||
<string name="compose_hint">Type or paste what\'s on your mind</string>
|
||||
<string name="content_warning">Content warning</string>
|
||||
<string name="add_image_description">Add image description…</string>
|
||||
<string name="retry_upload">Retry upload</string>
|
||||
<string name="image_upload_failed">Image failed to upload</string>
|
||||
<string name="video_upload_failed">Video failed to upload</string>
|
||||
<string name="edit_image">Edit image</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="add_alt_text">Add alt text</string>
|
||||
<string name="alt_text_subtitle">Alt text describes your photos for people with low or no vision. Try to only include enough detail to understand the context.</string>
|
||||
<string name="alt_text_hint">e.g. A dog looking around suspiciously with narrowed eyes at the camera.</string>
|
||||
</resources>
|
Loading…
Reference in New Issue