Follow Peertube & Misskey instances

This commit is contained in:
Thomas 2022-06-27 18:16:41 +02:00
parent e32101a6bb
commit 79d037036c
8 changed files with 639 additions and 48 deletions

View File

@ -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

View File

@ -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<List<MisskeyNote>> getMisskey(@Body MisskeyNote.MisskeyParams params);
//Public timelines for Misskey
@FormUrlEncoded
@POST("api/notes")
Call<List<MisskeyNote>> 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<PeertubeVideo> getPeertube(
@Query("start") String start,
@Query("filter") String filter,
@Query("sort") String sort,
@Query("count") int count
);
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<MisskeyFile> files;
@SerializedName("emojis")
public List<MisskeyEmoji> 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<Attachment> 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<MisskeyEmoji> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Video> data;
public static Status convert(Video peertubeVideo) {
Status status = new Status();
status.id = peertubeVideo.id;
status.content = peertubeVideo.description != null ? peertubeVideo.description : "";
status.text = peertubeVideo.description;
status.visibility = "public";
status.created_at = peertubeVideo.publishedAt;
status.uri = peertubeVideo.uuid;
status.sensitive = peertubeVideo.nsfw;
status.url = "https://" + peertubeVideo.account.host + "/videos/watch/" + peertubeVideo.uuid;
Account account = new Account();
account.id = peertubeVideo.channel.id;
account.acct = peertubeVideo.channel.name;
account.username = peertubeVideo.channel.name;
account.display_name = peertubeVideo.channel.displayName;
if (peertubeVideo.channel.avatar != null) {
account.avatar = "https://" + peertubeVideo.account.host + peertubeVideo.channel.avatar.path;
account.avatar_static = "https://" + peertubeVideo.account.host + peertubeVideo.channel.avatar.path;
}
status.account = account;
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.type = "video/mp4";
attachment.url = "https://" + peertubeVideo.account.host + peertubeVideo.embedPath;
attachment.preview_url = "https://" + peertubeVideo.account.host + peertubeVideo.thumbnailPath;
attachmentList.add(attachment);
status.media_attachments = attachmentList;
return status;
}
public static class Video implements Serializable {
@SerializedName("account")
public PeertubeAccount account;
@SerializedName("category")
public Item category;
@SerializedName("channel")
public Channel channel;
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("description")
public String description;
@SerializedName("duration")
public int duration;
@SerializedName("embedPath")
public String embedPath;
@SerializedName("id")
public String id;
@SerializedName("isLive")
public boolean isLive = false;
@SerializedName("url")
public String url;
@SerializedName("isLocal")
public boolean isLocal;
@SerializedName("language")
public ItemStr language;
@SerializedName("licence")
public Item licence;
@SerializedName("likes")
public int likes;
@SerializedName("name")
public String name;
@SerializedName("nsfw")
public boolean nsfw;
@SerializedName("originallyPublishedAt")
public Date originallyPublishedAt;
@SerializedName("previewPath")
public String previewPath;
@SerializedName("privacy")
public Item privacy;
@SerializedName("publishedAt")
public Date publishedAt;
@SerializedName("thumbnailPath")
public String thumbnailPath;
@SerializedName("updatedAt")
public Date updatedAt;
@SerializedName("uuid")
public String uuid;
@SerializedName("views")
public int views;
}
public static class PeertubeAccount implements Serializable {
@SerializedName("avatar")
public Avatar avatar;
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("description")
public String description;
@SerializedName("displayName")
public String displayName;
@SerializedName("followersCount")
public int followersCount;
@SerializedName("followingCount")
public int followingCount;
@SerializedName("host")
public String host;
@SerializedName("hostRedundancyAllowed")
public boolean hostRedundancyAllowed;
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("username")
public String username;
@SerializedName("updatedAt")
public Date updatedAt;
@SerializedName("url")
public String url;
@SerializedName("userId")
public String userId;
}
public static class Item implements Serializable {
@SerializedName("id")
public int id;
@SerializedName("label")
public String label;
}
public static class ItemStr implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("label")
public String label;
}
public static class Channel implements Serializable {
@SerializedName("avatar")
public Avatar avatar;
@SerializedName("displayName")
public String displayName;
@SerializedName("host")
public String host;
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("url")
public String url;
}
public static class Avatar implements Serializable {
@SerializedName("createdAt")
public Date createdAt;
@SerializedName("path")
public String path;
@SerializedName("updatedAt")
public Date updatedAt;
}
}

View File

@ -213,19 +213,27 @@ public class MastodonHelper {
return;
}
String targetedUrl = disableGif ? (type == MediaAccountType.AVATAR ? account.avatar_static : account.header_static) : (type == MediaAccountType.AVATAR ? account.avatar : account.header);
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
Glide.with(view.getContext())
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
if (targetedUrl != null) {
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
Glide.with(view.getContext())
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
} else {
Glide.with(view.getContext())
.asGif()
.load(targetedUrl)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
}
} else {
Glide.with(view.getContext())
.asGif()
.load(targetedUrl)
.asDrawable()
.load(placeholder)
.thumbnail(0.1f)
.placeholder(placeholder)
.into(view);
}
}

View File

