From 79d037036c1847ad2fc5b154de7b89b05236ea6d Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 27 Jun 2022 18:16:41 +0200 Subject: [PATCH] Follow Peertube & Misskey instances --- .../activities/ReorderTimelinesActivity.java | 76 +++++-- .../endpoints/MastodonTimelinesService.java | 33 ++++ .../client/entities/misskey/MisskeyNote.java | 152 ++++++++++++++ .../entities/peertube/PeertubeVideo.java | 187 ++++++++++++++++++ .../android/helper/MastodonHelper.java | 28 ++- .../timeline/FragmentMastodonTimeline.java | 96 +++++++-- .../ui/pageadapter/FedilabPageAdapter.java | 3 +- .../viewmodel/mastodon/TimelinesVM.java | 112 +++++++++++ 8 files changed, 639 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java diff --git a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java index cde71cdf..90a6c0cc 100644 --- a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java @@ -19,6 +19,7 @@ import static app.fedilab.android.helper.PinnedTimelineHelper.sortMenuItem; import static app.fedilab.android.helper.PinnedTimelineHelper.sortPositionAsc; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; @@ -37,6 +38,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -69,8 +71,10 @@ import app.fedilab.android.viewmodel.mastodon.ReorderVM; import es.dmoral.toasty.Toasty; import okhttp3.Call; import okhttp3.Callback; +import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; @@ -87,7 +91,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra private ActivityReorderTabsBinding binding; private boolean changes; private boolean bottomChanges; - + private boolean update; public void setChanges(boolean changes) { this.changes = changes; } @@ -112,10 +116,12 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra bottomChanges = false; ReorderVM reorderVM = new ViewModelProvider(ReorderTimelinesActivity.this).get(ReorderVM.class); reorderVM.getPinned().observe(ReorderTimelinesActivity.this, _pinned -> { + update = true; this.pinned = _pinned; if (this.pinned == null) { this.pinned = new Pinned(); this.pinned.pinnedTimelines = new ArrayList<>(); + update = false; } sortPositionAsc(this.pinned.pinnedTimelines); reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this, ReorderTimelinesActivity.this); @@ -153,7 +159,6 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } else if (item.getItemId() == R.id.action_add_timeline) { addInstance(); } - return super.onOptionsItemSelected(item); } @@ -168,11 +173,17 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ReorderTimelinesActivity.this, Helper.dialogStyle()); PopupSearchInstanceBinding popupSearchInstanceBinding = PopupSearchInstanceBinding.inflate(getLayoutInflater()); dialogBuilder.setView(popupSearchInstanceBinding.getRoot()); + TextWatcher textWatcher = autoComplete(popupSearchInstanceBinding); + popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher); + popupSearchInstanceBinding.setAttachmentGroup.setOnCheckedChangeListener((group, checkedId) -> { if (checkedId == R.id.twitter_accounts) { popupSearchInstanceBinding.searchInstance.setHint(R.string.list_of_twitter_accounts); + popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher); } else { popupSearchInstanceBinding.searchInstance.setHint(R.string.instance); + popupSearchInstanceBinding.searchInstance.removeTextChangedListener(textWatcher); + popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher); } }); popupSearchInstanceBinding.searchInstance.setFilters(new InputFilter[]{new InputFilter.LengthFilter(60)}); @@ -180,16 +191,24 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra String instanceName = popupSearchInstanceBinding.searchInstance.getText().toString().trim().replace("@", ""); new Thread(() -> { String url = null; - if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) + boolean getCall = true; + RequestBody formBody = new FormBody.Builder() + .build(); + if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.mastodon_instance) { url = "https://" + instanceName + "/api/v1/timelines/public?local=true"; - else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) { url = "https://" + instanceName + "/api/v1/videos/"; - else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { url = "https://" + instanceName + "/api/v1/timelines/public"; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) { url = "https://" + instanceName + "/api/notes/local-timeline"; + getCall = false; } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { url = "https://" + instanceName + "/api/statuses/public_timeline.json"; + } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this); + String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); + url = "https://" + nitterHost + "/" + instanceName.replaceAll("\\s", "") + "/rss"; } OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) @@ -198,9 +217,16 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra .readTimeout(10, TimeUnit.SECONDS).build(); Request request; if (url != null) { - request = new Request.Builder() - .url(url) - .build(); + if (getCall) { + request = new Request.Builder() + .url(url) + .build(); + } else { + request = new Request.Builder() + .url(url) + .post(formBody) + .build(); + } client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { @@ -236,13 +262,26 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.position = pinned.pinnedTimelines.size(); pinned.pinnedTimelines.add(pinnedTimeline); - try { - new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); - changes = true; - reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size()); - } catch (DBException e) { - e.printStackTrace(); + + if (update) { + try { + new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + } else { + try { + new Pinned(ReorderTimelinesActivity.this).insertPinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } } + reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size()); + Bundle b = new Bundle(); + b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); + Intent intentBD = new Intent(Helper.BROADCAST_DATA); + intentBD.putExtras(b); + LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD); }); } else { runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); @@ -267,7 +306,11 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra popupSearchInstanceBinding.searchInstance.setOnItemClickListener((parent, view1, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); - popupSearchInstanceBinding.searchInstance.addTextChangedListener(new TextWatcher() { + + } + + private TextWatcher autoComplete(PopupSearchInstanceBinding popupSearchInstanceBinding) { + return new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -314,8 +357,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } } } - }); - + }; } @Override diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java index 01dfb83f..69850f5b 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java @@ -21,12 +21,16 @@ import app.fedilab.android.client.entities.api.Conversation; import app.fedilab.android.client.entities.api.Marker; import app.fedilab.android.client.entities.api.MastodonList; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.misskey.MisskeyNote; +import app.fedilab.android.client.entities.peertube.PeertubeVideo; import retrofit2.Call; +import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Header; +import retrofit2.http.Headers; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Path; @@ -191,4 +195,33 @@ public interface MastodonTimelinesService { @Field("home[last_read_id]") String home_last_read_id, @Field("notifications[last_read_id]") String notifications_last_read_id ); + + + @Headers({"Accept: application/json"}) + @POST("api/notes") + Call> getMisskey(@Body MisskeyNote.MisskeyParams params); + + + //Public timelines for Misskey + @FormUrlEncoded + @POST("api/notes") + Call> getMisskey( + @Field("local") boolean local, + @Field("file") boolean file, + @Field("poll") boolean poll, + @Field("remote") boolean remote, + @Field("reply") boolean reply, + @Field("untilId") String max_id, + @Field("since_id") String since_id, + @Field("limit") Integer limit + ); + + @GET("api/v1/videos") + Call getPeertube( + @Query("start") String start, + @Query("filter") String filter, + @Query("sort") String sort, + @Query("count") int count + ); + } diff --git a/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java b/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java new file mode 100644 index 00000000..3b2bcda0 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/misskey/MisskeyNote.java @@ -0,0 +1,152 @@ +package app.fedilab.android.client.entities.misskey; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.Status; + + +@SuppressWarnings("ALL") +public class MisskeyNote implements Serializable { + + @SerializedName("id") + public String id; + @SerializedName("createdAt") + public Date createdAt; + @SerializedName("replyId") + public String replyId; + @SerializedName("cw") + public String cw; + @SerializedName("text") + public String text; + @SerializedName("url") + public String url; + @SerializedName("uri") + public String uri; + @SerializedName("visibility") + public String visibility; + @SerializedName("repliesCount") + public int repliesCount; + @SerializedName("user") + public MisskeyUser user; + @SerializedName("files") + public List files; + @SerializedName("emojis") + public List emojis; + + public static Status convert(MisskeyNote misskeyNote) { + Status status = new Status(); + status.id = misskeyNote.id; + status.in_reply_to_id = misskeyNote.replyId; + status.content = misskeyNote.text != null ? misskeyNote.text : ""; + status.text = misskeyNote.text != null ? misskeyNote.text : ""; + status.spoiler_text = misskeyNote.cw; + status.visibility = misskeyNote.visibility; + status.created_at = misskeyNote.createdAt; + status.uri = misskeyNote.uri; + status.url = misskeyNote.url; + + Account account = new Account(); + account.id = misskeyNote.user.id; + account.acct = misskeyNote.user.username; + account.username = misskeyNote.user.username; + account.display_name = misskeyNote.user.name; + account.avatar = misskeyNote.user.avatarUrl; + account.avatar_static = misskeyNote.user.avatarUrl; + status.account = account; + + if (misskeyNote.files != null && misskeyNote.files.size() > 0) { + List attachmentList = new ArrayList<>(); + for (MisskeyFile misskeyFile : misskeyNote.files) { + Attachment attachment = new Attachment(); + attachment.type = misskeyFile.type; + attachment.description = misskeyFile.comment; + attachment.url = misskeyFile.url; + attachment.preview_url = misskeyFile.thumbnailUrl; + if (misskeyFile.isSensitive) { + status.sensitive = true; + } + attachmentList.add(attachment); + } + status.media_attachments = attachmentList; + } + + return status; + } + + public static class MisskeyUser implements Serializable { + @SerializedName("id") + public String id; + @SerializedName("name") + public String name; + @SerializedName("username") + public String username; + @SerializedName("avatarUrl") + public String avatarUrl; + @SerializedName("emojis") + public List emojis; + } + + public static class MisskeyFile implements Serializable { + @SerializedName("id") + public String id; + @SerializedName("comment") + public String comment; + @SerializedName("isSensitive") + public boolean isSensitive; + @SerializedName("thumbnailUrl") + public String thumbnailUrl; + @SerializedName("url") + public String url; + @SerializedName("type") + public String type; + } + + public static class MisskeyEmoji implements Serializable { + @SerializedName("name") + public String name; + @SerializedName("comment") + public String url; + } + + public static class MisskeyParams implements Serializable { + @SerializedName("local") + public boolean local = true; + @SerializedName("file") + public boolean file = false; + @SerializedName("poll") + public boolean poll = false; + @SerializedName("remote") + public boolean remote = false; + @SerializedName("reply") + public boolean reply = false; + @SerializedName("max_id") + public String max_id; + @SerializedName("since_id") + public String since_id; + @SerializedName("limit") + public int limit; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java new file mode 100644 index 00000000..90f32381 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java @@ -0,0 +1,187 @@ +package app.fedilab.android.client.entities.peertube; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.Status; + +@SuppressWarnings("ALL") +public class PeertubeVideo implements Serializable { + + @SerializedName("total") + public int total; + @SerializedName("data") + public List