Skip to content
This repository was archived by the owner on May 30, 2025. It is now read-only.

Commit 84179bc

Browse files
committed
implement announcements
closes #127
1 parent 9dc795d commit 84179bc

16 files changed

+335
-10
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.joinmastodon.android.api.requests.announcements;
2+
3+
import org.joinmastodon.android.api.MastodonAPIRequest;
4+
5+
public class DismissAnnouncement extends MastodonAPIRequest<Object>{
6+
public DismissAnnouncement(String id){
7+
super(HttpMethod.POST, "/announcements/" + id + "/dismiss", Object.class);
8+
setRequestBody(new Object());
9+
}
10+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.joinmastodon.android.api.requests.announcements;
2+
3+
import com.google.gson.reflect.TypeToken;
4+
5+
import org.joinmastodon.android.api.MastodonAPIRequest;
6+
import org.joinmastodon.android.model.Announcement;
7+
8+
import java.util.List;
9+
10+
public class GetAnnouncements extends MastodonAPIRequest<List<Announcement>> {
11+
public GetAnnouncements(boolean withDismissed) {
12+
super(MastodonAPIRequest.HttpMethod.GET, "/announcements", new TypeToken<>(){});
13+
addQueryParameter("with_dismissed", withDismissed ? "true" : "false");
14+
}
15+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package org.joinmastodon.android.fragments;
2+
3+
import android.app.Activity;
4+
import android.os.Bundle;
5+
import android.text.TextUtils;
6+
import android.view.View;
7+
import android.widget.ImageButton;
8+
9+
import com.squareup.otto.Subscribe;
10+
11+
import org.joinmastodon.android.E;
12+
import org.joinmastodon.android.R;
13+
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
14+
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
15+
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
16+
import org.joinmastodon.android.api.session.AccountSession;
17+
import org.joinmastodon.android.api.session.AccountSessionManager;
18+
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
19+
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
20+
import org.joinmastodon.android.model.Account;
21+
import org.joinmastodon.android.model.Announcement;
22+
import org.joinmastodon.android.model.HeaderPaginationList;
23+
import org.joinmastodon.android.model.Instance;
24+
import org.joinmastodon.android.model.ScheduledStatus;
25+
import org.joinmastodon.android.model.Status;
26+
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
27+
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
28+
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
29+
import org.joinmastodon.android.ui.text.HtmlParser;
30+
import org.joinmastodon.android.ui.utils.UiUtils;
31+
import org.parceler.Parcels;
32+
33+
import java.util.ArrayList;
34+
import java.util.Collections;
35+
import java.util.List;
36+
import java.util.stream.Collectors;
37+
38+
import me.grishka.appkit.Nav;
39+
import me.grishka.appkit.api.PaginatedList;
40+
import me.grishka.appkit.api.SimpleCallback;
41+
42+
public class AnnouncementsFragment extends BaseStatusListFragment<Announcement> {
43+
private Instance instance;
44+
private AccountSession session;
45+
private List<String> unreadIDs = null;
46+
47+
@Override
48+
public void onAttach(Activity activity){
49+
super.onAttach(activity);
50+
setTitle(R.string.sk_announcements);
51+
session = AccountSessionManager.getInstance().getAccount(accountID);
52+
instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
53+
loadData();
54+
}
55+
56+
@Override
57+
protected List<StatusDisplayItem> buildDisplayItems(Announcement a) {
58+
if(TextUtils.isEmpty(a.content)) return List.of();
59+
Account instanceUser = new Account();
60+
instanceUser.id = instanceUser.acct = instanceUser.username = session.domain;
61+
instanceUser.displayName = instance.title;
62+
instanceUser.url = "https://"+session.domain+"/about";
63+
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
64+
instanceUser.emojis = List.of();
65+
Status fakeStatus = a.toStatus();
66+
return List.of(
67+
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
68+
new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus)
69+
);
70+
}
71+
72+
public void onMarkAsRead(String id) {
73+
if (unreadIDs == null) return;
74+
unreadIDs.remove(id);
75+
if (unreadIDs.size() == 0) setResult(true, null);
76+
}
77+
78+
@Override
79+
public void onDestroy() {
80+
super.onDestroy();
81+
}
82+
83+
@Override
84+
protected void addAccountToKnown(Announcement s) {}
85+
86+
@Override
87+
public void onItemClick(String id) {
88+
}
89+
90+
@Override
91+
protected void onDataLoaded(List<Announcement> d, boolean more) {
92+
unreadIDs = d.stream().filter(a -> !a.read).map(a -> a.id).collect(Collectors.toList());
93+
super.onDataLoaded(d, more);
94+
}
95+
96+
@Override
97+
protected void doLoadData(int offset, int count){
98+
currentRequest=new GetAnnouncements(true)
99+
.setCallback(new SimpleCallback<>(this){
100+
@Override
101+
public void onSuccess(List<Announcement> result){
102+
onDataLoaded(result, false);
103+
}
104+
})
105+
.exec(accountID);
106+
}
107+
}

mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
import org.joinmastodon.android.E;
3030
import org.joinmastodon.android.GlobalUserPreferences;
3131
import org.joinmastodon.android.R;
32+
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
3233
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
3334
import org.joinmastodon.android.api.session.AccountSessionManager;
3435
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
3536
import org.joinmastodon.android.events.StatusCreatedEvent;
37+
import org.joinmastodon.android.model.Announcement;
3638
import org.joinmastodon.android.model.CacheablePaginatedResponse;
3739
import org.joinmastodon.android.model.Filter;
3840
import org.joinmastodon.android.model.Status;
@@ -56,11 +58,14 @@
5658
import me.grishka.appkit.utils.V;
5759

5860
public class HomeTimelineFragment extends StatusListFragment{
61+
private static final int ANNOUNCEMENTS_RESULT = 654;
62+
5963
private ImageButton fab;
6064
private ImageView toolbarLogo;
6165
private Button toolbarShowNewPostsBtn;
6266
private boolean newPostsBtnShown;
6367
private AnimatorSet currentNewPostsAnim;
68+
private MenuItem announcements;
6469

6570
private String maxID;
6671

@@ -126,16 +131,40 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
126131
@Override
127132
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
128133
inflater.inflate(R.menu.home, menu);
134+
announcements = menu.findItem(R.id.announcements);
135+
136+
new GetAnnouncements(false).setCallback(new Callback<>() {
137+
@Override
138+
public void onSuccess(List<Announcement> result) {
139+
boolean hasUnread = result.stream().anyMatch(a -> !a.read);
140+
announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular);
141+
}
142+
143+
@Override
144+
public void onError(ErrorResponse error) {
145+
error.showToast(getActivity());
146+
}
147+
}).exec(accountID);
129148
}
130149

