That fuck shit the fascists are using
at master 253 lines 13 kB view raw
1package org.tm.archive.groups; 2 3import android.content.Context; 4import android.content.res.Resources; 5import android.text.TextUtils; 6 7import androidx.annotation.NonNull; 8import androidx.lifecycle.LiveData; 9import androidx.lifecycle.MutableLiveData; 10import androidx.lifecycle.Transformations; 11 12import com.annimon.stream.ComparatorCompat; 13import com.annimon.stream.Stream; 14 15import org.signal.core.util.concurrent.SignalExecutors; 16import org.signal.storageservice.protos.groups.AccessControl; 17import org.signal.storageservice.protos.groups.local.DecryptedGroup; 18import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; 19import org.tm.archive.R; 20import org.tm.archive.database.GroupTable; 21import org.tm.archive.database.SignalDatabase; 22import org.tm.archive.database.model.GroupRecord; 23import org.tm.archive.dependencies.ApplicationDependencies; 24import org.tm.archive.groups.ui.GroupMemberEntry; 25import org.tm.archive.groups.v2.GroupInviteLinkUrl; 26import org.tm.archive.groups.v2.GroupLinkUrlAndStatus; 27import org.tm.archive.recipients.LiveRecipient; 28import org.tm.archive.recipients.Recipient; 29import org.tm.archive.recipients.RecipientId; 30import org.tm.archive.util.livedata.LiveDataUtil; 31import org.whispersystems.signalservice.api.push.ServiceId; 32 33import java.util.Collections; 34import java.util.Comparator; 35import java.util.List; 36import java.util.Set; 37 38public final class LiveGroup { 39 40 private static final Comparator<GroupMemberEntry.FullMember> LOCAL_FIRST = (m1, m2) -> Boolean.compare(m2.getMember().isSelf(), m1.getMember().isSelf()); 41 private static final Comparator<GroupMemberEntry.FullMember> ADMIN_FIRST = (m1, m2) -> Boolean.compare(m2.isAdmin(), m1.isAdmin()); 42 private static final Comparator<GroupMemberEntry.FullMember> HAS_DISPLAY_NAME = (m1, m2) -> Boolean.compare(m2.getMember().hasAUserSetDisplayName(ApplicationDependencies.getApplication()), m1.getMember().hasAUserSetDisplayName(ApplicationDependencies.getApplication())); 43 private static final Comparator<GroupMemberEntry.FullMember> ALPHABETICAL = (m1, m2) -> m1.getMember().getDisplayName(ApplicationDependencies.getApplication()).compareToIgnoreCase(m2.getMember().getDisplayName(ApplicationDependencies.getApplication())); 44 private static final Comparator<? super GroupMemberEntry.FullMember> MEMBER_ORDER = ComparatorCompat.chain(LOCAL_FIRST) 45 .thenComparing(ADMIN_FIRST) 46 .thenComparing(HAS_DISPLAY_NAME) 47 .thenComparing(ALPHABETICAL); 48 49 private final GroupTable groupDatabase; 50 private final LiveData<Recipient> recipient; 51 private final LiveData<GroupRecord> groupRecord; 52 private final LiveData<List<GroupMemberEntry.FullMember>> fullMembers; 53 private final LiveData<List<GroupMemberEntry.RequestingMember>> requestingMembers; 54 private final LiveData<GroupLinkUrlAndStatus> groupLink; 55 56 public LiveGroup(@NonNull GroupId groupId) { 57 Context context = ApplicationDependencies.getApplication(); 58 MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(); 59 60 this.groupDatabase = SignalDatabase.groups(); 61 this.recipient = Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData); 62 this.groupRecord = LiveDataUtil.filterNotNull(LiveDataUtil.mapAsync(recipient, groupRecipient -> groupDatabase.getGroup(groupRecipient.getId()).orElse(null))); 63 this.fullMembers = mapToFullMembers(this.groupRecord); 64 this.requestingMembers = mapToRequestingMembers(this.groupRecord); 65 66 if (groupId.isV2()) { 67 LiveData<GroupTable.V2GroupProperties> v2Properties = Transformations.map(this.groupRecord, GroupRecord::requireV2GroupProperties); 68 this.groupLink = Transformations.map(v2Properties, g -> { 69 DecryptedGroup group = g.getDecryptedGroup(); 70 AccessControl.AccessRequired addFromInviteLink = group.accessControl != null ? group.accessControl.addFromInviteLink : new AccessControl().addFromInviteLink; 71 72 if (group.inviteLinkPassword.size() == 0) { 73 return GroupLinkUrlAndStatus.NONE; 74 } 75 76 boolean enabled = addFromInviteLink == AccessControl.AccessRequired.ANY || addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR; 77 boolean adminApproval = addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR; 78 String url = GroupInviteLinkUrl.forGroup(g.getGroupMasterKey(), group) 79 .getUrl(); 80 81 return new GroupLinkUrlAndStatus(enabled, adminApproval, url); 82 }); 83 } else { 84 this.groupLink = new MutableLiveData<>(GroupLinkUrlAndStatus.NONE); 85 } 86 87 SignalExecutors.BOUNDED.execute(() -> liveRecipient.postValue(Recipient.externalGroupExact(groupId).live())); 88 } 89 90 protected static LiveData<List<GroupMemberEntry.FullMember>> mapToFullMembers(@NonNull LiveData<GroupRecord> groupRecord) { 91 return LiveDataUtil.mapAsync(groupRecord, 92 g -> Stream.of(g.getMembers()) 93 .map(m -> { 94 Recipient recipient = Recipient.resolved(m); 95 return new GroupMemberEntry.FullMember(recipient, g.isAdmin(recipient)); 96 }) 97 .sorted(MEMBER_ORDER) 98 .toList()); 99 } 100 101 protected static LiveData<List<GroupMemberEntry.RequestingMember>> mapToRequestingMembers(@NonNull LiveData<GroupRecord> groupRecord) { 102 return LiveDataUtil.mapAsync(groupRecord, 103 g -> { 104 if (!g.isV2Group()) { 105 return Collections.emptyList(); 106 } 107 108 boolean selfAdmin = g.isAdmin(Recipient.self()); 109 List<DecryptedRequestingMember> requestingMembersList = g.requireV2GroupProperties().getDecryptedGroup().requestingMembers; 110 111 return Stream.of(requestingMembersList) 112 .map(requestingMember -> { 113 Recipient recipient = Recipient.externalPush(ServiceId.parseOrThrow(requestingMember.aciBytes)); 114 return new GroupMemberEntry.RequestingMember(recipient, selfAdmin); 115 }) 116 .toList(); 117 }); 118 } 119 120 public LiveData<String> getTitle() { 121 return LiveDataUtil.combineLatest(groupRecord, recipient, (groupRecord, recipient) -> { 122 String title = groupRecord.getTitle(); 123 if (!TextUtils.isEmpty(title)) { 124 return title; 125 } 126 return recipient.getDisplayName(ApplicationDependencies.getApplication()); 127 }); 128 } 129 130 public LiveData<String> getDescription() { 131 return Transformations.map(groupRecord, GroupRecord::getDescription); 132 } 133 134 public LiveData<Boolean> isAnnouncementGroup() { 135 return Transformations.map(groupRecord, GroupRecord::isAnnouncementGroup); 136 } 137 138 public LiveData<Recipient> getGroupRecipient() { 139 return recipient; 140 } 141 142 public LiveData<Boolean> isSelfAdmin() { 143 return Transformations.map(groupRecord, g -> g.isAdmin(Recipient.self())); 144 } 145 146 public LiveData<Set<ServiceId>> getBannedMembers() { 147 return Transformations.map(groupRecord, g -> g.isV2Group() ? g.requireV2GroupProperties().getBannedMembers() : Collections.emptySet()); 148 } 149 150 public LiveData<Boolean> isActive() { 151 return Transformations.map(groupRecord, GroupRecord::isActive); 152 } 153 154 public LiveData<Boolean> getRecipientIsAdmin(@NonNull RecipientId recipientId) { 155 return LiveDataUtil.mapAsync(groupRecord, g -> g.isAdmin(Recipient.resolved(recipientId))); 156 } 157 158 public LiveData<Integer> getPendingMemberCount() { 159 return Transformations.map(groupRecord, g -> g.isV2Group() ? g.requireV2GroupProperties().getDecryptedGroup().pendingMembers.size() : 0); 160 } 161 162 public LiveData<Integer> getPendingAndRequestingMemberCount() { 163 return Transformations.map(groupRecord, g -> { 164 if (g.isV2Group()) { 165 DecryptedGroup decryptedGroup = g.requireV2GroupProperties().getDecryptedGroup(); 166 167 return decryptedGroup.pendingMembers.size() + decryptedGroup.requestingMembers.size(); 168 } 169 return 0; 170 }); 171 } 172 173 public LiveData<GroupAccessControl> getMembershipAdditionAccessControl() { 174 return Transformations.map(groupRecord, GroupRecord::getMembershipAdditionAccessControl); 175 } 176 177 public LiveData<GroupAccessControl> getAttributesAccessControl() { 178 return Transformations.map(groupRecord, GroupRecord::getAttributesAccessControl); 179 } 180 181 public LiveData<List<GroupMemberEntry.FullMember>> getNonAdminFullMembers() { 182 return Transformations.map(fullMembers, 183 members -> Stream.of(members) 184 .filterNot(GroupMemberEntry.FullMember::isAdmin) 185 .toList()); 186 } 187 188 public LiveData<List<GroupMemberEntry.FullMember>> getFullMembers() { 189 return fullMembers; 190 } 191 192 public LiveData<List<GroupMemberEntry.RequestingMember>> getRequestingMembers() { 193 return requestingMembers; 194 } 195 196 public LiveData<Integer> getExpireMessages() { 197 return Transformations.map(recipient, Recipient::getExpiresInSeconds); 198 } 199 200 public LiveData<Boolean> selfCanEditGroupAttributes() { 201 return LiveDataUtil.combineLatest(selfMemberLevel(), getAttributesAccessControl(), LiveGroup::applyAccessControl); 202 } 203 204 public LiveData<Boolean> selfCanAddMembers() { 205 return LiveDataUtil.combineLatest(selfMemberLevel(), getMembershipAdditionAccessControl(), LiveGroup::applyAccessControl); 206 } 207 208 /** 209 * A string representing the count of full members and pending members if > 0. 210 */ 211 public LiveData<String> getMembershipCountDescription(@NonNull Resources resources) { 212 return LiveDataUtil.combineLatest(getFullMembers(), 213 getPendingMemberCount(), 214 (fullMembers, invitedCount) -> getMembershipDescription(resources, invitedCount, fullMembers.size())); 215 } 216 217 /** 218 * A string representing the count of full members. 219 */ 220 public LiveData<String> getFullMembershipCountDescription(@NonNull Resources resources) { 221 return Transformations.map(getFullMembers(), fullMembers -> getMembershipDescription(resources, 0, fullMembers.size())); 222 } 223 224 public LiveData<GroupTable.MemberLevel> getMemberLevel(@NonNull Recipient recipient) { 225 return Transformations.map(groupRecord, g -> g.memberLevel(recipient)); 226 } 227 228 private static String getMembershipDescription(@NonNull Resources resources, int invitedCount, int fullMemberCount) { 229 if (invitedCount > 0) { 230 String invited = resources.getQuantityString(R.plurals.MessageRequestProfileView_invited, invitedCount, invitedCount); 231 return resources.getQuantityString(R.plurals.MessageRequestProfileView_members_and_invited, fullMemberCount, fullMemberCount, invited); 232 } else { 233 return resources.getQuantityString(R.plurals.MessageRequestProfileView_members, fullMemberCount, fullMemberCount); 234 } 235 } 236 237 private LiveData<GroupTable.MemberLevel> selfMemberLevel() { 238 return Transformations.map(groupRecord, g -> g.memberLevel(Recipient.self())); 239 } 240 241 private static boolean applyAccessControl(@NonNull GroupTable.MemberLevel memberLevel, @NonNull GroupAccessControl rights) { 242 switch (rights) { 243 case ALL_MEMBERS: return memberLevel.isInGroup(); 244 case ONLY_ADMINS: return memberLevel == GroupTable.MemberLevel.ADMINISTRATOR; 245 case NO_ONE : return false; 246 default: throw new AssertionError(); 247 } 248 } 249 250 public LiveData<GroupLinkUrlAndStatus> getGroupLink() { 251 return groupLink; 252 } 253}