Add severed_relationships notifications (AND-174)
This commit is contained in:
@@ -11,6 +11,7 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.model.viewmodel.NotificationViewModel;
|
import org.joinmastodon.android.model.viewmodel.NotificationViewModel;
|
||||||
import org.joinmastodon.android.ui.displayitems.InlineStatusStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.InlineStatusStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.NotificationWithButtonStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
@@ -35,7 +36,10 @@ public abstract class BaseNotificationsListFragment extends BaseStatusListFragme
|
|||||||
else
|
else
|
||||||
titleItem=null;
|
titleItem=null;
|
||||||
}else{
|
}else{
|
||||||
titleItem=new NotificationHeaderStatusDisplayItem(n.getID(), this, n, accountID);
|
if(n.notification.type==NotificationType.SEVERED_RELATIONSHIPS)
|
||||||
|
titleItem=new NotificationWithButtonStatusDisplayItem(n.getID(), this, n, accountID);
|
||||||
|
else
|
||||||
|
titleItem=new NotificationHeaderStatusDisplayItem(n.getID(), this, n, accountID);
|
||||||
}
|
}
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
if(titleItem!=null && n.notification.type!=NotificationType.STATUS){
|
if(titleItem!=null && n.notification.type!=NotificationType.STATUS){
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
@@ -14,10 +16,9 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
|||||||
public NotificationType type;
|
public NotificationType type;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public Instant createdAt;
|
public Instant createdAt;
|
||||||
@RequiredField
|
|
||||||
public Account account;
|
public Account account;
|
||||||
|
|
||||||
public Status status;
|
public Status status;
|
||||||
|
public RelationshipSeveranceEvent event;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
@@ -25,6 +26,17 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
|||||||
account.postprocess();
|
account.postprocess();
|
||||||
if(status!=null)
|
if(status!=null)
|
||||||
status.postprocess();
|
status.postprocess();
|
||||||
|
if(event!=null){
|
||||||
|
try{
|
||||||
|
event.postprocess();
|
||||||
|
}catch(ObjectValidationException x){
|
||||||
|
Log.w("Notification", x);
|
||||||
|
event=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(type!=NotificationType.SEVERED_RELATIONSHIPS && account==null){
|
||||||
|
throw new ObjectValidationException("account must be present for type "+type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -18,7 +21,23 @@ public class NotificationGroup extends BaseModel{
|
|||||||
@RequiredField
|
@RequiredField
|
||||||
public List<String> sampleAccountIds;
|
public List<String> sampleAccountIds;
|
||||||
public String statusId;
|
public String statusId;
|
||||||
// TODO report
|
public RelationshipSeveranceEvent event;
|
||||||
// TODO event
|
|
||||||
// TODO moderation_warning
|
// TODO moderation_warning
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postprocess() throws ObjectValidationException{
|
||||||
|
super.postprocess();
|
||||||
|
if(event!=null){
|
||||||
|
try{
|
||||||
|
event.postprocess();
|
||||||
|
}catch(ObjectValidationException x){
|
||||||
|
Log.w("Notification", x);
|
||||||
|
event=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(type!=NotificationType.SEVERED_RELATIONSHIPS && sampleAccountIds.isEmpty()){
|
||||||
|
throw new ObjectValidationException("sample_account_ids must be present for type "+type);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ public enum NotificationType{
|
|||||||
@SerializedName("status")
|
@SerializedName("status")
|
||||||
STATUS,
|
STATUS,
|
||||||
@SerializedName("update")
|
@SerializedName("update")
|
||||||
UPDATE;
|
UPDATE,
|
||||||
|
@SerializedName("severed_relationships")
|
||||||
|
SEVERED_RELATIONSHIPS;
|
||||||
|
|
||||||
public boolean canBeGrouped(){
|
public boolean canBeGrouped(){
|
||||||
return this==REBLOG || this==FAVORITE;
|
return this==REBLOG || this==FAVORITE;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Parcel
|
||||||
|
public class RelationshipSeveranceEvent extends BaseModel{
|
||||||
|
public String id;
|
||||||
|
@RequiredField
|
||||||
|
public Type type;
|
||||||
|
public boolean purged;
|
||||||
|
@RequiredField
|
||||||
|
public String targetName;
|
||||||
|
public int followersCount;
|
||||||
|
public int followingCount;
|
||||||
|
public Instant createdAt;
|
||||||
|
|
||||||
|
public enum Type{
|
||||||
|
@SerializedName("domain_block")
|
||||||
|
DOMAIN_BLOCK,
|
||||||
|
@SerializedName("user_domain_block")
|
||||||
|
USER_DOMAIN_BLOCK,
|
||||||
|
@SerializedName("account_suspension")
|
||||||
|
ACCOUNT_SUSPENSION
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.NotificationType;
|
||||||
|
import org.joinmastodon.android.model.RelationshipSeveranceEvent;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.NotificationViewModel;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class NotificationWithButtonStatusDisplayItem extends StatusDisplayItem{
|
||||||
|
private final NotificationViewModel notification;
|
||||||
|
private CharSequence text;
|
||||||
|
private String buttonText;
|
||||||
|
private Runnable buttonAction;
|
||||||
|
|
||||||
|
public NotificationWithButtonStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, NotificationViewModel notification, String accountID){
|
||||||
|
super(parentID, parentFragment);
|
||||||
|
this.notification=notification;
|
||||||
|
if(notification.notification.type==NotificationType.SEVERED_RELATIONSHIPS){
|
||||||
|
RelationshipSeveranceEvent event=notification.notification.event;
|
||||||
|
String localDomain=AccountSessionManager.get(accountID).domain;
|
||||||
|
if(event!=null){
|
||||||
|
text=switch(event.type){
|
||||||
|
case ACCOUNT_SUSPENSION -> replacePlaceholdersWithBoldStrings(parentFragment.getString(R.string.relationship_severance_account_suspension,
|
||||||
|
"{{localDomain}}", "{{target}}"), Map.of("localDomain", localDomain, "target", event.targetName));
|
||||||
|
case DOMAIN_BLOCK -> replacePlaceholdersWithBoldStrings(parentFragment.getString(R.string.relationship_severance_domain_block,
|
||||||
|
"{{localDomain}}", "{{target}}", event.followersCount, parentFragment.getResources().getQuantityString(R.plurals.x_accounts, event.followingCount, event.followingCount)),
|
||||||
|
Map.of("localDomain", localDomain, "target", event.targetName));
|
||||||
|
case USER_DOMAIN_BLOCK -> replacePlaceholdersWithBoldStrings(parentFragment.getString(R.string.relationship_severance_user_domain_block,
|
||||||
|
"{{target}}", event.followersCount, parentFragment.getResources().getQuantityString(R.plurals.x_accounts, event.followingCount, event.followingCount)),
|
||||||
|
Map.of("target", event.targetName));
|
||||||
|
};
|
||||||
|
}else{
|
||||||
|
text="???";
|
||||||
|
}
|
||||||
|
buttonText=parentFragment.getString(R.string.relationship_severance_learn_more);
|
||||||
|
buttonAction=()->UiUtils.launchWebBrowser(parentFragment.getActivity(), "https://"+localDomain+"/severed_relationships");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpannableStringBuilder replacePlaceholdersWithBoldStrings(String in, Map<String, String> replacements){
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder(in);
|
||||||
|
for(Map.Entry<String, String> e:replacements.entrySet()){
|
||||||
|
String placeholder="{{"+e.getKey()+"}}";
|
||||||
|
int index=ssb.toString().indexOf(placeholder);
|
||||||
|
if(index==-1)
|
||||||
|
continue;
|
||||||
|
ssb.replace(index, index+placeholder.length(), e.getValue());
|
||||||
|
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), index, index+e.getValue().length(), 0);
|
||||||
|
}
|
||||||
|
return ssb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType(){
|
||||||
|
return Type.NOTIFICATION_WITH_BUTTON;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Holder extends StatusDisplayItem.Holder<NotificationWithButtonStatusDisplayItem>{
|
||||||
|
private final ImageView icon;
|
||||||
|
private final TextView text;
|
||||||
|
private final Button button;
|
||||||
|
|
||||||
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
|
super(activity, R.layout.display_item_notification_with_button, parent);
|
||||||
|
icon=findViewById(R.id.icon);
|
||||||
|
text=findViewById(R.id.text);
|
||||||
|
button=findViewById(R.id.button);
|
||||||
|
button.setOnClickListener(v->item.buttonAction.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(NotificationWithButtonStatusDisplayItem item){
|
||||||
|
icon.setImageResource(switch(item.notification.notification.type){
|
||||||
|
case SEVERED_RELATIONSHIPS -> R.drawable.ic_heart_broken_fill1_24px;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + item.notification.notification.type);
|
||||||
|
});
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
|
||||||
|
text.setText(item.text);
|
||||||
|
button.setText(item.buttonText);
|
||||||
|
button.setEnabled(item.buttonAction!=null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,7 @@ public abstract class StatusDisplayItem{
|
|||||||
case SECTION_HEADER -> new SectionHeaderStatusDisplayItem.Holder(activity, parent);
|
case SECTION_HEADER -> new SectionHeaderStatusDisplayItem.Holder(activity, parent);
|
||||||
case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent);
|
case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent);
|
||||||
case INLINE_STATUS -> new InlineStatusStatusDisplayItem.Holder(activity, parent);
|
case INLINE_STATUS -> new InlineStatusStatusDisplayItem.Holder(activity, parent);
|
||||||
|
case NOTIFICATION_WITH_BUTTON -> new NotificationWithButtonStatusDisplayItem.Holder(activity, parent);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +227,8 @@ public abstract class StatusDisplayItem{
|
|||||||
HEADER_CHECKABLE,
|
HEADER_CHECKABLE,
|
||||||
NOTIFICATION_HEADER,
|
NOTIFICATION_HEADER,
|
||||||
FILTER_SPOILER,
|
FILTER_SPOILER,
|
||||||
INLINE_STATUS
|
INLINE_STATUS,
|
||||||
|
NOTIFICATION_WITH_BUTTON
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class Holder<T> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
public static abstract class Holder<T> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M481,877Q347,742 267.5,659Q188,576 146.5,521Q105,466 92.5,427Q80,388 80,340Q80,248 144,184Q208,120 300,120Q345,120 387,136.5Q429,153 462,184L400,400L520,400L486,735L600,360L480,360L551,148Q576,134 603.5,127Q631,120 660,120Q752,120 816,184Q880,248 880,340Q880,388 867,428Q854,468 812,523.5Q770,579 691,661.5Q612,744 481,877Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingTop="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="center"
|
||||||
|
tools:tint="#0f0"
|
||||||
|
tools:src="@drawable/ic_repeat_24px"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@id/icon"
|
||||||
|
android:layout_alignWithParentIfMissing="true"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
android:minHeight="20dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
tools:text="Notification text"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_below="@id/text"
|
||||||
|
android:layout_toEndOf="@id/icon"
|
||||||
|
android:layout_marginStart="-8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@drawable/bg_button_m3_text"
|
||||||
|
android:textColor="?colorM3Primary"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
tools:text="Button text"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
@@ -798,4 +798,14 @@
|
|||||||
<string name="own_poll_ended">Your poll has ended</string>
|
<string name="own_poll_ended">Your poll has ended</string>
|
||||||
<string name="user_just_posted">%s just posted</string>
|
<string name="user_just_posted">%s just posted</string>
|
||||||
<string name="user_edited_post">%s edited a post you interacted with</string>
|
<string name="user_edited_post">%s edited a post you interacted with</string>
|
||||||
|
<string name="relationship_severance_account_suspension">An admin from %1$s has suspended %2$s, which means you can no longer receive updates from them or interact with them.</string>
|
||||||
|
<!-- %1$s is your server domain, %2$s is the domain that was blocked, %3$,d is the follower count, %4$s is the `x_accounts` plural string -->
|
||||||
|
<string name="relationship_severance_domain_block">An admin from %1$s has blocked %2$s, including %3$,d of your followers and %4$s you follow.</string>
|
||||||
|
<plurals name="x_accounts">
|
||||||
|
<item quantity="one">%,d account</item>
|
||||||
|
<item quantity="other">%,d accounts</item>
|
||||||
|
</plurals>
|
||||||
|
<!-- %1$s is the domain that was blocked, %2$,d is the follower count, %3$s is the `x_accounts` plural string -->
|
||||||
|
<string name="relationship_severance_user_domain_block">You have blocked %1$s, removing %2$,d of your followers and %3$s you follow.</string>
|
||||||
|
<string name="relationship_severance_learn_more">Learn more</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user