Hashtag timelines with multiple tags (#584)

* feat(api/hashtag): add any, all, and none parameter

* feat(timeline/hashtag): load with any, all and none parameter

* feat(timeline/hashtag): save any, all and none in timeline definition

* feat: set hastag parameter in UI

* feat: move strings to string res

* feat: show hint for tags

* refactor: use method for setting up tags text

* improve edit dialog, allow creating hashtag timelines

* add chips for hashtags

* add option for displaying only local posts in hashtag

* improve layout and wording

---------

Co-authored-by: sk <sk22@mailbox.org>
This commit is contained in:
FineFindus
2023-06-21 01:38:51 +02:00
committed by GitHub
parent bb4a52f03a
commit be425282a6
36 changed files with 3215 additions and 100 deletions

View File

@@ -3,6 +3,7 @@ package org.joinmastodon.android.model;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
@@ -19,6 +20,7 @@ import org.joinmastodon.android.fragments.discover.BubbleTimelineFragment;
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -33,6 +35,10 @@ public class TimelineDefinition {
private boolean listIsExclusive;
private @Nullable String hashtagName;
private @Nullable List<String> hashtagAny;
private @Nullable List<String> hashtagAll;
private @Nullable List<String> hashtagNone;
private boolean hashtagLocalOnly;
public static TimelineDefinition ofList(String listId, String listTitle, boolean listIsExclusive) {
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST);
@@ -79,10 +85,50 @@ public class TimelineDefinition {
return title;
}
@Nullable
public String getHashtagName() {
return hashtagName;
}
@Nullable
public List<String> getHashtagAny() {
return hashtagAny;
}
@Nullable
public List<String> getHashtagAll() {
return hashtagAll;
}
@Nullable
public List<String> getHashtagNone() {
return hashtagNone;
}
public boolean isHashtagLocalOnly() {
return hashtagLocalOnly;
}
public void setTitle(String title) {
this.title = title == null || title.isBlank() ? null : title;
}
private List<String> sanitizeTagList(List<String> tags) {
return tags.stream()
.map(String::trim)
.filter(str -> !TextUtils.isEmpty(str))
.collect(Collectors.toList());
}
public void setTagOptions(String main, List<String> any, List<String> all, List<String> none, boolean localOnly) {
this.hashtagName = main;
this.hashtagAny = sanitizeTagList(any);
this.hashtagAll = sanitizeTagList(all);
this.hashtagNone = sanitizeTagList(none);
this.hashtagLocalOnly = localOnly;
}
public String getDefaultTitle(Context ctx) {
return switch (type) {
case HOME -> ctx.getString(R.string.sk_timeline_home);
@@ -140,16 +186,17 @@ public class TimelineDefinition {
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());
if (type == TimelineType.HASHTAG) {
if (hashtagName == null && that.hashtagName == null) return true;
if (hashtagName == null || that.hashtagName == null) return false;
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;
return Objects.hash(type, title, listId, hashtagName, hashtagAny, hashtagAll, hashtagNone);
}
public TimelineDefinition copy() {
@@ -159,6 +206,9 @@ public class TimelineDefinition {
def.listTitle = listTitle;
def.listIsExclusive = listIsExclusive;
def.hashtagName = hashtagName;
def.hashtagAny = hashtagAny;
def.hashtagAll = hashtagAll;
def.hashtagNone = hashtagNone;
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
return def;
}
@@ -170,6 +220,10 @@ public class TimelineDefinition {
args.putBoolean("listIsExclusive", listIsExclusive);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtag", hashtagName);
args.putBoolean("localOnly", hashtagLocalOnly);
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));
args.putStringArrayList("none", hashtagNone == null ? new ArrayList<>() : new ArrayList<>(hashtagNone));
}
return args;
}