Make more clear representation of placeholder in notifications

This commit is contained in:
charlag 2017-11-05 19:11:00 +03:00 committed by Konrad Pozniak
parent 80a10c1ac1
commit 0dede1ba7d
7 changed files with 348 additions and 127 deletions

View File

@ -101,11 +101,19 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (position < notifications.size()) { if (position < notifications.size()) {
NotificationViewData notification = notifications.get(position); NotificationViewData notification = notifications.get(position);
Notification.Type type = notification.getType(); if (notification instanceof NotificationViewData.Placeholder) {
NotificationViewData.Placeholder placeholder = ((NotificationViewData.Placeholder) notification);
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!placeholder.isLoading(), statusListener);
return;
}
NotificationViewData.Concrete concreteNotificaton =
(NotificationViewData.Concrete) notification;
Notification.Type type = concreteNotificaton.getType();
switch (type) { switch (type) {
case MENTION: { case MENTION: {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusViewData status = notification.getStatusViewData(); StatusViewData status = concreteNotificaton.getStatusViewData();
holder.setupWithStatus(status, holder.setupWithStatus(status,
statusListener, mediaPreviewEnabled); statusListener, mediaPreviewEnabled);
break; break;
@ -113,23 +121,18 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case FAVOURITE: case FAVOURITE:
case REBLOG: { case REBLOG: {
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder; StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
holder.setMessage(type, notification.getAccount().getDisplayName(), holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(),
notification.getStatusViewData()); concreteNotificaton.getStatusViewData());
holder.setupButtons(notificationActionListener, notification.getAccount().id); holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().id);
holder.setAvatars(notification.getStatusViewData().getAvatar(), holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(),
notification.getAccount().avatar); concreteNotificaton.getAccount().avatar);
break; break;
} }
case FOLLOW: { case FOLLOW: {
FollowViewHolder holder = (FollowViewHolder) viewHolder; FollowViewHolder holder = (FollowViewHolder) viewHolder;
holder.setMessage(notification.getAccount().getDisplayName(), holder.setMessage(concreteNotificaton.getAccount().getDisplayName(),
notification.getAccount().username, notification.getAccount().avatar); concreteNotificaton.getAccount().username, concreteNotificaton.getAccount().avatar);
holder.setupButtons(notificationActionListener, notification.getAccount().id); holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().id);
break;
}
case PLACEHOLDER: {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!notification.isPlaceholderLoading(), statusListener);
break; break;
} }
} }
@ -150,21 +153,25 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
return VIEW_TYPE_FOOTER; return VIEW_TYPE_FOOTER;
} else { } else {
NotificationViewData notification = notifications.get(position); NotificationViewData notification = notifications.get(position);
switch (notification.getType()) { if (notification instanceof NotificationViewData.Concrete) {
default: NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification);
case MENTION: { switch (concrete.getType()) {
return VIEW_TYPE_MENTION; default:
} case MENTION: {
case FAVOURITE: return VIEW_TYPE_MENTION;
case REBLOG: { }
return VIEW_TYPE_STATUS_NOTIFICATION; case FAVOURITE:
} case REBLOG: {
case FOLLOW: { return VIEW_TYPE_STATUS_NOTIFICATION;
return VIEW_TYPE_FOLLOW; }
} case FOLLOW: {
case PLACEHOLDER: { return VIEW_TYPE_FOLLOW;
return VIEW_TYPE_PLACEHOLDER; }
} }
} else if (notification instanceof NotificationViewData.Placeholder) {
return VIEW_TYPE_PLACEHOLDER;
} else {
throw new AssertionError("Unknown notification type");
} }
} }
} }

View File

@ -27,7 +27,6 @@ public class Notification {
FAVOURITE, FAVOURITE,
@SerializedName("follow") @SerializedName("follow")
FOLLOW, FOLLOW,
PLACEHOLDER
} }
public Type type; public Type type;

View File

