That fuck shit the fascists are using
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}