Compare commits
12 Commits
v1.1.5+for
...
v1.1.5+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec40488ed1 | ||
|
|
88851a085e | ||
|
|
6e718d6765 | ||
|
|
b26d491eda | ||
|
|
abdbab9d7b | ||
|
|
af1c7194e6 | ||
|
|
8e507e7970 | ||
|
|
3b542730b1 | ||
|
|
b038f81718 | ||
|
|
e1206703cf | ||
|
|
2110861f1b | ||
|
|
4f6476c807 |
@@ -9,8 +9,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 67
|
versionCode 69
|
||||||
versionName "1.1.5+fork.67"
|
versionName "1.1.5+fork.69"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import android.content.SharedPreferences;
|
|||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,7 +22,6 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showReplies;
|
public static boolean showReplies;
|
||||||
public static boolean showBoosts;
|
public static boolean showBoosts;
|
||||||
public static boolean loadNewPosts;
|
public static boolean loadNewPosts;
|
||||||
public static boolean showFederatedTimeline;
|
|
||||||
public static boolean showInteractionCounts;
|
public static boolean showInteractionCounts;
|
||||||
public static boolean alwaysExpandContentWarnings;
|
public static boolean alwaysExpandContentWarnings;
|
||||||
public static boolean disableMarquee;
|
public static boolean disableMarquee;
|
||||||
@@ -31,18 +32,22 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean uniformNotificationIcon;
|
public static boolean uniformNotificationIcon;
|
||||||
public static boolean reduceMotion;
|
public static boolean reduceMotion;
|
||||||
public static boolean keepOnlyLatestNotification;
|
public static boolean keepOnlyLatestNotification;
|
||||||
|
public static boolean disableAltTextReminder;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||||
|
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||||
public static Map<String, List<String>> recentLanguages;
|
public static Map<String, List<String>> recentLanguages;
|
||||||
|
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
private static <T> T fromJson(String json, Type type, T orElse) {
|
||||||
|
if (json == null) return orElse;
|
||||||
try { return gson.fromJson(json, type); }
|
try { return gson.fromJson(json, type); }
|
||||||
catch (JsonSyntaxException ignored) { return orElse; }
|
catch (JsonSyntaxException ignored) { return orElse; }
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,6 @@ public class GlobalUserPreferences{
|
|||||||
showReplies=prefs.getBoolean("showReplies", true);
|
showReplies=prefs.getBoolean("showReplies", true);
|
||||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||||
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
|
||||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||||
@@ -66,9 +70,11 @@ public class GlobalUserPreferences{
|
|||||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||||
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
|
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
publishButtonText=prefs.getString("publishButtonText", "");
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
||||||
|
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||||
@@ -85,7 +91,6 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("showReplies", showReplies)
|
.putBoolean("showReplies", showReplies)
|
||||||
.putBoolean("showBoosts", showBoosts)
|
.putBoolean("showBoosts", showBoosts)
|
||||||
.putBoolean("loadNewPosts", loadNewPosts)
|
.putBoolean("loadNewPosts", loadNewPosts)
|
||||||
.putBoolean("showFederatedTimeline", showFederatedTimeline)
|
|
||||||
.putBoolean("trueBlackTheme", trueBlackTheme)
|
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||||
@@ -96,10 +101,12 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||||
.putBoolean("reduceMotion", reduceMotion)
|
.putBoolean("reduceMotion", reduceMotion)
|
||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
|
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
.putString("color", color.name())
|
.putString("color", color.name())
|
||||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||||
|
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class GetList extends MastodonAPIRequest<ListTimeline> {
|
||||||
|
public GetList(String id) {
|
||||||
|
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
Request r=new Request();
|
Request r=new Request();
|
||||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||||
r.data.alerts=alerts;
|
r.data.alerts=alerts;
|
||||||
r.data.policy=policy;
|
r.policy=policy;
|
||||||
r.subscription.keys.p256dh=encryptionKey;
|
r.subscription.keys.p256dh=encryptionKey;
|
||||||
r.subscription.keys.auth=authKey;
|
r.subscription.keys.auth=authKey;
|
||||||
setRequestBody(r);
|
setRequestBody(r);
|
||||||
@@ -18,6 +18,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
private static class Request{
|
private static class Request{
|
||||||
public Subscription subscription=new Subscription();
|
public Subscription subscription=new Subscription();
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
|
|
||||||
private static class Keys{
|
private static class Keys{
|
||||||
public String p256dh;
|
public String p256dh;
|
||||||
@@ -31,7 +32,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,36 @@ package org.joinmastodon.android.api.requests.notifications;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||||
|
private final PushSubscription.Policy policy;
|
||||||
|
|
||||||
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||||
setRequestBody(new Request(alerts, policy));
|
setRequestBody(new Request(alerts, policy));
|
||||||
|
this.policy=policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateAndPostprocessResponse(PushSubscription respObj, Response httpResponse) throws IOException{
|
||||||
|
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||||
|
respObj.policy=policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
|
|
||||||
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
this.data.alerts=alerts;
|
this.data.alerts=alerts;
|
||||||
this.data.policy=policy;
|
this.policy=policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||||||
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
||||||
instanceUser.emojis = List.of();
|
instanceUser.emojis = List.of();
|
||||||
Status fakeStatus = a.toStatus();
|
Status fakeStatus = a.toStatus();
|
||||||
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus);
|
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||||
textItem.textSelectable = true;
|
textItem.textSelectable = true;
|
||||||
return List.of(
|
return List.of(
|
||||||
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
||||||
|
|||||||
@@ -951,6 +951,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void publish(){
|
private void publish(){
|
||||||
|
publish(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publish(boolean forceWithoutAltTexts){
|
||||||
String text=mainEditText.getText().toString();
|
String text=mainEditText.getText().toString();
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
req.status=text;
|
req.status=text;
|
||||||
@@ -960,6 +964,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
req.scheduledAt = scheduledAt;
|
req.scheduledAt = scheduledAt;
|
||||||
if(!attachments.isEmpty()){
|
if(!attachments.isEmpty()){
|
||||||
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||||
|
Optional<DraftMediaAttachment> withoutAltText = attachments.stream().filter(a -> a.description == null || a.description.isBlank()).findFirst();
|
||||||
|
boolean isDraft = scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
|
||||||
|
if (!forceWithoutAltTexts && !GlobalUserPreferences.disableAltTextReminder && !isDraft && withoutAltText.isPresent()) {
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_alt_text_missing_title)
|
||||||
|
.setMessage(R.string.sk_alt_text_missing)
|
||||||
|
.setPositiveButton(R.string.add_alt_text, (d, w) -> editMediaDescription(withoutAltText.get()))
|
||||||
|
.setNegativeButton(R.string.sk_publish_anyway, (d, w) -> publish(true))
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
||||||
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
||||||
@@ -1524,6 +1539,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||||
if(att.serverAttachment==null)
|
if(att.serverAttachment==null)
|
||||||
return;
|
return;
|
||||||
|
editMediaDescription(att);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editMediaDescription(DraftMediaAttachment att) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("attachment", att.serverAttachment.id);
|
args.putString("attachment", att.serverAttachment.id);
|
||||||
|
|||||||
@@ -0,0 +1,351 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static android.view.Menu.NONE;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.ui.utils.UiUtils.makeBackItem;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SubMenu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||||
|
private String accountID;
|
||||||
|
private TimelinesAdapter adapter;
|
||||||
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
|
private Menu optionsMenu;
|
||||||
|
private boolean updated;
|
||||||
|
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
|
||||||
|
private final List<ListTimeline> listTimelines = new ArrayList<>();
|
||||||
|
private final List<Hashtag> hashtags = new ArrayList<>();
|
||||||
|
|
||||||
|
public EditTimelinesFragment() {
|
||||||
|
super(10);
|
||||||
|
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
|
||||||
|
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
setTitle(R.string.sk_timelines);
|
||||||
|
accountID = getArguments().getString("account");
|
||||||
|
|
||||||
|
new GetLists().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> result) {
|
||||||
|
listTimelines.addAll(result);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
|
||||||
|
new GetFollowedHashtags().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(HeaderPaginationList<Hashtag> result) {
|
||||||
|
hashtags.addAll(result);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
itemTouchHelper.attachToRecyclerView(list);
|
||||||
|
refreshLayout.setEnabled(false);
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
this.optionsMenu = menu;
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_back) {
|
||||||
|
updateOptionsMenu();
|
||||||
|
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TimelineDefinition tl = timelineByMenuItem.get(item);
|
||||||
|
if (tl != null) {
|
||||||
|
data.add(tl.copy());
|
||||||
|
adapter.notifyItemInserted(data.size());
|
||||||
|
saveTimelines();
|
||||||
|
updateOptionsMenu();
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
||||||
|
if (data.contains(tl)) return;
|
||||||
|
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, tl.getTitle(getContext()));
|
||||||
|
item.setIcon(tl.getIcon().iconRes);
|
||||||
|
timelineByMenuItem.put(item, tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOptionsMenu() {
|
||||||
|
optionsMenu.clear();
|
||||||
|
timelineByMenuItem.clear();
|
||||||
|
|
||||||
|
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
|
||||||
|
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
||||||
|
|
||||||
|
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
|
||||||
|
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
||||||
|
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
|
||||||
|
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_list_24_regular);
|
||||||
|
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
|
||||||
|
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
|
||||||
|
makeBackItem(timelinesMenu);
|
||||||
|
makeBackItem(listsMenu);
|
||||||
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
|
TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
||||||
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
||||||
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
|
||||||
|
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
|
||||||
|
listsMenu.getItem().setVisible(listsMenu.size() > 0);
|
||||||
|
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
|
||||||
|
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTimelines() {
|
||||||
|
updated = true;
|
||||||
|
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeTimeline(int position) {
|
||||||
|
data.remove(position);
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
saveTimelines();
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
|
||||||
|
return adapter = new TimelinesAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop() {
|
||||||
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (updated) UiUtils.restartApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new TimelineViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView title;
|
||||||
|
private final ImageView dragger;
|
||||||
|
|
||||||
|
public TimelineViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_text, list);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
dragger=findViewById(R.id.dragger_thingy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public void onBind(TimelineDefinition item) {
|
||||||
|
title.setText(item.getTitle(getContext()));
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
||||||
|
dragger.setVisibility(View.VISIBLE);
|
||||||
|
dragger.setOnTouchListener((View v, MotionEvent event) -> {
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
itemTouchHelper.startDrag(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
Context ctx = getContext();
|
||||||
|
LinearLayout view = (LinearLayout) getActivity().getLayoutInflater()
|
||||||
|
.inflate(R.layout.edit_timeline, (ViewGroup) itemView, false);
|
||||||
|
|
||||||
|
TextInputFrameLayout inputLayout = view.findViewById(R.id.input);
|
||||||
|
EditText editText = inputLayout.getEditText();
|
||||||
|
editText.setText(item.getCustomTitle());
|
||||||
|
editText.setHint(item.getDefaultTitle(ctx));
|
||||||
|
|
||||||
|
ImageButton btn = view.findViewById(R.id.button);
|
||||||
|
PopupMenu popup = new PopupMenu(ctx, btn);
|
||||||
|
TimelineDefinition.Icon currentIcon = item.getIcon();
|
||||||
|
btn.setImageResource(currentIcon.iconRes);
|
||||||
|
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
||||||
|
btn.setOnTouchListener(popup.getDragToOpenListener());
|
||||||
|
btn.setOnClickListener(l -> popup.show());
|
||||||
|
|
||||||
|
Menu menu = popup.getMenu();
|
||||||
|
TimelineDefinition.Icon defaultIcon = item.getDefaultIcon();
|
||||||
|
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
||||||
|
if (!currentIcon.equals(defaultIcon)) {
|
||||||
|
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
||||||
|
}
|
||||||
|
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
|
||||||
|
if (icon.hidden || icon.equals(item.getIcon())) continue;
|
||||||
|
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
||||||
|
}
|
||||||
|
UiUtils.enablePopupMenuIcons(ctx, popup);
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
||||||
|
btn.setImageResource(icon.iconRes);
|
||||||
|
btn.setContentDescription(ctx.getString(icon.nameRes));
|
||||||
|
item.setIcon(icon);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
new M3AlertDialogBuilder(ctx)
|
||||||
|
.setTitle(R.string.sk_edit_timeline)
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
|
item.setTitle(editText.getText().toString().trim());
|
||||||
|
rebind();
|
||||||
|
saveTimelines();
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.sk_remove, (d, which) ->
|
||||||
|
removeTimeline(getAbsoluteAdapterPosition()))
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
editText.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
|
||||||
|
public ItemTouchHelperCallback() {
|
||||||
|
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
|
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
int toPosition = target.getAbsoluteAdapterPosition();
|
||||||
|
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
Collections.swap(data, fromPosition, toPosition);
|
||||||
|
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||||
|
saveTimelines();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||||
|
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
||||||
|
viewHolder.itemView.animate().alpha(0.65f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
viewHolder.itemView.animate().alpha(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
|
int position = viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
removeTimeline(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -16,6 +17,7 @@ import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
|||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,7 +28,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends StatusListFragment{
|
public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||||
private String hashtag;
|
private String hashtag;
|
||||||
private boolean following;
|
private boolean following;
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
@@ -41,7 +43,6 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
updateTitle(getArguments().getString("hashtag"));
|
updateTitle(getArguments().getString("hashtag"));
|
||||||
following=getArguments().getBoolean("following", false);
|
following=getArguments().getBoolean("following", false);
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,11 +60,31 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
followButton = menu.findItem(R.id.follow_hashtag);
|
followButton = menu.findItem(R.id.follow_hashtag);
|
||||||
updateFollowingState(following);
|
updateFollowingState(following);
|
||||||
|
|
||||||
followButton.setOnMenuItemClickListener(i -> {
|
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag hashtag) {
|
||||||
|
updateTitle(hashtag.name);
|
||||||
|
updateFollowingState(hashtag.following);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
|
if (item.getItemId() == R.id.follow_hashtag) {
|
||||||
updateFollowingState(!following);
|
updateFollowingState(!following);
|
||||||
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Hashtag i) {
|
public void onSuccess(Hashtag i) {
|
||||||
@@ -78,20 +99,13 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
return true;
|
return true;
|
||||||
});
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
@Override
|
||||||
@Override
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
public void onSuccess(Hashtag hashtag) {
|
return TimelineDefinition.ofHashtag(hashtag);
|
||||||
updateTitle(hashtag.name);
|
|
||||||
updateFollowingState(hashtag.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -40,12 +40,11 @@ import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
|||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
|
|
||||||
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
|
|
||||||
import org.joinmastodon.android.model.Announcement;
|
import org.joinmastodon.android.model.Announcement;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
@@ -65,6 +64,7 @@ import me.grishka.appkit.utils.V;
|
|||||||
|
|
||||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
||||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||||
|
private static final int PINNED_UPDATED_RESULT = 523;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private MenuItem announcements;
|
private MenuItem announcements;
|
||||||
@@ -73,8 +73,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
private boolean newPostsBtnShown;
|
private boolean newPostsBtnShown;
|
||||||
private AnimatorSet currentNewPostsAnim;
|
private AnimatorSet currentNewPostsAnim;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private final List<Fragment> fragments = new ArrayList<>();
|
|
||||||
private final List<FrameLayout> tabViews = new ArrayList<>();
|
|
||||||
private View switcher;
|
private View switcher;
|
||||||
private FrameLayout toolbarFrame;
|
private FrameLayout toolbarFrame;
|
||||||
private ImageView timelineIcon;
|
private ImageView timelineIcon;
|
||||||
@@ -83,11 +81,24 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
private PopupMenu switcherPopup;
|
private PopupMenu switcherPopup;
|
||||||
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
||||||
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
||||||
|
private List<TimelineDefinition> timelineDefinitions;
|
||||||
|
private int count;
|
||||||
|
private Fragment[] fragments;
|
||||||
|
private FrameLayout[] tabViews;
|
||||||
|
private TimelineDefinition[] timelines;
|
||||||
|
private Map<Integer, TimelineDefinition> timelinesByMenuItem = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID = getArguments().getString("account");
|
accountID = getArguments().getString("account");
|
||||||
|
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
|
||||||
|
assert timelineDefinitions != null;
|
||||||
|
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
||||||
|
count = timelineDefinitions.size();
|
||||||
|
fragments = new Fragment[count];
|
||||||
|
tabViews = new FrameLayout[count];
|
||||||
|
timelines = new TimelineDefinition[count];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,25 +113,28 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
pager = new ViewPager2(getContext());
|
pager = new ViewPager2(getContext());
|
||||||
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
||||||
|
|
||||||
if (fragments.size() == 0) {
|
if (fragments[0] == null) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
|
args.putBoolean("onlyPosts", true);
|
||||||
|
|
||||||
fragments.add(new HomeTimelineFragment());
|
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
||||||
fragments.add(new LocalTimelineFragment());
|
TimelineDefinition tl = timelineDefinitions.get(i);
|
||||||
if (GlobalUserPreferences.showFederatedTimeline) fragments.add(new FederatedTimelineFragment());
|
fragments[i] = tl.getFragment();
|
||||||
|
timelines[i] = tl;
|
||||||
|
}
|
||||||
|
|
||||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||||
for (int i = 0; i < fragments.size(); i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
fragments.get(i).setArguments(args);
|
fragments[i].setArguments(timelines[i].populateArguments(new Bundle(args)));
|
||||||
FrameLayout tabView = new FrameLayout(getActivity());
|
FrameLayout tabView = new FrameLayout(getActivity());
|
||||||
tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
tabView.setId(i + 1);
|
tabView.setId(i + 1);
|
||||||
transaction.add(i + 1, fragments.get(i));
|
transaction.add(i + 1, fragments[i]);
|
||||||
view.addView(tabView);
|
view.addView(tabView);
|
||||||
tabViews.add(tabView);
|
tabViews[i] = tabView;
|
||||||
}
|
}
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
}
|
}
|
||||||
@@ -140,7 +154,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron);
|
collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron);
|
||||||
switcher = toolbarFrame.findViewById(R.id.switcher_btn);
|
switcher = toolbarFrame.findViewById(R.id.switcher_btn);
|
||||||
switcherPopup = new PopupMenu(getContext(), switcher);
|
switcherPopup = new PopupMenu(getContext(), switcher);
|
||||||
switcherPopup.inflate(R.menu.home_switcher);
|
|
||||||
switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected);
|
switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected);
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
||||||
switcher.setOnClickListener(v->{
|
switcher.setOnClickListener(v->{
|
||||||
@@ -160,9 +173,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position){
|
public void onPageSelected(int position){
|
||||||
updateSwitcherIcon(position);
|
updateSwitcherIcon(position);
|
||||||
if (position==0) return;
|
if (!timelines[position].equals(TimelineDefinition.HOME_TIMELINE)) hideNewPostsButton();
|
||||||
hideNewPostsButton();
|
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
||||||
if (fragments.get(position) instanceof BaseRecyclerFragment<?> page){
|
|
||||||
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +182,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
if (!GlobalUserPreferences.reduceMotion) {
|
if (!GlobalUserPreferences.reduceMotion) {
|
||||||
pager.setPageTransformer((v, pos) -> {
|
pager.setPageTransformer((v, pos) -> {
|
||||||
if (tabViews.get(pager.getCurrentItem()) != v) return;
|
if (tabViews[pager.getCurrentItem()] != v) return;
|
||||||
float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f);
|
float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f);
|
||||||
switcher.setScaleY(scaleFactor);
|
switcher.setScaleY(scaleFactor);
|
||||||
switcher.setScaleX(scaleFactor);
|
switcher.setScaleX(scaleFactor);
|
||||||
@@ -285,6 +297,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
public void onSuccess(List<Announcement> result) {
|
public void onSuccess(List<Announcement> result) {
|
||||||
boolean hasUnread = result.stream().anyMatch(a -> !a.read);
|
boolean hasUnread = result.stream().anyMatch(a -> !a.read);
|
||||||
announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular);
|
announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular);
|
||||||
|
updateBadgedOptionsItem(announcements, hasUnread);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -292,6 +305,17 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
error.showToast(getActivity());
|
error.showToast(getActivity());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
|
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBadgedOptionsItem(MenuItem item, boolean asAction) {
|
||||||
|
item.setShowAsAction(asAction ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
|
||||||
|
if (asAction) {
|
||||||
|
UiUtils.resetPopupItemTint(item);
|
||||||
|
} else {
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void addItemsToMap(List<T> addItems, Map<Integer, T> items) {
|
private <T> void addItemsToMap(List<T> addItems, Map<Integer, T> items) {
|
||||||
@@ -302,13 +326,24 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
private void updateSwitcherMenu() {
|
private void updateSwitcherMenu() {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
switcherPopup.getMenu().findItem(R.id.federated).setVisible(GlobalUserPreferences.showFederatedTimeline);
|
Menu switcherMenu = switcherPopup.getMenu();
|
||||||
|
switcherMenu.clear();
|
||||||
|
timelinesByMenuItem.clear();
|
||||||
|
|
||||||
|
for (TimelineDefinition tl : timelines) {
|
||||||
|
int menuItemId = View.generateViewId();
|
||||||
|
timelinesByMenuItem.put(menuItemId, tl);
|
||||||
|
MenuItem item = switcherMenu.add(0, menuItemId, 0, tl.getTitle(getContext()));
|
||||||
|
item.setIcon(tl.getIcon().iconRes);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), item);
|
||||||
|
}
|
||||||
|
|
||||||
if (!listItems.isEmpty()) {
|
if (!listItems.isEmpty()) {
|
||||||
MenuItem listsItem = switcherPopup.getMenu().findItem(R.id.lists);
|
SubMenu listsMenu = switcherMenu.addSubMenu(R.string.sk_list_timelines);
|
||||||
listsItem.setVisible(true);
|
UiUtils.insetPopupMenuIcon(context, listsMenu.getItem().setVisible(true)
|
||||||
SubMenu listsMenu = listsItem.getSubMenu();
|
.setIcon(R.drawable.ic_fluent_people_list_24_regular));
|
||||||
listsMenu.clear();
|
listsMenu.clear();
|
||||||
|
UiUtils.insetPopupMenuIcon(context, UiUtils.makeBackItem(listsMenu));
|
||||||
listItems.forEach((id, list) -> {
|
listItems.forEach((id, list) -> {
|
||||||
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
|
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
|
||||||
item.setIcon(R.drawable.ic_fluent_people_list_24_regular);
|
item.setIcon(R.drawable.ic_fluent_people_list_24_regular);
|
||||||
@@ -317,10 +352,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hashtagsItems.isEmpty()) {
|
if (!hashtagsItems.isEmpty()) {
|
||||||
MenuItem hashtagsItem = switcherPopup.getMenu().findItem(R.id.followed_hashtags);
|
SubMenu hashtagsMenu = switcherMenu.addSubMenu(R.string.sk_hashtags_you_follow);
|
||||||
hashtagsItem.setVisible(true);
|
UiUtils.insetPopupMenuIcon(context, hashtagsMenu.getItem().setVisible(true)
|
||||||
SubMenu hashtagsMenu = hashtagsItem.getSubMenu();
|
.setIcon(R.drawable.ic_fluent_number_symbol_24_regular));
|
||||||
hashtagsMenu.clear();
|
hashtagsMenu.clear();
|
||||||
|
UiUtils.insetPopupMenuIcon(context, UiUtils.makeBackItem(hashtagsMenu));
|
||||||
hashtagsItems.forEach((id, hashtag) -> {
|
hashtagsItems.forEach((id, hashtag) -> {
|
||||||
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
|
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
|
||||||
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
@@ -333,28 +369,35 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
ListTimeline list;
|
ListTimeline list;
|
||||||
Hashtag hashtag;
|
Hashtag hashtag;
|
||||||
if (id == R.id.home) {
|
|
||||||
navigateTo(0);
|
Bundle args = new Bundle();
|
||||||
return true;
|
args.putString("account", accountID);
|
||||||
} else if (id == R.id.local) {
|
|
||||||
navigateTo(1);
|
if (id == R.id.menu_back) {
|
||||||
return true;
|
switcher.post(() -> switcherPopup.show());
|
||||||
} else if (id == R.id.federated) {
|
|
||||||
navigateTo(2);
|
|
||||||
return true;
|
return true;
|
||||||
} else if ((list = listItems.get(id)) != null) {
|
} else if ((list = listItems.get(id)) != null) {
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putString("listID", list.id);
|
args.putString("listID", list.id);
|
||||||
args.putString("listTitle", list.title);
|
args.putString("listTitle", list.title);
|
||||||
args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
Nav.goForResult(getActivity(), ListTimelineFragment.class, args, PINNED_UPDATED_RESULT, this);
|
||||||
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag.name, hashtag.following);
|
args.putString("hashtag", hashtag.name);
|
||||||
|
args.putBoolean("following", hashtag.following);
|
||||||
|
Nav.goForResult(getActivity(), HashtagTimelineFragment.class, args, PINNED_UPDATED_RESULT, this);
|
||||||
|
} else {
|
||||||
|
TimelineDefinition tl = timelinesByMenuItem.get(id);
|
||||||
|
if (tl != null) {
|
||||||
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
|
if (timelines[i] == tl) {
|
||||||
|
navigateTo(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateTo(int i) {
|
private void navigateTo(int i) {
|
||||||
navigateTo(i, !GlobalUserPreferences.reduceMotion);
|
navigateTo(i, !GlobalUserPreferences.reduceMotion);
|
||||||
}
|
}
|
||||||
@@ -365,32 +408,28 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
private void updateSwitcherIcon(int i) {
|
||||||
timelineIcon.setImageResource(switch (i) {
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
default -> R.drawable.ic_fluent_home_24_regular;
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
case 1 -> R.drawable.ic_fluent_people_community_24_regular;
|
|
||||||
case 2 -> R.drawable.ic_fluent_earth_24_regular;
|
|
||||||
});
|
|
||||||
timelineTitle.setText(switch (i) {
|
|
||||||
default -> R.string.sk_timeline_home;
|
|
||||||
case 1 -> R.string.sk_timeline_local;
|
|
||||||
case 2 -> R.string.sk_timeline_federated;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args);
|
int id = item.getItemId();
|
||||||
if (item.getItemId() == R.id.announcements) {
|
if (id == R.id.settings) {
|
||||||
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
|
} else if (id == R.id.announcements) {
|
||||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||||
|
} else if (id == R.id.edit_timelines) {
|
||||||
|
Nav.go(getActivity(), EditTimelinesFragment.class, args);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
((ScrollableToTop) fragments.get(pager.getCurrentItem())).scrollToTop();
|
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideNewPostsButton(){
|
public void hideNewPostsButton(){
|
||||||
@@ -426,7 +465,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void showNewPostsButton(){
|
public void showNewPostsButton(){
|
||||||
if(newPostsBtnShown || pager == null || pager.getCurrentItem() != 0)
|
if(newPostsBtnShown || pager == null || !timelines[pager.getCurrentItem()].equals(TimelineDefinition.HOME_TIMELINE))
|
||||||
return;
|
return;
|
||||||
newPostsBtnShown=true;
|
newPostsBtnShown=true;
|
||||||
if(currentNewPostsAnim!=null){
|
if(currentNewPostsAnim!=null){
|
||||||
@@ -469,15 +508,22 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){
|
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||||
if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) {
|
if (reqCode == ANNOUNCEMENTS_RESULT && success) {
|
||||||
announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular);
|
announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular);
|
||||||
|
announcements.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), announcements);
|
||||||
|
} else if (reqCode == PINNED_UPDATED_RESULT && result != null && result.getBoolean("pinnedUpdated", false)) {
|
||||||
|
UiUtils.restartApp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING) {
|
||||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
|
MenuItem settings = getToolbar().getMenu().findItem(R.id.settings);
|
||||||
|
settings.setIcon(R.drawable.ic_settings_24_badged);
|
||||||
|
updateBadgedOptionsItem(settings, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@@ -519,7 +565,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
FrameLayout tabView = tabViews.get(viewType % getItemCount());
|
FrameLayout tabView = tabViews[viewType % getItemCount()];
|
||||||
((ViewGroup)tabView.getParent()).removeView(tabView);
|
((ViewGroup)tabView.getParent()).removeView(tabView);
|
||||||
tabView.setVisibility(View.VISIBLE);
|
tabView.setVisibility(View.VISIBLE);
|
||||||
return new SimpleViewHolder(tabView);
|
return new SimpleViewHolder(tabView);
|
||||||
@@ -530,7 +576,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
return fragments.size();
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
@@ -32,6 +35,7 @@ import me.grishka.appkit.utils.V;
|
|||||||
public class HomeTimelineFragment extends FabStatusListFragment {
|
public class HomeTimelineFragment extends FabStatusListFragment {
|
||||||
private HomeTabFragment parent;
|
private HomeTabFragment parent;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
private String lastSavedMarkerID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
@@ -91,6 +95,29 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHidden(){
|
||||||
|
super.onHidden();
|
||||||
|
if(!data.isEmpty()){
|
||||||
|
String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
|
||||||
|
if(!topPostID.equals(lastSavedMarkerID)){
|
||||||
|
lastSavedMarkerID=topPostID;
|
||||||
|
new SaveMarkers(topPostID, null)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SaveMarkers.Response result){
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
lastSavedMarkerID=null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,28 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
import org.joinmastodon.android.api.requests.lists.GetList;
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -28,11 +33,12 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
|
||||||
public class ListTimelineFragment extends StatusListFragment {
|
public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||||
private String listID;
|
private String listID;
|
||||||
private String listTitle;
|
private String listTitle;
|
||||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
|
private Bundle resultArgs = new Bundle();
|
||||||
|
|
||||||
public ListTimelineFragment() {
|
public ListTimelineFragment() {
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
@@ -45,21 +51,36 @@ public class ListTimelineFragment extends StatusListFragment {
|
|||||||
listID = args.getString("listID");
|
listID = args.getString("listID");
|
||||||
listTitle = args.getString("listTitle");
|
listTitle = args.getString("listTitle");
|
||||||
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
||||||
|
resultArgs.putString("listID", listID);
|
||||||
|
|
||||||
setTitle(listTitle);
|
setTitle(listTitle);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
new GetList(listID).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ListTimeline listTimeline) {
|
||||||
|
// TODO: save updated info
|
||||||
|
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
||||||
|
if (!listTimeline.repliesPolicy.equals(repliesPolicy)) repliesPolicy = listTimeline.repliesPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.list, menu);
|
inflater.inflate(R.menu.list, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
Bundle args = new Bundle();
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
args.putString("listID", listID);
|
|
||||||
if (item.getItemId() == R.id.edit) {
|
if (item.getItemId() == R.id.edit) {
|
||||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||||
editor.applyList(listTitle, repliesPolicy);
|
editor.applyList(listTitle, repliesPolicy);
|
||||||
@@ -74,9 +95,9 @@ public class ListTimelineFragment extends StatusListFragment {
|
|||||||
setTitle(list.title);
|
setTitle(list.title);
|
||||||
listTitle = list.title;
|
listTitle = list.title;
|
||||||
repliesPolicy = list.repliesPolicy;
|
repliesPolicy = list.repliesPolicy;
|
||||||
args.putString("listTitle", listTitle);
|
resultArgs.putString("listTitle", listTitle);
|
||||||
args.putInt("repliesPolicy", repliesPolicy.ordinal());
|
resultArgs.putInt("repliesPolicy", repliesPolicy.ordinal());
|
||||||
setResult(true, args);
|
setResult(true, resultArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,14 +110,24 @@ public class ListTimelineFragment extends StatusListFragment {
|
|||||||
.show();
|
.show();
|
||||||
} else if (item.getItemId() == R.id.delete) {
|
} else if (item.getItemId() == R.id.delete) {
|
||||||
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
|
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
|
||||||
args.putBoolean("deleted", true);
|
resultArgs.putBoolean("deleted", true);
|
||||||
setResult(true, args);
|
setResult(true, resultArgs);
|
||||||
Nav.finish(this);
|
Nav.finish(this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle getResultArgs() {
|
||||||
|
return resultArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
|
return TimelineDefinition.ofList(listID, listTitle);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count) {
|
protected void doLoadData(int offset, int count) {
|
||||||
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null)
|
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
|||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -46,6 +47,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
|||||||
private HashMap<String, Boolean> userInList = new HashMap<>();
|
private HashMap<String, Boolean> userInList = new HashMap<>();
|
||||||
private int inProgress = 0;
|
private int inProgress = 0;
|
||||||
private ListsAdapter adapter;
|
private ListsAdapter adapter;
|
||||||
|
private boolean pinnedUpdated;
|
||||||
|
|
||||||
public ListTimelinesFragment() {
|
public ListTimelinesFragment() {
|
||||||
super(10);
|
super(10);
|
||||||
@@ -74,6 +76,12 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
|||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (pinnedUpdated) UiUtils.restartApp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -159,6 +167,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
|||||||
@Override
|
@Override
|
||||||
public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){
|
public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){
|
||||||
if (reqCode == LIST_CHANGED_RESULT && listChanged) {
|
if (reqCode == LIST_CHANGED_RESULT && listChanged) {
|
||||||
|
if (result.getBoolean("pinnedUpdated")) pinnedUpdated = true;
|
||||||
String listID = result.getString("listID");
|
String listID = result.getString("listID");
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
ListTimeline item = data.get(i);
|
ListTimeline item = data.get(i);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
|
|
||||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
|
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
@@ -104,13 +104,12 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
|
|
||||||
tabViews=new FrameLayout[3];
|
tabViews=new FrameLayout[2];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
case 0 -> R.id.notifications_all;
|
case 0 -> R.id.notifications_all;
|
||||||
case 1 -> R.id.notifications_mentions;
|
case 1 -> R.id.notifications_mentions;
|
||||||
case 2 -> R.id.notifications_posts;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -150,15 +149,9 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
mentionsFragment=new NotificationsListFragment();
|
mentionsFragment=new NotificationsListFragment();
|
||||||
mentionsFragment.setArguments(args);
|
mentionsFragment.setArguments(args);
|
||||||
|
|
||||||
args=new Bundle(args);
|
|
||||||
args.putBoolean("onlyPosts", true);
|
|
||||||
postsFragment=new NotificationsListFragment();
|
|
||||||
postsFragment.setArguments(args);
|
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.notifications_all, allNotificationsFragment)
|
.add(R.id.notifications_all, allNotificationsFragment)
|
||||||
.add(R.id.notifications_mentions, mentionsFragment)
|
.add(R.id.notifications_mentions, mentionsFragment)
|
||||||
.add(R.id.notifications_posts, postsFragment)
|
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +161,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.all_notifications;
|
case 0 -> R.string.all_notifications;
|
||||||
case 1 -> R.string.mentions;
|
case 1 -> R.string.mentions;
|
||||||
case 2 -> R.string.posts;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -217,7 +209,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> allNotificationsFragment;
|
case 0 -> allNotificationsFragment;
|
||||||
case 1 -> mentionsFragment;
|
case 1 -> mentionsFragment;
|
||||||
case 2 -> postsFragment;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -238,7 +229,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
return 3;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
private boolean onlyPosts;
|
private boolean onlyPosts;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -175,6 +177,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||||
|
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification getNotificationByID(String id){
|
private Notification getNotificationByID(String id){
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||||
|
protected boolean pinnedUpdated;
|
||||||
|
protected List<TimelineDefinition> pinnedTimelines;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
updatePinButton(menu.findItem(R.id.pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isPinned() {
|
||||||
|
return pinnedTimelines.contains(makeTimelineDefinition());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updatePinButton(MenuItem pin) {
|
||||||
|
boolean pinned = isPinned();
|
||||||
|
pin.setIcon(pinned ?
|
||||||
|
R.drawable.ic_fluent_pin_24_filled :
|
||||||
|
R.drawable.ic_fluent_pin_24_regular);
|
||||||
|
pin.setTitle(pinned ? R.string.sk_unpin_timeline : R.string.sk_pin_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract TimelineDefinition makeTimelineDefinition();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.pin) {
|
||||||
|
togglePin(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void togglePin(MenuItem pin) {
|
||||||
|
pinnedUpdated = true;
|
||||||
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
|
TimelineDefinition def = makeTimelineDefinition();
|
||||||
|
boolean pinned = isPinned();
|
||||||
|
if (pinned) pinnedTimelines.remove(def);
|
||||||
|
else pinnedTimelines.add(def);
|
||||||
|
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
|
||||||
|
GlobalUserPreferences.pinnedTimelines.put(accountID, pinnedTimelines);
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
updatePinButton(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Bundle getResultArgs() {
|
||||||
|
return new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Bundle resultArgs = getResultArgs();
|
||||||
|
if (pinnedUpdated) {
|
||||||
|
resultArgs.putBoolean("pinnedUpdated", true);
|
||||||
|
setResult(true, resultArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null);
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -165,11 +165,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_behavior));
|
items.add(new HeaderItem(R.string.settings_behavior));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{
|
|
||||||
GlobalUserPreferences.showFederatedTimeline=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||||
GlobalUserPreferences.playGifs=i.checked;
|
GlobalUserPreferences.playGifs=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -196,6 +191,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_disable_alt_text_reminder, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
|
||||||
|
GlobalUserPreferences.disableAltTextReminder=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||||
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -317,11 +316,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||||
}
|
}
|
||||||
if(needAppRestart){
|
if(needAppRestart) UiUtils.restartApp();
|
||||||
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
|
|
||||||
MastodonApp.context.startActivity(intent);
|
|
||||||
Runtime.getRuntime().exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
|||||||
", endpoint='"+endpoint+'\''+
|
", endpoint='"+endpoint+'\''+
|
||||||
", alerts="+alerts+
|
", alerts="+alerts+
|
||||||
", serverKey='"+serverKey+'\''+
|
", serverKey='"+serverKey+'\''+
|
||||||
|
", policy="+policy+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
|||||||
s.id = id;
|
s.id = id;
|
||||||
s.mediaAttachments = mediaAttachments;
|
s.mediaAttachments = mediaAttachments;
|
||||||
s.createdAt = scheduledAt;
|
s.createdAt = scheduledAt;
|
||||||
s.inReplyToId = "" + params.inReplyToId;
|
s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null;
|
||||||
s.content = s.text = params.text;
|
s.content = s.text = params.text;
|
||||||
s.spoilerText = params.spoilerText;
|
s.spoilerText = params.spoilerText;
|
||||||
s.visibility = params.visibility;
|
s.visibility = params.visibility;
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.BuildConfig;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
|
import org.joinmastodon.android.fragments.HomeTimelineFragment;
|
||||||
|
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
||||||
|
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||||
|
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
|
||||||
|
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class TimelineDefinition {
|
||||||
|
private TimelineType type;
|
||||||
|
private String title;
|
||||||
|
private @Nullable Icon icon;
|
||||||
|
|
||||||
|
private @Nullable String listId;
|
||||||
|
private @Nullable String listTitle;
|
||||||
|
|
||||||
|
private @Nullable String hashtagName;
|
||||||
|
|
||||||
|
public static TimelineDefinition ofList(String listId, String listTitle) {
|
||||||
|
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST, listTitle);
|
||||||
|
def.listId = listId;
|
||||||
|
def.listTitle = listTitle;
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimelineDefinition ofList(ListTimeline list) {
|
||||||
|
return ofList(list.id, list.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimelineDefinition ofHashtag(String hashtag) {
|
||||||
|
TimelineDefinition def = new TimelineDefinition(TimelineType.HASHTAG, hashtag);
|
||||||
|
def.hashtagName = hashtag;
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimelineDefinition ofHashtag(Hashtag hashtag) {
|
||||||
|
return ofHashtag(hashtag.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public TimelineDefinition() {}
|
||||||
|
|
||||||
|
public TimelineDefinition(TimelineType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineDefinition(TimelineType type, String title) {
|
||||||
|
this.type = type;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle(Context ctx) {
|
||||||
|
return title != null ? title : getDefaultTitle(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title == null || title.isBlank() ? null : title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultTitle(Context ctx) {
|
||||||
|
return switch (type) {
|
||||||
|
case HOME -> ctx.getString(R.string.sk_timeline_home);
|
||||||
|
case LOCAL -> ctx.getString(R.string.sk_timeline_local);
|
||||||
|
case FEDERATED -> ctx.getString(R.string.sk_timeline_federated);
|
||||||
|
case POST_NOTIFICATIONS -> ctx.getString(R.string.sk_timeline_posts);
|
||||||
|
case LIST -> listTitle;
|
||||||
|
case HASHTAG -> hashtagName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Icon getDefaultIcon() {
|
||||||
|
return switch (type) {
|
||||||
|
case HOME -> Icon.HOME;
|
||||||
|
case LOCAL -> Icon.LOCAL;
|
||||||
|
case FEDERATED -> Icon.FEDERATED;
|
||||||
|
case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS;
|
||||||
|
case LIST -> Icon.LIST;
|
||||||
|
case HASHTAG -> Icon.HASHTAG;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fragment getFragment() {
|
||||||
|
return switch (type) {
|
||||||
|
case HOME -> new HomeTimelineFragment();
|
||||||
|
case LOCAL -> new LocalTimelineFragment();
|
||||||
|
case FEDERATED -> new FederatedTimelineFragment();
|
||||||
|
case LIST -> new ListTimelineFragment();
|
||||||
|
case HASHTAG -> new HashtagTimelineFragment();
|
||||||
|
case POST_NOTIFICATIONS -> new NotificationsListFragment();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Icon getIcon() {
|
||||||
|
return icon == null ? getDefaultIcon() : icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(@Nullable Icon icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
TimelineDefinition that = (TimelineDefinition) o;
|
||||||
|
if (type != that.type) return false;
|
||||||
|
if (type == TimelineType.LIST) return Objects.equals(listId, that.listId);
|
||||||
|
if (type == TimelineType.HASHTAG) return Objects.equals(hashtagName.toLowerCase(), that.hashtagName.toLowerCase());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = type.ordinal();
|
||||||
|
result = 31 * result + (listId != null ? listId.hashCode() : 0);
|
||||||
|
result = 31 * result + (hashtagName.toLowerCase() != null ? hashtagName.toLowerCase().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineDefinition copy() {
|
||||||
|
TimelineDefinition def = new TimelineDefinition(type, title);
|
||||||
|
def.listId = listId;
|
||||||
|
def.listTitle = listTitle;
|
||||||
|
def.hashtagName = hashtagName;
|
||||||
|
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle populateArguments(Bundle args) {
|
||||||
|
if (type == TimelineType.LIST) {
|
||||||
|
args.putString("listTitle", title);
|
||||||
|
args.putString("listID", listId);
|
||||||
|
} else if (type == TimelineType.HASHTAG) {
|
||||||
|
args.putString("hashtag", hashtagName);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG }
|
||||||
|
|
||||||
|
public enum Icon {
|
||||||
|
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
|
||||||
|
STAR(R.drawable.ic_fluent_star_24_regular, R.string.sk_icon_star),
|
||||||
|
|
||||||
|
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
|
||||||
|
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
|
||||||
|
FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true),
|
||||||
|
POST_NOTIFICATIONS(R.drawable.ic_fluent_alert_24_regular, R.string.sk_timeline_posts, true),
|
||||||
|
LIST(R.drawable.ic_fluent_people_list_24_regular, R.string.sk_list, true),
|
||||||
|
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true);
|
||||||
|
|
||||||
|
public final int iconRes, nameRes;
|
||||||
|
public final boolean hidden;
|
||||||
|
|
||||||
|
Icon(@DrawableRes int iconRes, @StringRes int nameRes) {
|
||||||
|
this(iconRes, nameRes, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(@DrawableRes int iconRes, @StringRes int nameRes, boolean hidden) {
|
||||||
|
this.iconRes = iconRes;
|
||||||
|
this.nameRes = nameRes;
|
||||||
|
this.hidden = hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final TimelineDefinition HOME_TIMELINE = new TimelineDefinition(TimelineType.HOME);
|
||||||
|
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
|
||||||
|
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
|
||||||
|
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
|
||||||
|
|
||||||
|
public static final List<TimelineDefinition> DEFAULT_TIMELINES = BuildConfig.BUILD_TYPE.equals("playRelease")
|
||||||
|
? List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy())
|
||||||
|
: List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy(), FEDERATED_TIMELINE.copy());
|
||||||
|
public static final List<TimelineDefinition> ALL_TIMELINES = List.of(
|
||||||
|
HOME_TIMELINE.copy(),
|
||||||
|
LOCAL_TIMELINE.copy(),
|
||||||
|
FEDERATED_TIMELINE.copy(),
|
||||||
|
POSTS_TIMELINE.copy()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,18 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
@@ -10,6 +21,7 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
|
|
||||||
public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
||||||
public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
||||||
@@ -23,9 +35,109 @@ public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Holder extends ImageStatusDisplayItem.Holder<PhotoStatusDisplayItem>{
|
public static class Holder extends ImageStatusDisplayItem.Holder<PhotoStatusDisplayItem>{
|
||||||
|
private final FrameLayout altTextWrapper;
|
||||||
|
private final TextView altTextButton;
|
||||||
|
private final View altTextScroller;
|
||||||
|
private final ImageButton altTextClose;
|
||||||
|
private final TextView altText;
|
||||||
|
|
||||||
|
private boolean altTextShown;
|
||||||
|
private AnimatorSet currentAnim;
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_photo, parent);
|
super(activity, R.layout.display_item_photo, parent);
|
||||||
|
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||||
|
altTextButton=findViewById(R.id.alt_button);
|
||||||
|
altTextScroller=findViewById(R.id.alt_text_scroller);
|
||||||
|
altTextClose=findViewById(R.id.alt_text_close);
|
||||||
|
altText=findViewById(R.id.alt_text);
|
||||||
|
|
||||||
|
altTextButton.setOnClickListener(this::onShowHideClick);
|
||||||
|
altTextClose.setOnClickListener(this::onShowHideClick);
|
||||||
|
// altTextScroller.setNestedScrollingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(ImageStatusDisplayItem item){
|
||||||
|
super.onBind(item);
|
||||||
|
altTextShown=false;
|
||||||
|
if(currentAnim!=null)
|
||||||
|
currentAnim.cancel();
|
||||||
|
altTextScroller.setVisibility(View.GONE);
|
||||||
|
altTextClose.setVisibility(View.GONE);
|
||||||
|
altTextButton.setVisibility(View.VISIBLE);
|
||||||
|
if(TextUtils.isEmpty(item.attachment.description)){
|
||||||
|
altTextWrapper.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
altTextWrapper.setVisibility(View.VISIBLE);
|
||||||
|
altText.setText(item.attachment.description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onShowHideClick(View v){
|
||||||
|
boolean show=v.getId()==R.id.alt_button;
|
||||||
|
|
||||||
|
if(altTextShown==show)
|
||||||
|
return;
|
||||||
|
if(currentAnim!=null)
|
||||||
|
currentAnim.cancel();
|
||||||
|
|
||||||
|
altTextShown=show;
|
||||||
|
if(show){
|
||||||
|
altTextScroller.setVisibility(View.VISIBLE);
|
||||||
|
altTextClose.setVisibility(View.VISIBLE);
|
||||||
|
}else{
|
||||||
|
altTextButton.setVisibility(View.VISIBLE);
|
||||||
|
// Hide these views temporarily so FrameLayout measures correctly
|
||||||
|
altTextScroller.setVisibility(View.GONE);
|
||||||
|
altTextClose.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the current size...
|
||||||
|
int prevLeft=altTextWrapper.getLeft();
|
||||||
|
int prevRight=altTextWrapper.getRight();
|
||||||
|
int prevTop=altTextWrapper.getTop();
|
||||||
|
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw(){
|
||||||
|
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
|
||||||
|
// ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change
|
||||||
|
if(!show){
|
||||||
|
// Show these views again so they're visible for the duration of the animation.
|
||||||
|
// No one would notice they were missing during measure/layout.
|
||||||
|
altTextScroller.setVisibility(View.VISIBLE);
|
||||||
|
altTextClose.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(
|
||||||
|
ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()),
|
||||||
|
ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()),
|
||||||
|
ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()),
|
||||||
|
ObjectAnimator.ofFloat(altTextButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f),
|
||||||
|
ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f),
|
||||||
|
ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f)
|
||||||
|
);
|
||||||
|
set.setDuration(300);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
if(show){
|
||||||
|
altTextButton.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
altTextScroller.setVisibility(View.GONE);
|
||||||
|
altTextClose.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
currentAnim=null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set.start();
|
||||||
|
currentAnim=set;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
|
||||||
|
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){
|
||||||
String parentID=parentObject.getID();
|
String parentID=parentObject.getID();
|
||||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||||
Status statusForContent=status.getContentStatus();
|
Status statusForContent=status.getContentStatus();
|
||||||
@@ -100,7 +104,7 @@ public abstract class StatusDisplayItem{
|
|||||||
HeaderStatusDisplayItem header;
|
HeaderStatusDisplayItem header;
|
||||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
|
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
|
||||||
if(!TextUtils.isEmpty(statusForContent.content))
|
if(!TextUtils.isEmpty(statusForContent.content))
|
||||||
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
|
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
|
||||||
else
|
else
|
||||||
header.needBottomPadding=true;
|
header.needBottomPadding=true;
|
||||||
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
||||||
|
|||||||
@@ -42,14 +42,16 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private CharSequence parsedSpoilerText;
|
private CharSequence parsedSpoilerText;
|
||||||
public boolean textSelectable;
|
public boolean textSelectable;
|
||||||
public final Status status;
|
public final Status status;
|
||||||
|
public boolean disableTranslate;
|
||||||
public boolean translated = false;
|
public boolean translated = false;
|
||||||
public TranslatedStatus translation = null;
|
public TranslatedStatus translation = null;
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
|
|
||||||
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
|
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.text=text;
|
this.text=text;
|
||||||
this.status=status;
|
this.status=status;
|
||||||
|
this.disableTranslate=disableTranslate;
|
||||||
emojiHelper.setText(text);
|
emojiHelper.setText(text);
|
||||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||||
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
|
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
|
||||||
@@ -139,7 +141,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
|
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
|
||||||
boolean translateEnabled = instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
|
boolean translateEnabled = !item.disableTranslate && instanceInfo.v2 != null &&
|
||||||
|
instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
|
||||||
|
|
||||||
translateWrap.setVisibility(
|
translateWrap.setVisibility(
|
||||||
(!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable) &&
|
(!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable) &&
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class DiscoverInfoBannerHelper{
|
|||||||
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
||||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||||
case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner;
|
case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner;
|
||||||
|
case POST_NOTIFICATIONS -> R.string.sk_notify_posts_info_banner;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,7 @@ public class DiscoverInfoBannerHelper{
|
|||||||
TRENDING_LINKS,
|
TRENDING_LINKS,
|
||||||
LOCAL_TIMELINE,
|
LOCAL_TIMELINE,
|
||||||
FEDERATED_TIMELINE,
|
FEDERATED_TIMELINE,
|
||||||
|
POST_NOTIFICATIONS,
|
||||||
// ACCOUNTS
|
// ACCOUNTS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.ui.utils;
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import static android.view.Menu.NONE;
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.theme;
|
import static org.joinmastodon.android.GlobalUserPreferences.theme;
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ import android.util.Log;
|
|||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
@@ -772,11 +774,20 @@ public class UiUtils{
|
|||||||
item.setTitle(ssb);
|
item.setTitle(ssb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void resetPopupItemTint(MenuItem item) {
|
||||||
|
if(Build.VERSION.SDK_INT>=26) {
|
||||||
|
item.setIconTintList(null);
|
||||||
|
} else {
|
||||||
|
Drawable icon=item.getIcon().mutate();
|
||||||
|
icon.setTintList(null);
|
||||||
|
item.setIcon(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) {
|
public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) {
|
||||||
if(menu.getClass().getSimpleName().equals("MenuBuilder")){
|
if(menu.getClass().getSimpleName().equals("MenuBuilder")){
|
||||||
try {
|
try {
|
||||||
Method m = menu.getClass().getDeclaredMethod(
|
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
|
||||||
"setOptionalIconsVisible", Boolean.TYPE);
|
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
m.invoke(menu, true);
|
m.invoke(menu, true);
|
||||||
enableMenuIcons(context, menu, asAction);
|
enableMenuIcons(context, menu, asAction);
|
||||||
@@ -789,6 +800,8 @@ public class UiUtils{
|
|||||||
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||||
for(int i=0;i<m.size();i++){
|
for(int i=0;i<m.size();i++){
|
||||||
MenuItem item=m.getItem(i);
|
MenuItem item=m.getItem(i);
|
||||||
|
SubMenu subMenu = item.getSubMenu();
|
||||||
|
if (subMenu != null) enableMenuIcons(context, subMenu, exclude);
|
||||||
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId())) continue;
|
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId())) continue;
|
||||||
insetPopupMenuIcon(item, iconTint);
|
insetPopupMenuIcon(item, iconTint);
|
||||||
}
|
}
|
||||||
@@ -887,6 +900,18 @@ public class UiUtils{
|
|||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void restartApp() {
|
||||||
|
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
|
||||||
|
MastodonApp.context.startActivity(intent);
|
||||||
|
Runtime.getRuntime().exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MenuItem makeBackItem(Menu m) {
|
||||||
|
MenuItem back = m.add(0, R.id.menu_back, NONE, R.string.back);
|
||||||
|
back.setIcon(R.drawable.ic_arrow_back);
|
||||||
|
return back;
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InteractionPerformer {
|
public interface InteractionPerformer {
|
||||||
void interact(StatusInteractionController ic, Status status, Consumer<Status> resultConsumer);
|
void interact(StatusInteractionController ic, Status status, Consumer<Status> resultConsumer);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
public class NestableScrollView extends ScrollView{
|
||||||
|
private float downY, touchslop;
|
||||||
|
private boolean didDisallow;
|
||||||
|
|
||||||
|
public NestableScrollView(Context context){
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestableScrollView(Context context, AttributeSet attrs){
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestableScrollView(Context context, AttributeSet attrs, int defStyleAttr){
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(){
|
||||||
|
touchslop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent ev){
|
||||||
|
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||||
|
if(canScrollVertically(-1) || canScrollVertically(1)){
|
||||||
|
getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
didDisallow=true;
|
||||||
|
}else{
|
||||||
|
didDisallow=false;
|
||||||
|
}
|
||||||
|
downY=ev.getY();
|
||||||
|
}else if(didDisallow && ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||||
|
if(Math.abs(downY-ev.getY())>=touchslop){
|
||||||
|
if(!canScrollVertically((int)(downY-ev.getY()))){
|
||||||
|
didDisallow=false;
|
||||||
|
getParent().requestDisallowInterceptTouchEvent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
mastodon/src/main/res/drawable/bg_image_alt_overlay.xml
Normal file
5
mastodon/src/main/res/drawable/bg_image_alt_overlay.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#D9000000"/>
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
</shape>
|
||||||
5
mastodon/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
5
mastodon/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M11.75 3c0.38 0 0.693 0.282 0.743 0.648L12.5 3.75 12.501 11h7.253c0.415 0 0.75 0.336 0.75 0.75 0 0.38-0.282 0.694-0.648 0.743L19.754 12.5h-7.253l0.002 7.25c0 0.413-0.335 0.75-0.75 0.75-0.38 0-0.693-0.283-0.743-0.649l-0.007-0.102-0.002-7.249H3.752c-0.414 0-0.75-0.336-0.75-0.75 0-0.38 0.282-0.694 0.648-0.743L3.752 11h7.25L11 3.75C11 3.336 11.336 3 11.75 3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M21.068 7.758l-4.826-4.826c-1.327-1.327-3.564-0.964-4.404 0.715l-2.435 4.87c-0.088 0.176-0.24 0.31-0.426 0.374l-4.166 1.44c-0.873 0.3-1.129 1.412-0.476 2.065L7.439 15.5 3 19.94V21h1.06l4.44-4.44 3.104 3.105c0.653 0.653 1.764 0.397 2.066-0.476l1.44-4.166c0.063-0.185 0.197-0.338 0.373-0.426l4.87-2.435c1.68-0.84 2.042-3.077 0.715-4.404z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M15.25 13c0.967 0 1.75 0.784 1.75 1.75v4.5c0 0.966-0.783 1.75-1.75 1.75H3.75C2.785 21 2 20.216 2 19.25v-4.5C2 13.784 2.785 13 3.75 13h11.5zM21 14.899v5.351c0 0.414-0.335 0.75-0.75 0.75-0.38 0-0.693-0.282-0.743-0.648L19.5 20.25v-5.338C19.732 14.969 19.975 15 20.226 15c0.268 0 0.527-0.035 0.775-0.101zM15.25 14.5H3.75c-0.138 0-0.25 0.112-0.25 0.25v4.5c0 0.138 0.112 0.25 0.25 0.25h11.5c0.139 0 0.25-0.112 0.25-0.25v-4.5c0-0.138-0.111-0.25-0.25-0.25zm5-4.408c1.054 0 1.908 0.854 1.908 1.908 0 1.054-0.854 1.908-1.908 1.908-1.053 0-1.908-0.854-1.908-1.908 0-1.054 0.855-1.908 1.908-1.908zM15.246 3c0.967 0 1.75 0.784 1.75 1.75v4.5c0 0.966-0.783 1.75-1.75 1.75h-11.5c-0.966 0-1.75-0.784-1.75-1.75v-4.5c0-0.918 0.707-1.671 1.607-1.744L3.746 3h11.5zm0 1.5h-11.5L3.69 4.507C3.579 4.533 3.496 4.632 3.496 4.75v4.5c0 0.138 0.112 0.25 0.25 0.25h11.5c0.138 0 0.25-0.112 0.25-0.25v-4.5c0-0.138-0.112-0.25-0.25-0.25zM20.25 3c0.38 0 0.694 0.282 0.744 0.648L21 3.75v5.351C20.754 9.035 20.495 9 20.227 9c-0.25 0-0.494 0.03-0.726 0.088V3.75C19.5 3.336 19.836 3 20.25 3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@@ -10,4 +11,62 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:scaleType="centerCrop"/>
|
android:scaleType="centerCrop"/>
|
||||||
|
|
||||||
|
<!-- This is hidden from screenreaders because that same alt text is set as content description on the ImageView -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/alt_text_wrapper"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|bottom"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:importantForAccessibility="noHideDescendants"
|
||||||
|
android:background="@drawable/bg_image_alt_overlay">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_button"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="ALT"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/alt_text_close"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
android:tint="#FFF"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.NestableScrollView
|
||||||
|
android:id="@+id/alt_text_scroller"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="40dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
tools:text="Alt text goes here"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.NestableScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
||||||
22
mastodon/src/main/res/layout/edit_timeline.xml
Normal file
22
mastodon/src/main/res/layout/edit_timeline.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<ImageButton
|
||||||
|
style="?android:editTextStyle"
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:contentDescription="@string/sk_timeline_icon"
|
||||||
|
android:paddingHorizontal="14dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
<org.joinmastodon.android.ui.views.TextInputFrameLayout
|
||||||
|
android:id="@+id/input"
|
||||||
|
android:layout_marginStart="-8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -23,9 +23,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginVertical="16dp"
|
android:layout_margin="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:textAppearance="@style/m3_body_medium"
|
android:textAppearance="@style/m3_body_medium"
|
||||||
tools:text="@string/sk_update_available"/>
|
tools:text="@string/sk_update_available"/>
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,17 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:paddingRight="16dp"/>
|
android:paddingHorizontal="8dp"
|
||||||
|
tools:ignore="RtlSymmetry" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dragger_thingy"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:tint="?colorDarkIcon"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_fluent_re_order_dots_vertical_24_regular"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/pin"
|
||||||
|
android:title="@string/sk_pin_timeline"
|
||||||
|
android:icon="@drawable/ic_fluent_pin_24_regular"
|
||||||
|
android:showAsAction="always" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/follow_hashtag"
|
android:id="@+id/follow_hashtag"
|
||||||
android:icon="@drawable/ic_fluent_person_add_24_regular"
|
android:icon="@drawable/ic_fluent_person_add_24_regular"
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/announcements"
|
android:id="@+id/announcements"
|
||||||
android:icon="@drawable/ic_fluent_megaphone_24_regular"
|
android:icon="@drawable/ic_fluent_megaphone_24_regular"
|
||||||
android:showAsAction="always"
|
|
||||||
android:title="@string/sk_announcements" />
|
android:title="@string/sk_announcements" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/edit_timelines"
|
||||||
|
android:icon="@drawable/ic_fluent_edit_24_regular"
|
||||||
|
android:title="@string/sk_edit_timelines" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
android:icon="@drawable/ic_fluent_settings_24_regular"
|
android:icon="@drawable/ic_fluent_settings_24_regular"
|
||||||
android:showAsAction="always"
|
|
||||||
android:title="@string/settings" />
|
android:title="@string/settings" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:id="@+id/home" android:icon="@drawable/ic_fluent_home_24_regular" android:title="@string/sk_timeline_home" />
|
|
||||||
<item android:id="@+id/local" android:icon="@drawable/ic_fluent_people_community_24_regular" android:title="@string/sk_timeline_local" />
|
|
||||||
<item android:id="@+id/federated" android:icon="@drawable/ic_fluent_earth_24_regular" android:title="@string/sk_timeline_federated" />
|
|
||||||
<item android:id="@+id/lists" android:icon="@drawable/ic_fluent_people_list_24_regular" android:title="@string/sk_list_timelines" android:visible="false">
|
|
||||||
<menu />
|
|
||||||
</item>
|
|
||||||
<item android:id="@+id/followed_hashtags" android:icon="@drawable/ic_fluent_number_symbol_24_regular" android:title="@string/sk_hashtags_you_follow" android:visible="false">
|
|
||||||
<menu />
|
|
||||||
</item>
|
|
||||||
</menu>
|
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/pin"
|
||||||
|
android:title="@string/sk_pin_timeline"
|
||||||
|
android:icon="@drawable/ic_fluent_pin_24_regular"
|
||||||
|
android:showAsAction="always" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/edit"
|
android:id="@+id/edit"
|
||||||
android:title="@string/edit"
|
android:title="@string/edit"
|
||||||
android:icon="@drawable/ic_fluent_edit_24_regular"
|
android:icon="@drawable/ic_fluent_edit_24_regular" />
|
||||||
android:showAsAction="always"/>
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/delete"
|
android:id="@+id/delete"
|
||||||
android:title="@string/delete"
|
android:title="@string/delete"
|
||||||
android:icon="@drawable/ic_fluent_delete_24_regular"
|
android:icon="@drawable/ic_fluent_delete_24_regular" />
|
||||||
android:showAsAction="always"/>
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<item name="notifications_all" type="id"/>
|
<item name="notifications_all" type="id"/>
|
||||||
<item name="notifications_mentions" type="id"/>
|
<item name="notifications_mentions" type="id"/>
|
||||||
<item name="notifications_posts" type="id"/>
|
|
||||||
|
|
||||||
<item name="timeline_home" type="id" />
|
<item name="menu_add_timeline" type="id" />
|
||||||
|
<item name="menu_back" type="id" />
|
||||||
</resources>
|
</resources>
|
||||||
@@ -149,4 +149,25 @@
|
|||||||
<string name="sk_do_remove_follower">Remove</string>
|
<string name="sk_do_remove_follower">Remove</string>
|
||||||
<string name="sk_remove_follower_success">Successfully removed follower</string>
|
<string name="sk_remove_follower_success">Successfully removed follower</string>
|
||||||
<string name="sk_changelog">Changelog</string>
|
<string name="sk_changelog">Changelog</string>
|
||||||
|
<string name="sk_alt_text_missing_title">Missing alt text</string>
|
||||||
|
<string name="sk_alt_text_missing">At least one attachment does not contain a description.</string>
|
||||||
|
<string name="sk_publish_anyway">Publish anyway</string>
|
||||||
|
<string name="sk_settings_disable_alt_text_reminder">Disable alt text reminder</string>
|
||||||
|
<string name="sk_notify_posts_info_banner">If you enable post notifications for some people, their new posts will appear here.</string>
|
||||||
|
<string name="sk_timelines">Timelines</string>
|
||||||
|
<string name="sk_timeline_posts">Posts</string>
|
||||||
|
<string name="sk_timelines_add">Add</string>
|
||||||
|
<string name="sk_timeline">Timeline</string>
|
||||||
|
<string name="sk_list">List</string>
|
||||||
|
<string name="sk_hashtag">Hashtag</string>
|
||||||
|
<string name="sk_pin_timeline">Pin timeline</string>
|
||||||
|
<string name="sk_unpin_timeline">Unpin timeline</string>
|
||||||
|
<string name="sk_pinned_timeline">Pinned to home</string>
|
||||||
|
<string name="sk_unpinned_timeline">Unpinned from home</string>
|
||||||
|
<string name="sk_remove">Remove</string>
|
||||||
|
<string name="sk_timeline_icon">Icon</string>
|
||||||
|
<string name="sk_icon_heart">Heart</string>
|
||||||
|
<string name="sk_icon_star">Star</string>
|
||||||
|
<string name="sk_edit_timeline">Edit timeline</string>
|
||||||
|
<string name="sk_edit_timelines">Edit timelines</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user