mirror of
https://codeberg.org/tom79/Fedilab.git
synced 2024-12-22 16:50:04 +02:00
Follow Peertube & Misskey instances
This commit is contained in:
parent
e32101a6bb
commit
79d037036c
8 changed files with 639 additions and 48 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue