* add bookmarks to timelines

* add Bookmarks to main menu

* cleanup

* handle BookmarkEvent

* fix tests

* fix bookmark handling in NotificationsFragment

* add bookmark accessibility actions
This commit is contained in:
Konrad Pozniak 2019-11-19 10:15:32 +01:00 committed by GitHub
commit d9694df0c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1235 additions and 122 deletions

View file

@ -55,6 +55,7 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.BookmarkEvent;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.FavoriteEvent;
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent;
@ -309,6 +310,16 @@ public class NotificationsFragment extends SFragment implements
event.getFavourite());
}
private void handleBookmarkEvent(BookmarkEvent event) {
Pair<Integer, Notification> posAndNotification =
findReplyPosition(event.getStatusId());
if (posAndNotification == null) return;
//noinspection ConstantConditions
setBookmarkForStatus(posAndNotification.first,
posAndNotification.second.getStatus(),
event.getBookmark());
}
private void handleReblogEvent(ReblogEvent event) {
Pair<Integer, Notification> posAndNotification = findReplyPosition(event.getStatusId());
if (posAndNotification == null) return;
@ -365,6 +376,8 @@ public class NotificationsFragment extends SFragment implements
.subscribe(event -> {
if (event instanceof FavoriteEvent) {
handleFavEvent((FavoriteEvent) event);
} else if (event instanceof BookmarkEvent) {
handleBookmarkEvent((BookmarkEvent) event);
} else if (event instanceof ReblogEvent) {
handleReblogEvent((ReblogEvent) event);
} else if (event instanceof BlockEvent) {
@ -463,6 +476,41 @@ public class NotificationsFragment extends SFragment implements
updateAdapter();
}
@Override
public void onBookmark(final boolean bookmark, final int position) {
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
timelineCases.bookmark(status, bookmark)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this)))
.subscribe(
(newStatus) -> setBookmarkForStatus(position, status, bookmark),
(t) -> Log.d(getClass().getSimpleName(),
"Failed to bookmark status: " + status.getId(), t)
);
}
private void setBookmarkForStatus(int position, Status status, boolean bookmark) {
status.setBookmarked(bookmark);
if (status.getReblog() != null) {
status.getReblog().setBookmarked(bookmark);
}
NotificationViewData.Concrete viewdata = (NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder(viewdata.getStatusViewData());
viewDataBuilder.setBookmarked(bookmark);
NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
viewDataBuilder.createStatusViewData(), viewdata.isExpanded());
notifications.setPairedItem(position, newViewData);
updateAdapter();
}
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();

View file

