@ -0,0 +1,150 @@
|
|||||||
|
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("untilId")
|
||||||
|
public String untilId;
|
||||||
|
@SerializedName("limit")
|
||||||
|
public int limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
package app.fedilab.android.client.entities.nitter;
|
||||||
|
/* 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 android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.simpleframework.xml.Element;
|
||||||
|
import org.simpleframework.xml.ElementList;
|
||||||
|
import org.simpleframework.xml.Namespace;
|
||||||
|
import org.simpleframework.xml.Path;
|
||||||
|
import org.simpleframework.xml.Root;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import app.fedilab.android.client.endpoints.MastodonTimelinesService;
|
||||||
|
import app.fedilab.android.client.entities.api.Attachment;
|
||||||
|
import app.fedilab.android.client.entities.api.Status;
|
||||||
|
import app.fedilab.android.helper.Helper;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
|
||||||
|
|
||||||
|
@Root(name = "rss", strict = false)
|
||||||
|
public class Nitter implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
public static HashMap<String, Nitter> accounts = new HashMap<>();
|
||||||
|
|
||||||
|
@Element(name = "title")
|
||||||
|
@Path("channel")
|
||||||
|
public String title;
|
||||||
|
@Element(name = "image")
|
||||||
|
@Path("channel")
|
||||||
|
public Image image;
|
||||||
|
@ElementList(name = "item", inline = true)
|
||||||
|
@Path("channel")
|
||||||
|
public List<FeedItem> mFeedItems;
|
||||||
|
|
||||||
|
public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) {
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.connectTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.callTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.proxy(Helper.getProxy(context))
|
||||||
|
.build();
|
||||||
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
|
.baseUrl("https://" + instance)
|
||||||
|
.addConverterFactory(SimpleXmlConverterFactory.create())
|
||||||
|
.client(okHttpClient)
|
||||||
|
.build();
|
||||||
|
return retrofit.create(MastodonTimelinesService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Status convert(Context context, String instance, FeedItem feedItem) {
|
||||||
|
Status status = new Status();
|
||||||
|
status.id = feedItem.pubDate;
|
||||||
|
status.content = feedItem.description;
|
||||||
|
status.text = feedItem.title;
|
||||||
|
status.visibility = "public";
|
||||||
|
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz");
|
||||||
|
status.uri = feedItem.guid;
|
||||||
|
status.url = feedItem.link;
|
||||||
|
if (!accounts.containsValue(feedItem.creator)) {
|
||||||
|
MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance);
|
||||||
|
Call<Nitter> accountCall = mastodonTimelinesService.getNitterAccount(feedItem.creator.replace("@", ""));
|
||||||
|
if (accountCall != null) {
|
||||||
|
try {
|
||||||
|
Response<Nitter> publicTlResponse = accountCall.execute();
|
||||||
|
if (publicTlResponse.isSuccessful()) {
|
||||||
|
Nitter nitterAccount = publicTlResponse.body();
|
||||||
|
accounts.put(feedItem.creator, nitterAccount);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Nitter nitterAccount = accounts.get(feedItem.creator);
|
||||||
|
if (nitterAccount != null) {
|
||||||
|
app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account();
|
||||||
|
String[] names = nitterAccount.image.title.split("/");
|
||||||
|
account.id = feedItem.guid;
|
||||||
|
account.acct = names[1].replace("@", "");
|
||||||
|
account.username = names[1].replace("@", "");
|
||||||
|
account.display_name = names[0];
|
||||||
|
account.avatar = nitterAccount.image.url;
|
||||||
|
account.avatar_static = nitterAccount.image.url;
|
||||||
|
account.url = nitterAccount.image.link;
|
||||||
|
status.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feedItem.description != null) {
|
||||||
|
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
|
||||||
|
Matcher matcher = imgPattern.matcher(feedItem.description);
|
||||||
|
String description = feedItem.description;
|
||||||
|
ArrayList<Attachment> attachmentList = new ArrayList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
description = description.replaceAll(Pattern.quote(matcher.group()), "");
|
||||||
|
Attachment attachment = new Attachment();
|
||||||
|
attachment.type = "image";
|
||||||
|
attachment.url = matcher.group(1);
|
||||||
|
attachment.preview_url = matcher.group(1);
|
||||||
|
attachment.id = matcher.group(1);
|
||||||
|
attachmentList.add(attachment);
|
||||||
|
}
|
||||||
|
status.media_attachments = attachmentList;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Root(name = "image", strict = false)
|
||||||
|
public static class Image implements Serializable {
|
||||||
|
@Element(name = "title")
|
||||||
|
public String title;
|
||||||
|
@Element(name = "url")
|
||||||
|
public String url;
|
||||||
|
@Element(name = "link")
|
||||||
|
public String link;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Root(name = "item", strict = false)
|
||||||
|
public static class FeedItem implements Serializable {
|
||||||
|
@Namespace(prefix = "dc")
|
||||||
|
@Element(name = "creator", required = false)
|
||||||
|
public String creator;
|
||||||
|
@Element(name = "title", required = false)
|
||||||
|
public String title;
|
||||||
|
@Element(name = "description", required = false)
|
||||||
|
public String description;
|
||||||
|
@Element(name = "pubDate", required = false)
|
||||||
|
public String pubDate;
|
||||||
|
@Element(name = "guid", required = false)
|
||||||
|
public String guid;
|
||||||
|
@Element(name = "link", required = false)
|
||||||
|
public String link;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "creator: " + creator + "\r" + "title: " + title + "\r" + "description: "
|
||||||
|
+ description + "\r" + "pubDate: " + pubDate + "\r"
|
||||||
|
+ "guid: " + guid + "\r" + "link: " + link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,211 @@
|
|||||||
|
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";
|
||||||
|
attachment.url = "https://" + peertubeVideo.account.host + peertubeVideo.embedPath;
|
||||||
|
attachment.preview_url = "https://" + peertubeVideo.account.host + peertubeVideo.thumbnailPath;
|
||||||
|
attachment.peertubeId = peertubeVideo.uuid;
|
||||||
|
attachment.peertubeHost = peertubeVideo.account.host;
|
||||||
|
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("files")
|
||||||
|
public List<File> files;
|
||||||
|
@SerializedName("views")
|
||||||
|
public int views;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class File implements Serializable {
|
||||||
|
@SerializedName("fileDownloadUrl")
|
||||||
|
public String fileDownloadUrl;
|
||||||
|
@SerializedName("fileUrl")
|
||||||
|
public String fileUrl;
|
||||||
|
@SerializedName("fps")
|
||||||
|
public int fps;
|
||||||
|
@SerializedName("magnetUri")
|
||||||
|
public String magnetUri;
|
||||||
|
@SerializedName("metadataUrl")
|
||||||
|
public String metadataUrl;
|
||||||
|
@SerializedName("resolution")
|
||||||
|
public Item resolutions;
|
||||||
|
@SerializedName("size")
|
||||||
|
public long size;
|
||||||
|
@SerializedName("torrentDownloadUrl")
|
||||||
|
public String torrentDownloadUrl;
|
||||||
|
@SerializedName("torrentUrl")
|
||||||
|
public String torrentUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package app.fedilab.android.helper
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
|
||||||
|
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
|
||||||
|
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
|
||||||
|
*
|
||||||
|
* This solution has limitations when using multiple levels of nested scrollable elements
|
||||||
|
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
|
||||||
|
*/
|
||||||
|
class NestedScrollableHost : FrameLayout {
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
|
||||||
|
private var touchSlop = 0
|
||||||
|
private var initialX = 0f
|
||||||
|
private var initialY = 0f
|
||||||
|
private val parentViewPager: ViewPager2?
|
||||||
|
get() {
|
||||||
|
var v: View? = parent as? View
|
||||||
|
while (v != null && v !is ViewPager2) {
|
||||||
|
v = v.parent as? View
|
||||||
|
}
|
||||||
|
return v as? ViewPager2
|
||||||
|
}
|
||||||
|
|
||||||
|
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
|
||||||
|
|
||||||
|
init {
|
||||||
|
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
|
||||||
|
val direction = -delta.sign.toInt()
|
||||||
|
return when (orientation) {
|
||||||
|
0 -> child?.canScrollHorizontally(direction) ?: false
|
||||||
|
1 -> child?.canScrollVertically(direction) ?: false
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||||
|
handleInterceptTouchEvent(e)
|
||||||
|
return super.onInterceptTouchEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInterceptTouchEvent(e: MotionEvent) {
|
||||||
|
val orientation = parentViewPager?.orientation ?: return
|
||||||
|
|
||||||
|
// Early return if child can't scroll in same direction as parent
|
||||||
|
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
initialX = e.x
|
||||||
|
initialY = e.y
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else if (e.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
val dx = e.x - initialX
|
||||||
|
val dy = e.y - initialY
|
||||||
|
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
|
||||||
|
|
||||||
|
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||||
|
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
|
||||||
|
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
||||||
|
|
||||||
|
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||||
|
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||||
|
// Gesture is perpendicular, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
} else {
|
||||||
|
// Gesture is parallel, query child if movement in that direction is possible
|
||||||
|
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
|
||||||
|
// Child can scroll, disallow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else {
|
||||||
|
// Child cannot scroll, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package app.fedilab.android.helper;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
public class ZoomOutPageTransformer implements ViewPager2.PageTransformer {
|
||||||
|
private static final float MIN_SCALE = 0.85f;
|
||||||
|
private static final float MIN_ALPHA = 0.5f;
|
||||||
|
|
||||||
|
public void transformPage(View view, float position) {
|
||||||
|
int pageWidth = view.getWidth();
|
||||||
|
int pageHeight = view.getHeight();
|
||||||
|
|
||||||
|
if (position < -1) { // [-Infinity,-1)
|
||||||
|
// This page is way off-screen to the left.
|
||||||
|
view.setAlpha(0f);
|
||||||
|
|
||||||
|
} else if (position <= 1) { // [-1,1]
|
||||||
|
// Modify the default slide transition to shrink the page as well
|
||||||
|
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
|
||||||
|
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
|
||||||
|
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
|
||||||
|
if (position < 0) {
|
||||||
|
view.setTranslationX(horzMargin - vertMargin / 2);
|
||||||
|
} else {
|
||||||
|
view.setTranslationX(-horzMargin + vertMargin / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale the page down (between MIN_SCALE and 1)
|
||||||
|
view.setScaleX(scaleFactor);
|
||||||
|
view.setScaleY(scaleFactor);
|
||||||
|
|
||||||
|
// Fade the page relative to its size.
|
||||||
|
view.setAlpha(MIN_ALPHA +
|
||||||
|
(scaleFactor - MIN_SCALE) /
|
||||||
|
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
|
||||||
|
|
||||||
|
} else { // (1,+Infinity]
|
||||||
|
// This page is way off-screen to the right.
|
||||||
|
view.setAlpha(0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 220 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 245 KiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 316 KiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 252 KiB |