Previewing posts on Akkoma (#933)

* Previewing posts on Akkoma

* move preview property into status

* clean up code

* revert imm-related changes

---------

Co-authored-by: sk <sk22@mailbox.org>
This commit is contained in:
Jacoco 2023-11-15 15:17:29 +01:00 committed by GitHub
parent 786ce78b08
commit 6b234209c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 101 additions and 31 deletions

View File

@ -48,6 +48,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String quoteId;
public ContentType contentType;
public boolean preview;
public static class Poll{
public ArrayList<String> options=new ArrayList<>();
public int expiresIn;

View File

@ -214,7 +214,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private BackgroundColorSpan overLimitBG;
private ForegroundColorSpan overLimitFG;
public ComposeFragment(){
super(R.layout.toolbar_fragment_with_progressbar);
}
@ -807,25 +807,28 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
actionItem.setActionView(wrap);
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
draftsBtn = wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
draftsBtn=wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more);
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
Menu draftOptionsMenu=draftOptionsPopup.getMenu();
draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{
int id = i.getItemId();
if (id == R.id.draft) updateScheduledAt(getDraftInstant());
else if (id == R.id.schedule) pickScheduledDateTime();
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
else navigateToUnsentPosts();
int id=i.getItemId();
if(id==R.id.draft) updateScheduledAt(getDraftInstant());
else if(id==R.id.schedule) pickScheduledDateTime();
else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
else if(id==R.id.drafts) navigateToUnsentPosts();
else if(id==R.id.preview) publish(true);
return true;
});
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
publishButton=wrap.findViewById(R.id.publish_btn);
languageButton=wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
if(!getLocalPrefs().bottomEncoding){
@ -1051,6 +1054,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void publish(){
publish(false);
}
private void publish(boolean preview){
sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@ -1064,10 +1071,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton.setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
mediaViewController.saveAltTextsBeforePublishing(
()->actuallyPublish(preview),
this::handlePublishError);
}
private void actuallyPublish(){
private void actuallyPublish(boolean preview){
String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request();
if("bottom".equals(postLang.encoding)){
@ -1085,6 +1094,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt;
req.preview=preview;
if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){
@ -1112,7 +1122,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback<Status> resCallback=new Callback<>(){
@Override
public void onSuccess(Status result){
maybeDeleteScheduledPost(() -> {
if(preview){
openPreview(result);
return;
}
maybeDeleteScheduledPost(()->{
wm.removeView(sendingOverlay);
sendingOverlay=null;
if(editingStatus==null || redraftStatus){
@ -1134,10 +1149,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
E.post(new StatusUpdatedEvent(editedStatus));
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()){
Nav.finish(ComposeFragment.this);
}
if (getArguments().getBoolean("navigateToStatus", false)) {
if(getArguments().getBoolean("navigateToStatus", false)){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
@ -1153,11 +1168,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
};
if(editingStatus!=null && !redraftStatus){
if(editingStatus!=null && !redraftStatus && !preview){
new EditStatus(req, editingStatus.id)
.setCallback(resCallback)
.exec(accountID);
}else if(req.scheduledAt == null){
}else if(req.scheduledAt == null || preview){
new CreateStatus(req, uuid)
.setCallback(resCallback)
.exec(accountID);
@ -1210,6 +1225,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
private void openPreview(Status result){
result.preview=true;
wm.removeView(sendingOverlay);
sendingOverlay=null;
publishButton.setEnabled(true);
V.setVisibilityAnimated(sendProgress, View.GONE);
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null){
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
}
Nav.go(getActivity(), ThreadFragment.class, args);
}
private void updateRecentLanguages() {
if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage();
@ -1528,6 +1562,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
contentTypePopup.setOnMenuItemClickListener(i->{
uuid=null;
int index=i.getItemId();
contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());

View File

@ -84,8 +84,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
@Override
public void onItemClick(String id){
Status status=getContentStatusByID(id);
if(status==null)
return;
if(status==null || status.preview) return;
status.filterRevealed=true;
Bundle args=new Bundle();
args.putString("account", accountID);

View File

@ -50,22 +50,25 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus, updatedStatus;
protected Status mainStatus, updatedStatus, replyTo;
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished;
protected boolean contextInitiallyRendered, transitionFinished, preview;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
if(inReplyToAccount!=null)
knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
preview=mainStatus.preview;
if(preview) setRefreshEnabled(false);
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false);
}
@ -130,11 +133,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override
protected void doLoadData(int offset, int count){
if (refreshing) loadMainStatus();
currentRequest=new GetStatusContext(mainStatus.id)
if(preview && replyTo==null){
result=new StatusContext();
result.descendants=Collections.emptyList();
result.ancestors=Collections.emptyList();
return;
}
if(refreshing && !preview) loadMainStatus();
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(StatusContext result){
if(preview){
result.descendants=Collections.emptyList();
result.ancestors.add(replyTo);
}
ThreadFragment.this.result = result;
maybeApplyContext();
}

View File

@ -97,6 +97,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public transient boolean fromStatusCreated;
public transient boolean preview;
public Status(){}

View File

@ -129,6 +129,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}
private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){
if(item.status.preview) return;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("status", Parcels.wrap(item.status));
@ -136,6 +137,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}
private void startEditHistoryFragment(){
if(item.status.preview) return;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putString("id", item.status.id);

View File

@ -158,6 +158,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onButtonTouch(View v, MotionEvent event){
if(item.status.preview) return false;
boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame &&
parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled());
int action = event.getAction();
@ -180,6 +181,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onReplyClick(View v){
if(item.status.preview) return;
UiUtils.opacityIn(v);
Bundle args=new Bundle();
args.putString("account", item.accountID);
@ -188,6 +190,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onReplyLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
Bundle args=new Bundle();
@ -203,6 +206,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onBoostClick(View v){
if(item.status.preview) return;
if (GlobalUserPreferences.confirmBoost) {
UiUtils.opacityIn(v);
onBoostLongClick(v);
@ -218,6 +222,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onBoostLongClick(View v){
if(item.status.preview) return false;
Context ctx = itemView.getContext();
View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null);
Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create();
@ -300,6 +305,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onFavoriteClick(View v){
if(item.status.preview) return;
favorite.setSelected(!item.status.favourited);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
UiUtils.opacityIn(v);
@ -308,6 +314,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onFavoriteLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status,
@ -322,6 +329,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onBookmarkClick(View v){
if(item.status.preview) return;
bookmark.setSelected(!item.status.bookmarked);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
UiUtils.opacityIn(v);
@ -329,6 +337,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onBookmarkLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status,
@ -343,6 +352,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onShareClick(View v){
if(item.status.preview) return;
UiUtils.opacityIn(v);
Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
@ -351,6 +361,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onShareLongClick(View v){
if(item.status.preview) return false;
UiUtils.copyText(v, item.status.url);
return true;
}

View File

@ -445,6 +445,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
private void onMoreClick(View v){
if(item.status.preview) return;
updateOptionsMenu();
optionsMenu.show();
if(relationship==null && currentRelationshipRequest==null){

View File

@ -76,7 +76,7 @@ public abstract class StatusDisplayItem{
public static final int FLAG_NO_TRANSLATE=1 << 5;
public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6;
public static final int FLAG_IS_FOR_QUOTE=1 << 7;
public void setAncestryInfo(
boolean hasDescendantNeighbor,
boolean hasAncestoringNeighbor,
@ -166,7 +166,7 @@ public abstract class StatusDisplayItem{
HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
if((flags & FLAG_NO_HEADER)==0){
ReblogOrReplyLineStatusDisplayItem replyLine = null;
boolean threadReply = statusForContent.inReplyToAccountId != null &&
@ -305,7 +305,7 @@ public abstract class StatusDisplayItem{
items.addAll(contentItems);
}
AccountLocalPreferences lp=fragment.getLocalPrefs();
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled &&
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
statusForContent.reactions!=null){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M16.088 6.412c-0.072-0.093-0.15-0.182-0.234-0.266-0.312-0.313-0.693-0.55-1.113-0.69L13.363 5.01c-0.106-0.037-0.198-0.107-0.263-0.198C13.035 4.719 13 4.609 13 4.497c0-0.113 0.035-0.223 0.1-0.315 0.065-0.091 0.157-0.16 0.263-0.198l1.378-0.448c0.414-0.143 0.789-0.379 1.096-0.69 0.299-0.304 0.525-0.67 0.663-1.072l0.011-0.034 0.448-1.377c0.038-0.106 0.107-0.198 0.2-0.263 0.091-0.065 0.2-0.1 0.313-0.1 0.113 0 0.223 0.035 0.315 0.1s0.161 0.157 0.199 0.263l0.447 1.377c0.14 0.418 0.375 0.798 0.687 1.11 0.312 0.312 0.693 0.547 1.111 0.686l1.378 0.448 0.028 0.007c0.106 0.037 0.198 0.107 0.263 0.198 0.065 0.092 0.1 0.202 0.1 0.314 0 0.113-0.035 0.223-0.1 0.315-0.065 0.091-0.157 0.16-0.263 0.198l-1.378 0.448c-0.419 0.139-0.8 0.374-1.112 0.686-0.312 0.311-0.547 0.692-0.686 1.11l-0.448 1.377L18 8.671c-0.04 0.092-0.104 0.171-0.186 0.23C17.722 8.964 17.613 9 17.5 9c-0.113 0-0.222-0.035-0.314-0.1s-0.162-0.157-0.2-0.263L16.54 7.26c-0.101-0.307-0.254-0.593-0.45-0.848zm7.695 3.801l-0.766-0.248c-0.232-0.078-0.444-0.208-0.617-0.381-0.173-0.174-0.304-0.385-0.381-0.617l-0.25-0.765c-0.02-0.06-0.059-0.11-0.11-0.146C21.61 8.018 21.547 8 21.485 8c-0.063 0-0.124 0.02-0.175 0.056-0.051 0.036-0.09 0.087-0.11 0.146l-0.25 0.764c-0.075 0.231-0.203 0.442-0.374 0.615-0.17 0.173-0.379 0.304-0.609 0.384l-0.765 0.248c-0.06 0.021-0.11 0.06-0.147 0.11C19.02 10.376 19 10.437 19 10.499c0 0.063 0.02 0.124 0.055 0.175 0.037 0.05 0.088 0.09 0.147 0.11l0.765 0.249c0.233 0.077 0.445 0.208 0.619 0.382 0.173 0.174 0.303 0.386 0.38 0.62l0.249 0.764c0.02 0.06 0.06 0.11 0.11 0.146C21.376 12.982 21.437 13 21.5 13c0.063 0 0.124-0.02 0.175-0.056 0.05-0.036 0.09-0.087 0.11-0.146l0.249-0.764c0.077-0.233 0.208-0.444 0.381-0.618 0.174-0.173 0.385-0.303 0.618-0.38l0.765-0.25c0.06-0.02 0.11-0.059 0.147-0.11C23.98 10.626 24 10.564 24 10.502c0-0.063-0.02-0.124-0.055-0.175-0.037-0.05-0.088-0.09-0.147-0.11l-0.015-0.004zM5.25 3h7.797c-0.306 0.11-0.573 0.308-0.767 0.569C12.098 3.834 12 4.148 12 4.469V4.5H5.25C4.836 4.5 4.5 4.836 4.5 5.25v12.5c0 0.966 0.784 1.75 1.75 1.75h9.25V7.327c0.034 0.072 0.065 0.146 0.09 0.222L16 8.999c0.106 0.31 0.305 0.579 0.57 0.77 0.133 0.092 0.278 0.162 0.43 0.21V14h4v3.75c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V5.25C3 4.007 4.007 3 5.25 3zM17 19.5h0.75c0.966 0 1.75-0.784 1.75-1.75V15.5H17v4zM7.25 7C6.836 7 6.5 7.336 6.5 7.75S6.836 8.5 7.25 8.5h5.5c0.414 0 0.75-0.336 0.75-0.75S13.164 7 12.75 7h-5.5zM6.5 11.75C6.5 11.336 6.836 11 7.25 11h5.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-5.5c-0.414 0-0.75-0.336-0.75-0.75zM7.25 15c-0.414 0-0.75 0.336-0.75 0.75s0.336 0.75 0.75 0.75h3c0.414 0 0.75-0.336 0.75-0.75S10.664 15 10.25 15h-3z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -5,4 +5,5 @@
<item android:id="@+id/draft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_regular" />
<item android:id="@+id/undraft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_filled" android:contentDescription="@string/sk_compose_no_draft" />
<item android:id="@+id/drafts" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular" />
<item android:id="@+id/preview" android:title="@string/sk_open_post_preview" android:icon="@drawable/ic_fluent_receipt_sparkles_24_regular" android:visible="false" />
</menu>

View File

@ -433,4 +433,6 @@
<string name="sk_settings_copy_crash_log">Copy latest crash log</string>
<string name="sk_settings_crash_log_unavailable">None available… yet</string>
<string name="sk_crash_log_copied">Crash log copied</string>
<string name="sk_open_post_preview">Preview post</string>
<string name="sk_post_preview">Preview</string>
</resources>