@ -44,6 +44,8 @@ import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.receiver.TimelineReceiver;
import com.keylesspalace.tusky.util.CollectionUtil;
import com.keylesspalace.tusky.util.Either;
import com.keylesspalace.tusky.util.HttpHeaderLink; import com.keylesspalace.tusky.util.HttpHeaderLink;
import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.PairedList; import com.keylesspalace.tusky.util.PairedList;
@ -74,6 +76,21 @@ public class NotificationsFragment extends SFragment implements
MIDDLE MIDDLE
} }
/**
* Placeholder for the notifications. Consider moving to the separate class to hide constructor
* and reuse in different places as needed.
*/
private static final class Placeholder {
private static final Placeholder INSTANCE = new Placeholder();
public static Placeholder getInstance() {
return INSTANCE;
}
private Placeholder() {
}
}
private SwipeRefreshLayout swipeRefreshLayout; private SwipeRefreshLayout swipeRefreshLayout;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
private RecyclerView recyclerView; private RecyclerView recyclerView;
@ -89,11 +106,17 @@ public class NotificationsFragment extends SFragment implements
private String bottomId; private String bottomId;
private String topId; private String topId;
private final PairedList<Notification, NotificationViewData> notifications // Each element is either a Notification for loading data or a Placeholder
= new PairedList<>(new Function<Notification, NotificationViewData>() { private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
= new PairedList<>(new Function<Either<Placeholder, Notification>, NotificationViewData>() {
@Override @Override
public NotificationViewData apply(Notification input) { public NotificationViewData apply(Either<Placeholder, Notification> input) {
return ViewDataUtils.notificationToViewData(input); if (input.isRight()) {
Notification notification = input.getAsRight();
return ViewDataUtils.notificationToViewData(notification);
} else {
return new NotificationViewData.Placeholder(false);
}
} }
}); });
@ -185,7 +208,7 @@ public class NotificationsFragment extends SFragment implements
ActionButtonActivity activity = (ActionButtonActivity) getActivity(); ActionButtonActivity activity = (ActionButtonActivity) getActivity();
FloatingActionButton composeButton = activity.getActionButton(); FloatingActionButton composeButton = activity.getActionButton();
if(composeButton != null) { if (composeButton != null) {
if (hideFab) { if (hideFab) {
if (dy > 0 && composeButton.isShown()) { if (dy > 0 && composeButton.isShown()) {
composeButton.hide(); // hides the button if we're scrolling down composeButton.hide(); // hides the button if we're scrolling down
@ -225,13 +248,12 @@ public class NotificationsFragment extends SFragment implements
@Override @Override
public void onReply(int position) { public void onReply(int position) {
Notification notification = notifications.get(position); super.reply(notifications.get(position).getAsRight().status);
super.reply(notification.status);
} }
@Override @Override
public void onReblog(final boolean reblog, final int position) { public void onReblog(final boolean reblog, final int position) {
final Notification notification = notifications.get(position); final Notification notification = notifications.get(position).getAsRight();
final Status status = notification.status; final Status status = notification.status;
reblogWithCallback(status, reblog, new Callback<Status>() { reblogWithCallback(status, reblog, new Callback<Status>() {
@Override @Override
@ -242,7 +264,9 @@ public class NotificationsFragment extends SFragment implements
if (status.reblog != null) { if (status.reblog != null) {
status.reblog.reblogged = reblog; status.reblog.reblogged = reblog;
} }
notifications.set(position, notification); // Java's type inference *eyeroll*
notifications.set(position,
Either.<Placeholder, Notification>right(notification));
adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true); adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true);
@ -260,7 +284,7 @@ public class NotificationsFragment extends SFragment implements
@Override @Override
public void onFavourite(final boolean favourite, final int position) { public void onFavourite(final boolean favourite, final int position) {
final Notification notification = notifications.get(position); final Notification notification = notifications.get(position).getAsRight();
final Status status = notification.status; final Status status = notification.status;
favouriteWithCallback(status, favourite, new Callback<Status>() { favouriteWithCallback(status, favourite, new Callback<Status>() {
@Override @Override
@ -272,7 +296,8 @@ public class NotificationsFragment extends SFragment implements
status.reblog.favourited = favourite; status.reblog.favourited = favourite;
} }
notifications.set(position, notification); notifications.set(position,
Either.<Placeholder, Notification>right(notification));
adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true); adapter.updateItemWithNotify(position, notifications.getPairedItem(position), true);
@ -289,7 +314,7 @@ public class NotificationsFragment extends SFragment implements
@Override @Override
public void onMore(View view, int position) { public void onMore(View view, int position) {
Notification notification = notifications.get(position); Notification notification = notifications.get(position).getAsRight();
super.more(notification.status, view, position); super.more(notification.status, view, position);
} }
@ -301,38 +326,40 @@ public class NotificationsFragment extends SFragment implements
@Override @Override
public void onViewThread(int position) { public void onViewThread(int position) {
Notification notification = notifications.get(position); Notification notification = notifications.get(position).getAsRight();
super.viewThread(notification.status); super.viewThread(notification.status);
} }
@Override @Override
public void onOpenReblog(int position) { public void onOpenReblog(int position) {
Notification notification = notifications.get(position); Notification notification = notifications.get(position).getAsRight();
if (notification != null) onViewAccount(notification.account.id); onViewAccount(notification.account.id);
} }
@Override @Override
public void onExpandedChange(boolean expanded, int position) { public void onExpandedChange(boolean expanded, int position) {
NotificationViewData old = notifications.getPairedItem(position); NotificationViewData.Concrete old =
(NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData statusViewData = StatusViewData statusViewData =
new StatusViewData.Builder(old.getStatusViewData()) new StatusViewData.Builder(old.getStatusViewData())
.setIsExpanded(expanded) .setIsExpanded(expanded)
.createStatusViewData(); .createStatusViewData();
NotificationViewData notificationViewData = new NotificationViewData(old.getType(), NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
old.getId(), old.getAccount(), statusViewData, false); old.getId(), old.getAccount(), statusViewData);
notifications.setPairedItem(position, notificationViewData); notifications.setPairedItem(position, notificationViewData);
adapter.updateItemWithNotify(position, notificationViewData, false); adapter.updateItemWithNotify(position, notificationViewData, false);
} }
@Override @Override
public void onContentHiddenChange(boolean isShowing, int position) { public void onContentHiddenChange(boolean isShowing, int position) {
NotificationViewData old = notifications.getPairedItem(position); NotificationViewData.Concrete old =
(NotificationViewData.Concrete) notifications.getPairedItem(position);
StatusViewData statusViewData = StatusViewData statusViewData =
new StatusViewData.Builder(old.getStatusViewData()) new StatusViewData.Builder(old.getStatusViewData())
.setIsShowingSensitiveContent(isShowing) .setIsShowingSensitiveContent(isShowing)
.createStatusViewData(); .createStatusViewData();
NotificationViewData notificationViewData = new NotificationViewData(old.getType(), NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
old.getId(), old.getAccount(), statusViewData, false); old.getId(), old.getAccount(), statusViewData);
notifications.setPairedItem(position, notificationViewData); notifications.setPairedItem(position, notificationViewData);
adapter.updateItemWithNotify(position, notificationViewData, false); adapter.updateItemWithNotify(position, notificationViewData, false);
} }
@ -341,13 +368,12 @@ public class NotificationsFragment extends SFragment implements
public void onLoadMore(int position) { public void onLoadMore(int position) {
//check bounds before accessing list, //check bounds before accessing list,
if (notifications.size() >= position && position > 0) { if (notifications.size() >= position && position > 0) {
String fromId = notifications.get(position - 1).id; // is it safe?
String toId = notifications.get(position + 1).id; String fromId = notifications.get(position - 1).getAsRight().id;
String toId = notifications.get(position + 1).getAsRight().id;
sendFetchNotificationsRequest(fromId, toId, FetchEnd.MIDDLE, position); sendFetchNotificationsRequest(fromId, toId, FetchEnd.MIDDLE, position);
NotificationViewData notificationViewData =
NotificationViewData old = notifications.getPairedItem(position); new NotificationViewData.Placeholder(true);
NotificationViewData notificationViewData = new NotificationViewData(old.getType(),
old.getId(), old.getAccount(), old.getStatusViewData(), true);
notifications.setPairedItem(position, notificationViewData); notifications.setPairedItem(position, notificationViewData);
adapter.updateItemWithNotify(position, notificationViewData, false); adapter.updateItemWithNotify(position, notificationViewData, false);
} else { } else {
@ -390,10 +416,11 @@ public class NotificationsFragment extends SFragment implements
@Override @Override
public void removeAllByAccountId(String accountId) { public void removeAllByAccountId(String accountId) {
// using iterator to safely remove items while iterating // using iterator to safely remove items while iterating
Iterator<Notification> iterator = notifications.iterator(); Iterator<Either<Placeholder, Notification>> iterator = notifications.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Notification notification = iterator.next(); Either<Placeholder, Notification> notification = iterator.next();
if (notification.account.id.equals(accountId)) { Notification maybeNotification = notification.getAsRightOrNull();
if (maybeNotification != null && maybeNotification.account.id.equals(accountId)) {
iterator.remove(); iterator.remove();
} }
} }
@ -470,7 +497,7 @@ public class NotificationsFragment extends SFragment implements
break; break;
} }
case MIDDLE: { case MIDDLE: {
insert(notifications, pos); replacePlaceholderWithNotifications(notifications, pos);
break; break;
} }
case BOTTOM: { case BOTTOM: {
@ -520,24 +547,25 @@ public class NotificationsFragment extends SFragment implements
if (uptoId != null) { if (uptoId != null) {
topId = uptoId; topId = uptoId;
} }
List<Either<Placeholder, Notification>> liftedNew =
liftNotificationList(newNotifications);
if (notifications.isEmpty()) { if (notifications.isEmpty()) {
notifications.addAll(newNotifications); notifications.addAll(liftedNew);
} else { } else {
int index = notifications.indexOf(newNotifications.get(newNotifications.size() - 1)); int index = notifications.indexOf(liftedNew.get(newNotifications.size() - 1));
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
notifications.remove(0); notifications.remove(0);
} }
int newIndex = newNotifications.indexOf(notifications.get(0));
int newIndex = liftedNew.indexOf(notifications.get(0));
if (newIndex == -1) { if (newIndex == -1) {
if(index == -1 && newNotifications.size() >= LOAD_AT_ONCE) { if (index == -1 && liftedNew.size() >= LOAD_AT_ONCE) {
Notification placeholder = new Notification(); liftedNew.add(Either.<Placeholder, Notification>left(Placeholder.getInstance()));
placeholder.type = Notification.Type.PLACEHOLDER;
newNotifications.add(placeholder);
} }
notifications.addAll(0, newNotifications); notifications.addAll(0, liftedNew);
} else { } else {
List<Notification> sublist = newNotifications.subList(0, newIndex); notifications.addAll(0, liftedNew.subList(0, newIndex));
notifications.addAll(0, sublist);
} }
} }
adapter.update(notifications.getPairedCopy()); adapter.update(notifications.getPairedCopy());
@ -551,9 +579,10 @@ public class NotificationsFragment extends SFragment implements
bottomId = fromId; bottomId = fromId;
} }
int end = notifications.size(); int end = notifications.size();
Notification last = notifications.get(end - 1); List<Either<Placeholder, Notification>> liftedNew = liftNotificationList(newNotifications);
if (last != null && !findNotification(newNotifications, last.id)) { Either<Placeholder, Notification> last = notifications.get(end - 1);
notifications.addAll(newNotifications); if (last != null && liftedNew.indexOf(last) == -1) {
notifications.addAll(liftedNew);
List<NotificationViewData> newViewDatas = notifications.getPairedCopy() List<NotificationViewData> newViewDatas = notifications.getPairedCopy()
.subList(notifications.size() - newNotifications.size(), .subList(notifications.size() - newNotifications.size(),
notifications.size()); notifications.size());
@ -561,23 +590,14 @@ public class NotificationsFragment extends SFragment implements
} }
} }
private static boolean findNotification(List<Notification> notifications, String id) {
for (Notification notification : notifications) {
if (notification.id.equals(id)) {
return true;
}
}
return false;
}
private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) { private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
if(fetchEnd == FetchEnd.MIDDLE && notifications.getPairedItem(position).getType() == Notification.Type.PLACEHOLDER) { if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
NotificationViewData old = notifications.getPairedItem(position); NotificationViewData placeholderVD =
NotificationViewData notificationViewData = new NotificationViewData(old.getType(), new NotificationViewData.Placeholder(false);
old.getId(), old.getAccount(), old.getStatusViewData(), false); notifications.setPairedItem(position, placeholderVD);
notifications.setPairedItem(position, notificationViewData); adapter.updateItemWithNotify(position, placeholderVD, true);
adapter.updateItemWithNotify(position, notificationViewData, true);
} }
Log.e(TAG, "Fetch failure: " + exception.getMessage()); Log.e(TAG, "Fetch failure: " + exception.getMessage());
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
@ -604,8 +624,8 @@ public class NotificationsFragment extends SFragment implements
} }
} }
private void insert(List<Notification> newNotifications, int pos) { private void replacePlaceholderWithNotifications(List<Notification> newNotifications, int pos) {
// Remove placeholder
notifications.remove(pos); notifications.remove(pos);
if (ListUtils.isEmpty(newNotifications)) { if (ListUtils.isEmpty(newNotifications)) {
@ -613,15 +633,29 @@ public class NotificationsFragment extends SFragment implements
return; return;
} }
if(newNotifications.size() >= LOAD_AT_ONCE) { List<Either<Placeholder, Notification>> liftedNew = liftNotificationList(newNotifications);
Notification placeholder = new Notification();
placeholder.type = Notification.Type.PLACEHOLDER; // If we fetched less posts than in the limit, it means that the hole is not filled
newNotifications.add(placeholder); // If we fetched at least as much it means that there are more posts to load and we should
// insert new placeholder
if (newNotifications.size() >= LOAD_AT_ONCE) {
liftedNew.add(Either.<Placeholder, Notification>left(Placeholder.getInstance()));
} }
notifications.addAll(pos, newNotifications); notifications.addAll(pos, liftedNew);
adapter.update(notifications.getPairedCopy()); adapter.update(notifications.getPairedCopy());
}
private final Function<Notification, Either<Placeholder, Notification>> notificationLifter =
new Function<Notification, Either<Placeholder, Notification>>() {
@Override
public Either<Placeholder, Notification> apply(Notification input) {
return Either.right(input);
}
};
private List<Either<Placeholder, Notification>> liftNotificationList(List<Notification> list) {
return CollectionUtil.map(list, notificationLifter);
} }
private void fullyRefresh() { private void fullyRefresh() {

View File

@ -0,0 +1,38 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.arch.core.util.Function;
import java.util.ArrayList;
import java.util.List;
/**
* Created by charlag on 05/11/17.
*/
public final class CollectionUtil {
private CollectionUtil() {
throw new AssertionError();
}
public static <E, R> List<R> map(List<E> list, Function<E, R> mapper) {
final List<R> newList = new ArrayList<>(list.size());
for (E el : list) {
newList.add(mapper.apply(el));
}
return newList;
}
}

View File

@ -0,0 +1,125 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Created by charlag on 05/11/17.
*
* Class to represent sum type/tagged union/variant/ADT e.t.c.
* It is either Left or Right.
*/
public final class Either<L, R> {
/**
* Constructs Left instance of either
* @param left Object to be considered Left
* @param <L> Left type
* @param <R> Right type
* @return new instance of Either which contains left.
*/
public static <L, R> Either<L, R> left(L left) {
return new Either<>(left, false);
}
/**
* Constructs Right instance of either
* @param right Object to be considered Right
* @param <L> Left type
* @param <R> Right type
* @return new instance of Either which contains right.
*/
public static <L, R> Either<L, R> right(R right) {
return new Either<>(right, true);
}
private final Object value;
// we need it because of the types erasure
private boolean isRight;
private Either(Object value, boolean isRight) {
this.value = value;
this.isRight = isRight;
}
public boolean isRight() {
return isRight;
}
/**
* Try to get contained object as a Left or throw an exception.
* @throws AssertionError If contained value is Right
* @return contained value as Right
*/
public @NonNull L getAsLeft() {
if (isRight) {
throw new AssertionError("Tried to get the Either as Left while it is Right");
}
//noinspection unchecked
return (L) value;
}
/**
* Try to get contained object as a Right or throw an exception.
* @throws AssertionError If contained value is Left
* @return contained value as Right
*/
public @NonNull R getAsRight() {
if (!isRight) {
throw new AssertionError("Tried to get the Either as Right while it is Left");
}
//noinspection unchecked
return (R) value;
}
/**
* Same as {@link #getAsLeft()} but returns {@code null} is the value if Right instead of
* throwing an exception.
* @return contained value as Left or null
*/
public @Nullable L getAsLeftOrNull() {
if (isRight) {
return null;
}
//noinspection unchecked
return (L) value;
}
/**
* Same as {@link #getAsRightOrNull()} but returns {@code null} is the value if Left instead of
* throwing an exception.
* @return contained value as Right or null
*/
public @Nullable R getAsRightOrNull() {
if (!isRight) {
return null;
}
//noinspection unchecked
return (R) value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof Either)) return false;
Either that = (Either) obj;
return this.isRight == that.isRight &&
(this.value == that.value ||
this.value != null && this.value.equals(that.value));
}
}

View File

@ -34,7 +34,7 @@ public final class ViewDataUtils {
@Nullable @Nullable
public static StatusViewData statusToViewData(@Nullable Status status) { public static StatusViewData statusToViewData(@Nullable Status status) {
if (status == null) return null; if (status == null) return null;
if(status.placeholder) { if (status.placeholder) {
return new StatusViewData.Builder().setId(status.id) return new StatusViewData.Builder().setId(status.id)
.setPlaceholder(true) .setPlaceholder(true)
.createStatusViewData(); .createStatusViewData();
@ -80,8 +80,8 @@ public final class ViewDataUtils {
} }
public static NotificationViewData notificationToViewData(Notification notification) { public static NotificationViewData notificationToViewData(Notification notification) {
return new NotificationViewData(notification.type, notification.id, notification.account, return new NotificationViewData.Concrete(notification.type, notification.id, notification.account,
statusToViewData(notification.status), false); statusToViewData(notification.status));
} }
public static List<NotificationViewData> notificationListToViewDataList( public static List<NotificationViewData> notificationListToViewDataList(

View File

@ -20,41 +20,59 @@ import com.keylesspalace.tusky.entity.Notification;
/** /**
* Created by charlag on 12/07/2017. * Created by charlag on 12/07/2017.
*
* Class to represent data required to display either a notification or a placeholder.
* It is either a {@link Placeholder} or a {@link Concrete}.
* It is modelled this way because close relationship between placeholder and concrete notification
* is fine in this case. Placeholder case is not modelled as a type of notification because
* invariants would be violated and because it would model domain incorrectly. It is prefereable to
* {@link com.keylesspalace.tusky.util.Either} because class hierarchy is cheaper, faster and
* more native.
*/ */
public abstract class NotificationViewData {
public final class NotificationViewData { private NotificationViewData() {
private final Notification.Type type;
private final String id;
private final Account account;
private final StatusViewData statusViewData;
private final boolean placeholderLoading;
public NotificationViewData(Notification.Type type, String id, Account account,
StatusViewData statusViewData, boolean placeholderLoading) {
this.type = type;
this.id = id;
this.account = account;
this.statusViewData = statusViewData;
this.placeholderLoading = placeholderLoading;
} }
public Notification.Type getType() { public static final class Concrete extends NotificationViewData {
return type; private final Notification.Type type;
private final String id;
private final Account account;
private final StatusViewData statusViewData;
public Concrete(Notification.Type type, String id, Account account,
StatusViewData statusViewData) {
this.type = type;
this.id = id;
this.account = account;
this.statusViewData = statusViewData;
}
public Notification.Type getType() {
return type;
}
public String getId() {
return id;
}
public Account getAccount() {
return account;
}
public StatusViewData getStatusViewData() {
return statusViewData;
}
} }
public String getId() { public static final class Placeholder extends NotificationViewData {
return id; private final boolean isLoading;
}
public Account getAccount() { public Placeholder(boolean isLoading) {
return account; this.isLoading = isLoading;
} }
public StatusViewData getStatusViewData() { public boolean isLoading() {
return statusViewData; return isLoading;
} }
public boolean isPlaceholderLoading() {
return placeholderLoading;
} }
} }