Merge pull request #439 from charlag/status-notifications-behavior
Status notifications behavior (closes #322, partly #292)
This commit is contained in:
commit
2816d9f929
|
@ -24,15 +24,20 @@ import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||||
import com.keylesspalace.tusky.view.RoundedTransformation;
|
import com.keylesspalace.tusky.view.RoundedTransformation;
|
||||||
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
||||||
|
@ -123,9 +128,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
|
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
|
||||||
holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(),
|
holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(),
|
||||||
concreteNotificaton.getStatusViewData());
|
concreteNotificaton.getStatusViewData());
|
||||||
holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().id);
|
holder.setupButtons(notificationActionListener,
|
||||||
|
concreteNotificaton.getAccount().id,
|
||||||
|
concreteNotificaton.getId());
|
||||||
holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(),
|
holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(),
|
||||||
concreteNotificaton.getAccount().avatar);
|
concreteNotificaton.getId());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FOLLOW: {
|
case FOLLOW: {
|
||||||
|
@ -211,6 +218,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
|
|
||||||
public interface NotificationActionListener {
|
public interface NotificationActionListener {
|
||||||
void onViewAccount(String id);
|
void onViewAccount(String id);
|
||||||
|
|
||||||
|
void onViewStatusForNotificationId(String notificationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FollowViewHolder extends RecyclerView.ViewHolder {
|
private static class FollowViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@ -258,13 +267,23 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder {
|
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder
|
||||||
private TextView message;
|
implements View.OnClickListener, ToggleButton.OnCheckedChangeListener {
|
||||||
private ImageView icon;
|
private final TextView message;
|
||||||
private TextView statusContent;
|
private final ImageView icon;
|
||||||
private ViewGroup container;
|
private final TextView statusContent;
|
||||||
private ImageView statusAvatar;
|
private final ViewGroup container;
|
||||||
private ImageView notificationAvatar;
|
private final ImageView statusAvatar;
|
||||||
|
private final ImageView notificationAvatar;
|
||||||
|
private final ViewGroup topBar;
|
||||||
|
private final View contentWarningBar;
|
||||||
|
private final TextView contentWarningDescriptionTextView;
|
||||||
|
private final ToggleButton contentWarningButton;
|
||||||
|
|
||||||
|
private String accountId;
|
||||||
|
private String notificationId;
|
||||||
|
private NotificationActionListener listener;
|
||||||
|
private StatusViewData.Concrete statusViewData;
|
||||||
|
|
||||||
StatusNotificationViewHolder(View itemView) {
|
StatusNotificationViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
@ -274,13 +293,24 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
container = itemView.findViewById(R.id.notification_container);
|
container = itemView.findViewById(R.id.notification_container);
|
||||||
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
|
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
|
||||||
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
|
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
|
||||||
|
topBar = itemView.findViewById(R.id.notification_top_bar);
|
||||||
|
contentWarningBar = itemView.findViewById(R.id.notification_content_warning_bar);
|
||||||
|
contentWarningDescriptionTextView = itemView.findViewById(R.id.notification_content_warning_description);
|
||||||
|
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
|
||||||
|
|
||||||
int darkerFilter = Color.rgb(123, 123, 123);
|
int darkerFilter = Color.rgb(123, 123, 123);
|
||||||
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
||||||
notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
|
container.setOnClickListener(this);
|
||||||
|
topBar.setOnClickListener(this);
|
||||||
|
contentWarningButton.setOnCheckedChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMessage(Notification.Type type, String displayName,
|
void setMessage(Notification.Type type, String displayName,
|
||||||
StatusViewData.Concrete status) {
|
StatusViewData.Concrete status) {
|
||||||
|
this.statusViewData = status;
|
||||||
|
|
||||||
Context context = message.getContext();
|
Context context = message.getContext();
|
||||||
String format;
|
String format;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -305,16 +335,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
|
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
message.setText(str);
|
message.setText(str);
|
||||||
statusContent.setText(status.getContent());
|
|
||||||
|
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
|
||||||
|
contentWarningBar.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
|
||||||
|
setupContentAndSpoiler(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupButtons(final NotificationActionListener listener, final String accountId) {
|
void setupButtons(final NotificationActionListener listener, final String accountId,
|
||||||
container.setOnClickListener(new View.OnClickListener() {
|
final String notificationId) {
|
||||||
@Override
|
this.listener = listener;
|
||||||
public void onClick(View v) {
|
this.accountId = accountId;
|
||||||
listener.onViewAccount(accountId);
|
this.notificationId = notificationId;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) {
|
void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) {
|
||||||
|
@ -341,5 +372,51 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
.into(notificationAvatar);
|
.into(notificationAvatar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
switch (v.getId()) {
|
||||||
|
case R.id.notification_container:
|
||||||
|
if (listener != null) listener.onViewStatusForNotificationId(notificationId);
|
||||||
|
break;
|
||||||
|
case R.id.notification_top_bar:
|
||||||
|
if (listener != null) listener.onViewAccount(accountId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupContentAndSpoiler(boolean shouldShowContentIfSpoiler) {
|
||||||
|
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
|
||||||
|
CharSequence content;
|
||||||
|
if (!shouldShowContentIfSpoiler && hasSpoiler) {
|
||||||
|
if (statusViewData.getMentions() != null &&
|
||||||
|
statusViewData.getMentions().length > 0) {
|
||||||
|
// If there is a content warning and mentions we're alternating between
|
||||||
|
// showing mentions and showing full content. As mentions are plain text we
|
||||||
|
// have to construct URLSpans ourselves.
|
||||||
|
SpannableStringBuilder contentBuilder = new SpannableStringBuilder();
|
||||||
|
for (Status.Mention mention : statusViewData.getMentions()) {
|
||||||
|
int start = contentBuilder.length() > 0 ? contentBuilder.length() - 1 : 0;
|
||||||
|
contentBuilder.append('@');
|
||||||
|
contentBuilder.append(mention.username);
|
||||||
|
contentBuilder.append(' ');
|
||||||
|
contentBuilder.setSpan(new URLSpan(mention.url), start,
|
||||||
|
mention.username.length() + 1, 0);
|
||||||
|
}
|
||||||
|
content = contentBuilder;
|
||||||
|
} else {
|
||||||
|
content = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = statusViewData.getContent();
|
||||||
|
}
|
||||||
|
statusContent.setText(content);
|
||||||
|
contentWarningDescriptionTextView.setText(statusViewData.getSpoilerText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
setupContentAndSpoiler(isChecked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,6 +394,18 @@ public class NotificationsFragment extends SFragment implements
|
||||||
super.viewAccount(id);
|
super.viewAccount(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStatusForNotificationId(String notificationId) {
|
||||||
|
for (Either<Placeholder, Notification> either : notifications) {
|
||||||
|
Notification notification = either.getAsRightOrNull();
|
||||||
|
if (notification != null && notification.id.equals(notificationId)) {
|
||||||
|
super.viewThread(notification.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.w(TAG, "Didn't find a notification for ID: " + notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
|
|
@ -40,11 +40,50 @@
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<com.keylesspalace.tusky.view.FlowLayout
|
||||||
|
android:id="@+id/notification_content_warning_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/notification_top_bar"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_toEndOf="@+id/notification_status_avatar"
|
||||||
|
android:layout_toRightOf="@+id/notification_status_avatar"
|
||||||
|
android:focusable="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:paddingHorizontal="4dp"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_content_warning_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:textColorTertiary"
|
||||||
|
tools:text="Example CW text"/>
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/notification_content_warning_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="?attr/content_warning_button"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:paddingBottom="3dp"
|
||||||
|
android:paddingLeft="6dp"
|
||||||
|
android:paddingRight="6dp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textOff="@string/status_content_warning_show_more"
|
||||||
|
android:textOn="@string/status_content_warning_show_less"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</com.keylesspalace.tusky.view.FlowLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/notification_content"
|
android:id="@+id/notification_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/notification_top_bar"
|
android:layout_below="@id/notification_content_warning_bar"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingEnd="0dp"
|
android:paddingEnd="0dp"
|
||||||
android:paddingLeft="58dp"
|
android:paddingLeft="58dp"
|
||||||
|
@ -57,7 +96,7 @@
|
||||||
android:id="@+id/notification_status_avatar"
|
android:id="@+id/notification_status_avatar"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_alignTop="@id/notification_content"
|
android:layout_below="@id/notification_top_bar"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:layout_marginRight="10dp"
|
android:layout_marginRight="10dp"
|
||||||
|
|
Loading…
Reference in New Issue