From b3a99e07649dd3081432b904cd928caea4b080c5 Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 17 Jan 2022 21:50:48 +0300 Subject: [PATCH] Tooting of text toots --- mastodon/build.gradle | 4 +- mastodon/src/main/AndroidManifest.xml | 2 +- .../main/java/org/joinmastodon/android/E.java | 22 +++++ .../android/api/MastodonAPIController.java | 7 ++ .../android/api/MastodonAPIRequest.java | 7 ++ .../api/requests/statuses/CreateStatus.java | 36 ++++++++ .../android/events/StatusCreatedEvent.java | 11 +++ .../android/fragments/CreateTootFragment.java | 88 +++++++++++++++++++ .../fragments/HomeTimelineFragment.java | 41 +++++++++ .../android/fragments/StatusListFragment.java | 14 ++- .../joinmastodon/android/model/Status.java | 8 +- .../ui/displayitems/StatusDisplayItem.java | 3 +- .../src/main/res/layout/fragment_new_toot.xml | 28 ++++++ 13 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/E.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/events/StatusCreatedEvent.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/CreateTootFragment.java create mode 100644 mastodon/src/main/res/layout/fragment_new_toot.xml diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 5e49a151..eb899785 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -32,8 +32,10 @@ dependencies { implementation 'me.grishka.litex:recyclerview:1.2.1' implementation 'me.grishka.litex:swiperefreshlayout:1.1.0' implementation 'me.grishka.litex:browser:1.4.0' - implementation 'me.grishka.appkit:appkit:1.1' + implementation 'me.grishka.appkit:appkit:1.1.1' implementation 'com.google.code.gson:gson:2.8.9' implementation 'org.jsoup:jsoup:1.14.3' + implementation 'com.squareup:otto:1.3.8' + implementation 'de.psdev:async-otto:1.0.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' } \ No newline at end of file diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml index 5a6bbaf2..ad647361 100644 --- a/mastodon/src/main/AndroidManifest.xml +++ b/mastodon/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:theme="@style/Theme.Mastodon" android:largeHeap="true"> - + diff --git a/mastodon/src/main/java/org/joinmastodon/android/E.java b/mastodon/src/main/java/org/joinmastodon/android/E.java new file mode 100644 index 00000000..40a3dcf9 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/E.java @@ -0,0 +1,22 @@ +package org.joinmastodon.android; + +import com.squareup.otto.AsyncBus; + +/** + * Created by grishka on 24.08.15. + */ +public class E{ + private static AsyncBus bus=new AsyncBus(); + + public static void post(Object event){ + bus.post(event); + } + + public static void register(Object listener){ + bus.register(listener); + } + + public static void unregister(Object listener){ + bus.unregister(listener); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java index 92e3357e..6702c720 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java @@ -25,6 +25,7 @@ import java.io.Reader; import java.time.Instant; import java.time.LocalDate; import java.util.List; +import java.util.Map; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -76,6 +77,12 @@ public class MastodonAPIController{ if(token!=null) builder.header("Authorization", "Bearer "+token); + if(req.headers!=null){ + for(Map.Entry header:req.headers.entrySet()){ + builder.header(header.getKey(), header.getValue()); + } + } + Request hreq=builder.build(); Call call=httpClient.newCall(hreq); synchronized(req){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java index 1c87b1a7..c8de46d2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java @@ -33,6 +33,7 @@ public abstract class MastodonAPIRequest extends APIRequest{ Call okhttpCall; Token token; boolean canceled; + Map headers; public MastodonAPIRequest(HttpMethod method, String path, Class respClass){ this.path=path; @@ -89,6 +90,12 @@ public abstract class MastodonAPIRequest extends APIRequest{ queryParams.put(key, value); } + protected void addHeader(String key, String value){ + if(headers==null) + headers=new HashMap<>(); + headers.put(key, value); + } + protected String getPathPrefix(){ return "/api/v1"; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java new file mode 100644 index 00000000..bdb9dd14 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java @@ -0,0 +1,36 @@ +package org.joinmastodon.android.api.requests.statuses; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.model.StatusPrivacy; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +public class CreateStatus extends MastodonAPIRequest{ + public CreateStatus(CreateStatus.Request req, String uuid){ + super(HttpMethod.POST, "/statuses", Status.class); + setRequestBody(req); + addHeader("Idempotency-Key", uuid); + } + + public static class Request{ + public String status; + public List mediaIds; + public Poll poll; + public String inReplyToId; + public boolean sensitive; + public String spoilerText; + public StatusPrivacy visibility; + public Instant scheduledAt; + public String language; + + public static class Poll{ + public ArrayList options=new ArrayList<>(); + public int expiresIn; + public boolean multiple; + public boolean hideTotals; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/events/StatusCreatedEvent.java b/mastodon/src/main/java/org/joinmastodon/android/events/StatusCreatedEvent.java new file mode 100644 index 00000000..cacb161f --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/events/StatusCreatedEvent.java @@ -0,0 +1,11 @@ +package org.joinmastodon.android.events; + +import org.joinmastodon.android.model.Status; + +public class StatusCreatedEvent{ + public Status status; + + public StatusCreatedEvent(Status status){ + this.status=status; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateTootFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateTootFragment.java new file mode 100644 index 00000000..2d140a8f --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateTootFragment.java @@ -0,0 +1,88 @@ +package org.joinmastodon.android.fragments; + +import android.app.Activity; +import android.os.Bundle; +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.EditText; + +import org.joinmastodon.android.E; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.statuses.CreateStatus; +import org.joinmastodon.android.events.StatusCreatedEvent; +import org.joinmastodon.android.model.Status; + +import java.util.UUID; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; +import me.grishka.appkit.fragments.ToolbarFragment; + +public class CreateTootFragment extends ToolbarFragment{ + + private EditText mainEditText; + private String accountID; + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + setHasOptionsMenu(true); + accountID=getArguments().getString("account"); + } + + @Override + public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + View view=inflater.inflate(R.layout.fragment_new_toot, container, false); + mainEditText=view.findViewById(R.id.toot_text); + return view; + } + + @Override + public void onResume(){ + super.onResume(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class); + view.postDelayed(()->{ + mainEditText.requestFocus(); + imm.showSoftInput(mainEditText, 0); + }, 100); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + menu.add("TOOT!").setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + String text=mainEditText.getText().toString(); + CreateStatus.Request req=new CreateStatus.Request(); + req.status=text; + String uuid=UUID.randomUUID().toString(); + new CreateStatus(req, uuid) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Status result){ + Nav.finish(CreateTootFragment.this); + E.post(new StatusCreatedEvent(result)); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(getActivity()); + } + }) + .exec(accountID); + return true; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index ec677007..385a1acb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -1,13 +1,23 @@ package org.joinmastodon.android.fragments; import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import com.squareup.otto.Subscribe; + +import org.joinmastodon.android.E; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline; +import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.model.Status; +import java.util.Collections; import java.util.List; +import me.grishka.appkit.Nav; import me.grishka.appkit.api.SimpleCallback; public class HomeTimelineFragment extends StatusListFragment{ @@ -17,6 +27,7 @@ public class HomeTimelineFragment extends StatusListFragment{ public void onAttach(Activity activity){ super.onAttach(activity); setTitle(R.string.app_name); + setHasOptionsMenu(true); accountID=getArguments().getString("account"); loadData(); } @@ -32,4 +43,34 @@ public class HomeTimelineFragment extends StatusListFragment{ }) .exec(accountID); } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + menu.add("New toot"); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + Bundle args=new Bundle(); + args.putString("account", accountID); + Nav.go(getActivity(), CreateTootFragment.class, args); + return true; + } + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + E.register(this); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + E.unregister(this); + } + + @Subscribe + public void onStatusCreated(StatusCreatedEvent ev){ + prependItems(Collections.singletonList(ev.status)); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java index cc5f4000..b3df11ae 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java @@ -18,6 +18,7 @@ import me.grishka.appkit.views.UsableRecyclerView; public abstract class StatusListFragment extends BaseRecyclerFragment{ protected ArrayList displayItems=new ArrayList<>(); + private DisplayItemsAdapter adapter; public StatusListFragment(){ super(20); @@ -25,7 +26,7 @@ public abstract class StatusListFragment extends BaseRecyclerFragment{ @Override protected RecyclerView.Adapter getAdapter(){ - return new DisplayItemsAdapter(); + return adapter=new DisplayItemsAdapter(); } @Override @@ -42,6 +43,17 @@ public abstract class StatusListFragment extends BaseRecyclerFragment{ displayItems.clear(); } + protected void prependItems(List items){ + data.addAll(0, items); + int offset=0; + for(Status s:items){ + List toAdd=StatusDisplayItem.buildItems(this, s); + displayItems.addAll(offset, toAdd); + offset+=toAdd.size(); + } + adapter.notifyItemRangeInserted(0, offset); + } + protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter> implements ImageLoaderRecyclerAdapter{ public DisplayItemsAdapter(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index b9406cce..39961ffb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.model; import android.text.Html; import android.text.TextUtils; +import android.util.Log; import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.RequiredField; @@ -54,8 +55,6 @@ public class Status extends BaseModel{ public boolean bookmarked; public boolean pinned; - public transient CharSequence processedContent; - @Override public void postprocess() throws ObjectValidationException{ super.postprocess(); @@ -76,10 +75,6 @@ public class Status extends BaseModel{ card.postprocess(); if(reblog!=null) reblog.postprocess(); - - if(!TextUtils.isEmpty(content)){ - processedContent=HtmlParser.parse(content, emojis); - } } @Override @@ -114,7 +109,6 @@ public class Status extends BaseModel{ ", muted="+muted+ ", bookmarked="+bookmarked+ ", pinned="+pinned+ - ", processedContent="+processedContent+ '}'; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 2b90d630..e3fd309e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -7,6 +7,7 @@ import android.view.ViewGroup; import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.text.HtmlParser; import java.util.ArrayList; import java.util.List; @@ -49,7 +50,7 @@ public abstract class StatusDisplayItem{ } items.add(new HeaderStatusDisplayItem(status, statusForContent.account, statusForContent.createdAt)); if(!TextUtils.isEmpty(statusForContent.content)) - items.add(new TextStatusDisplayItem(status, statusForContent.processedContent, fragment)); + items.add(new TextStatusDisplayItem(status, HtmlParser.parse(statusForContent.content, statusForContent.emojis), fragment)); for(Attachment attachment:statusForContent.mediaAttachments){ if(attachment.type==Attachment.Type.IMAGE){ items.add(new PhotoStatusDisplayItem(status, attachment)); diff --git a/mastodon/src/main/res/layout/fragment_new_toot.xml b/mastodon/src/main/res/layout/fragment_new_toot.xml new file mode 100644 index 00000000..74172bc0 --- /dev/null +++ b/mastodon/src/main/res/layout/fragment_new_toot.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file