@ -49,6 +49,7 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.BookmarkEvent;
import com.keylesspalace.tusky.appstore.DomainMuteEvent;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.FavoriteEvent;
@ -128,7 +129,8 @@ public class TimelineFragment extends SFragment implements
USER_PINNED,
USER_WITH_REPLIES,
FAVOURITES,
LIST
LIST,
BOOKMARKS
}
private enum FetchEnd {
@ -492,6 +494,9 @@ public class TimelineFragment extends SFragment implements
} else if (event instanceof ReblogEvent) {
ReblogEvent reblogEvent = (ReblogEvent) event;
handleReblogEvent(reblogEvent);
} else if (event instanceof BookmarkEvent) {
BookmarkEvent bookmarkEvent = (BookmarkEvent) event;
handleBookmarkEvent(bookmarkEvent);
} else if (event instanceof UnfollowEvent) {
if (kind == Kind.HOME) {
String id = ((UnfollowEvent) event).getAccountId();
@ -630,6 +635,38 @@ public class TimelineFragment extends SFragment implements
updateAdapter();
}
@Override
public void onBookmark(final boolean bookmark, final int position) {
final Status status = statuses.get(position).asRight();
timelineCases.bookmark(status, bookmark)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(
(newStatus) -> setBookmarkForStatus(position, newStatus, bookmark),
(err) -> Log.d(TAG, "Failed to favourite status " + status.getId(), err)
);
}
private void setBookmarkForStatus(int position, Status status, boolean bookmark) {
status.setBookmarked(bookmark);
if (status.getReblog() != null) {
status.getReblog().setBookmarked(bookmark);
}
Pair<StatusViewData.Concrete, Integer> actual =
findStatusAndPosition(position, status);
if (actual == null) return;
StatusViewData newViewData = new StatusViewData
.Builder(actual.first)
.setBookmarked(bookmark)
.createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
final Status status = statuses.get(position).asRight();
@ -917,7 +954,7 @@ public class TimelineFragment extends SFragment implements
}
private boolean actionButtonPresent() {
return kind != Kind.TAG && kind != Kind.FAVOURITES &&
return kind != Kind.TAG && kind != Kind.FAVOURITES && kind != Kind.BOOKMARKS &&
getActivity() instanceof ActionButtonActivity;
}
@ -950,6 +987,8 @@ public class TimelineFragment extends SFragment implements
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null, null, null);
case FAVOURITES:
return api.favourites(fromId, uptoId, LOAD_AT_ONCE);
case BOOKMARKS:
return api.bookmarks(fromId, uptoId, LOAD_AT_ONCE);
case LIST:
return api.listTimeline(tagOrId, fromId, uptoId, LOAD_AT_ONCE);
}
@ -1095,11 +1134,8 @@ public class TimelineFragment extends SFragment implements
}
private void updateBottomLoadingState(FetchEnd fetchEnd) {
switch (fetchEnd) {
case BOTTOM: {
bottomLoading = false;
break;
}
if (fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = false;
}
}
@ -1223,8 +1259,8 @@ public class TimelineFragment extends SFragment implements
private final Function1<Status, Either<Placeholder, Status>> statusLifter =
Either.Right::new;
private @Nullable
Pair<StatusViewData.Concrete, Integer>
@Nullable
private Pair<StatusViewData.Concrete, Integer>
findStatusAndPosition(int position, Status status) {
StatusViewData.Concrete statusToUpdate;
int positionToUpdate;
@ -1260,6 +1296,13 @@ public class TimelineFragment extends SFragment implements
setFavouriteForStatus(pos, status, favEvent.getFavourite());
}
private void handleBookmarkEvent(@NonNull BookmarkEvent bookmarkEvent) {
int pos = findStatusOrReblogPositionById(bookmarkEvent.getStatusId());
if (pos < 0) return;
Status status = statuses.get(pos).asRight();
setBookmarkForStatus(pos, status, bookmarkEvent.getBookmark());
}
private void handleStatusComposeEvent(@NonNull Status status) {
switch (kind) {
case HOME:

View file

@ -45,6 +45,7 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.ViewThreadActivity;
import com.keylesspalace.tusky.adapter.ThreadAdapter;
import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.BookmarkEvent;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.FavoriteEvent;
import com.keylesspalace.tusky.appstore.ReblogEvent;
@ -186,6 +187,8 @@ public final class ViewThreadFragment extends SFragment implements
handleFavEvent((FavoriteEvent) event);
} else if (event instanceof ReblogEvent) {
handleReblogEvent((ReblogEvent) event);
} else if (event instanceof BookmarkEvent) {
handleBookmarkEvent((BookmarkEvent) event);
} else if (event instanceof BlockEvent) {
removeAllByAccountId(((BlockEvent) event).getAccountId());
} else if (event instanceof StatusComposedEvent) {
@ -239,7 +242,7 @@ public final class ViewThreadFragment extends SFragment implements
.as(autoDisposable(from(this)))
.subscribe(
(newStatus) -> updateStatus(position, newStatus),
(t) -> Log.d(getClass().getSimpleName(),
(t) -> Log.d(TAG,
"Failed to reblog status: " + status.getId(), t)
);
}
@ -253,11 +256,25 @@ public final class ViewThreadFragment extends SFragment implements
.as(autoDisposable(from(this)))
.subscribe(
(newStatus) -> updateStatus(position, newStatus),
(t) -> Log.d(getClass().getSimpleName(),
(t) -> Log.d(TAG,
"Failed to favourite status: " + status.getId(), t)
);
}
@Override
public void onBookmark(final boolean bookmark, final int position) {
final Status status = statuses.get(position);
timelineCases.bookmark(statuses.get(position), bookmark)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this)))
.subscribe(
(newStatus) -> updateStatus(position, newStatus),
(t) -> Log.d(TAG,
"Failed to bookmark status: " + status.getId(), t)
);
}
private void updateStatus(int position, Status status) {
if (position >= 0 && position < statuses.size()) {
@ -267,6 +284,7 @@ public final class ViewThreadFragment extends SFragment implements
.setReblogged(actionableStatus.getReblogged())
.setReblogsCount(actionableStatus.getReblogsCount())
.setFavourited(actionableStatus.getFavourited())
.setBookmarked(actionableStatus.getBookmarked())
.setFavouritesCount(actionableStatus.getFavouritesCount())
.createStatusViewData();
statuses.setPairedItem(position, viewData);
@ -621,6 +639,28 @@ public final class ViewThreadFragment extends SFragment implements
adapter.setItem(posAndStatus.first, newViewData, true);
}
private void handleBookmarkEvent(BookmarkEvent event) {
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
if (posAndStatus == null) return;
boolean bookmark = event.getBookmark();
posAndStatus.second.setBookmarked(bookmark);
if (posAndStatus.second.getReblog() != null) {
posAndStatus.second.getReblog().setBookmarked(bookmark);
}
StatusViewData.Concrete viewdata = statuses.getPairedItem(posAndStatus.first);
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
viewDataBuilder.setBookmarked(bookmark);
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
statuses.setPairedItem(posAndStatus.first, newViewData);
adapter.setItem(posAndStatus.first, newViewData, true);
}
private void handleStatusComposedEvent(StatusComposedEvent event) {
Status eventStatus = event.getStatus();
if (eventStatus.getInReplyToId() == null) return;