@ -48,7 +48,9 @@ import app.fedilab.android.client.entities.api.Marker;
import app.fedilab.android.client.entities.api.Pagination;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.QuickLoad;
import app.fedilab.android.client.entities.app.RemoteInstance;
import app.fedilab.android.client.entities.app.TagTimeline;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.FragmentPaginationBinding;
@ -136,6 +138,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private Account accountTimeline;
private boolean exclude_replies, exclude_reblogs, show_pinned, media_only, minified;
private String viewModelKey, remoteInstance;
private PinnedTimeline pinnedTimeline;
private String ident;
private String instance, user_id;
private ArrayList<String> idOfAddedStatuses;
@ -199,7 +202,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
list_id = getArguments().getString(Helper.ARG_LIST_ID, null);
search = getArguments().getString(Helper.ARG_SEARCH_KEYWORD, null);
searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null);
remoteInstance = getArguments().getString(Helper.ARG_REMOTE_INSTANCE, null);
pinnedTimeline = (PinnedTimeline) getArguments().getSerializable(Helper.ARG_REMOTE_INSTANCE);
if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) {
remoteInstance = pinnedTimeline.remoteInstance.host;
}
tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE);
accountTimeline = (Account) getArguments().getSerializable(Helper.ARG_ACCOUNT);
exclude_replies = !getArguments().getBoolean(Helper.ARG_SHOW_REPLIES, true);
@ -632,24 +639,75 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
});
}
} else if (timelineType == Timeline.TimeLineEnum.REMOTE) { //REMOTE TIMELINE
if (direction == null) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
//NITTER TIMELINES
if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) {
} //GNU TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.GNU) {
}//MISSKEY TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.MISSKEY) {
if (direction == null) {
timelinesVM.getMisskey(remoteInstance, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getMisskey(remoteInstance, max_id, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
timelinesVM.getMisskey(remoteInstance, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getMisskey(remoteInstance, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
} //PEERTUBE TIMELINES
else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) {
if (direction == null) {
timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPeertube(remoteInstance, max_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
} else { //Other remote timelines
if (direction == null) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} else if (direction == DIRECTION.BOTTOM) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
});
}
}
} else if (timelineType == Timeline.TimeLineEnum.LIST) { //LIST TIMELINE
if (direction == null) {

View File

@ -85,8 +85,7 @@ public class FedilabPageAdapter extends FragmentStateAdapter {
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.TAG) {
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, pinnedTimeline.tagTimeline);
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.REMOTE) {
String instance = pinnedTimeline.remoteInstance.host;
bundle.putString(Helper.ARG_REMOTE_INSTANCE, instance);
bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline);
}
}

View File

@ -27,6 +27,7 @@ import androidx.lifecycle.MutableLiveData;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -43,6 +44,8 @@ import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.BaseAccount;
import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.client.entities.misskey.MisskeyNote;
import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
@ -77,6 +80,15 @@ public class TimelinesVM extends AndroidViewModel {
super(application);
}
private MastodonTimelinesService initInstanceOnly(String instance) {
Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
return retrofit.create(MastodonTimelinesService.class);
}
private MastodonTimelinesService init(String instance) {
Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create();
@ -134,6 +146,106 @@ public class TimelinesVM extends AndroidViewModel {
}
/**
* Public timeline for Misskey
*
* @param maxId Return results older than this id
* @param sinceId Return results newer than this id
* @param limit Maximum number of results to return. Defaults to 20.
* @return {@link LiveData} containing a {@link Statuses}
*/
public LiveData<Statuses> getMisskey(@NonNull String instance,
String maxId,
String sinceId,
Integer limit) {
MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance);
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
MisskeyNote.MisskeyParams misskeyParams = new MisskeyNote.MisskeyParams();
misskeyParams.max_id = maxId;
misskeyParams.since_id = sinceId;
misskeyParams.limit = limit;
Call<List<MisskeyNote>> publicTlCall = mastodonTimelinesService.getMisskey(misskeyParams);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
Response<List<MisskeyNote>> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
List<MisskeyNote> misskeyNoteList = publicTlResponse.body();
List<Status> statusList = new ArrayList<>();
if (misskeyNoteList != null) {
for (MisskeyNote misskeyNote : misskeyNoteList) {
Status status = MisskeyNote.convert(misskeyNote);
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
statuses.pagination.min_id = statusList.get(0).id;
statuses.pagination.max_id = statusList.get(statusList.size() - 1).id;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses);
mainHandler.post(myRunnable);
}).start();
return statusesMutableLiveData;
}
/**
* Public timeline for Peertube
*
* @param maxId Return results older than this id
* @param limit Maximum number of results to return. Defaults to 20.
* @return {@link LiveData} containing a {@link Statuses}
*/
public LiveData<Statuses> getPeertube(@NonNull String instance,
String maxId,
Integer limit) {
MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance);
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
Call<PeertubeVideo> publicTlCall = mastodonTimelinesService.getPeertube(maxId, "local", "-publishedAt", limit);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
Response<PeertubeVideo> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
PeertubeVideo peertubeVideo = publicTlResponse.body();
List<Status> statusList = new ArrayList<>();
if (peertubeVideo != null) {
for (PeertubeVideo.Video video : peertubeVideo.data) {
Status status = PeertubeVideo.convert(video);
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
statuses.pagination.min_id = statusList.get(0).id;
statuses.pagination.max_id = statusList.get(statusList.size() - 1).id;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses);
mainHandler.post(myRunnable);
}).start();
return statusesMutableLiveData;
}
/**
* View public statuses containing the given hashtag.
*