131150
@Override
132151
public boolean onOptionsItemSelected(MenuItem item){
133152
Bundle args=new Bundle();
134153
args.putString("account", accountID);
135-
Nav.go(getActivity(), SettingsFragment.class, args);
154+
if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args);
155+
if (item.getItemId() == R.id.announcements) {
156+
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
157+
}
136158
return true;
137159
}
138160

161+
@Override
162+
public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){
163+
if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) {
164+
announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular);
165+
}
166+
}
167+
139168
@Override
140169
public void onConfigurationChanged(Configuration newConfig){
141170
super.onConfigurationChanged(newConfig);

mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/CustomWelcomeFragment.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ protected RecyclerView.Adapter getAdapter(){
139139
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
140140
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
141141
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
142+
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
142143
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
143144
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
144145
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.joinmastodon.android.model;
2+
3+
import org.joinmastodon.android.api.RequiredField;
4+
import org.parceler.Parcel;
5+
6+
import java.time.Instant;
7+
import java.util.List;
8+
9+
@Parcel
10+
public class Announcement extends BaseModel implements DisplayItemsParent {
11+
@RequiredField
12+
public String id;
13+
@RequiredField
14+
public String content;
15+
public Instant startsAt;
16+
public Instant endsAt;
17+
public boolean published;
18+
public boolean allDay;
19+
public Instant publishedAt;
20+
public Instant updatedAt;
21+
public boolean read;
22+
public List<Emoji> emojis;
23+
public List<Mention> mentions;
24+
public List<Hashtag> tags;
25+
26+
@Override
27+
public String toString() {
28+
return "Announcement{" +
29+
"id='" + id + '\'' +
30+
", content='" + content + '\'' +
31+
", startsAt=" + startsAt +
32+
", endsAt=" + endsAt +
33+
", published=" + published +
34+
", allDay=" + allDay +
35+
", publishedAt=" + publishedAt +
36+
", updatedAt=" + updatedAt +
37+
", read=" + read +
38+
", emojis=" + emojis +
39+
", mentions=" + mentions +
40+
", tags=" + tags +
41+
'}';
42+
}
43+
44+
public Status toStatus() {
45+
Status s = new Status();
46+
s.id = id;
47+
s.mediaAttachments = List.of();
48+
s.createdAt = startsAt != null ? startsAt : publishedAt;
49+
if (updatedAt != null) s.editedAt = updatedAt;
50+
s.content = s.text = content;
51+
s.spoilerText = "";
52+
s.visibility = StatusPrivacy.PUBLIC;
53+
s.mentions = List.of();
54+
s.tags = List.of();
55+
s.emojis = List.of();
56+
return s;
57+
}
58+
59+
@Override
60+
public String getID() {
61+
return id;
62+
}
63+
}

0 commit comments

Comments
 (0)