Files
RyukGram/src/Settings/SCIExcludedChatsViewController.m
T
faroukbmiled 06b2626714 feat: Per-chat and per-story blocking modes — "Block all" (exclude list) or "Block selected" (include list) with independent storage, per-entry
keep-deleted override, and adaptive UI
feat: Quick list buttons in chats and stories — add/remove directly from DM threads and story viewer
fix: KVO observer crash from multiple registrations in story audio toggle
fix: Seen auto-bypass race condition when overlapping events (boolean → counter)
fix: Confirm reel refresh not working after first pull
fix: Startup class scan replaced with direct class lookup
imp: All menu/button text adapts to active blocking mode
imp: Mark-seen triggers at the correct point per mode
imp: Migrated unexclude_inbox_button to chat_quick_list_button
imp: Menu changes in settings now reload table for dynamic titles
2026-04-10 13:41:58 +01:00

291 lines
14 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "SCIExcludedChatsViewController.h"
#import "../Features/StoriesAndMessages/SCIExcludedThreads.h"
@interface SCIExcludedChatsViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, copy) NSArray<NSDictionary *> *filtered;
@property (nonatomic, copy) NSString *query;
@property (nonatomic, assign) NSInteger sortMode;
@property (nonatomic, strong) UIBarButtonItem *sortBtn;
@property (nonatomic, strong) UIBarButtonItem *editBtn;
@property (nonatomic, strong) UIToolbar *batchToolbar;
@end
@implementation SCIExcludedChatsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Chats";
self.view.backgroundColor = [UIColor systemBackgroundColor];
self.searchBar = [[UISearchBar alloc] init];
self.searchBar.delegate = self;
self.searchBar.placeholder = @"Search by name or username";
[self.searchBar sizeToFit];
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.tableHeaderView = self.searchBar;
self.tableView.allowsMultipleSelectionDuringEditing = YES;
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tableView];
self.batchToolbar = [[UIToolbar alloc] init];
self.batchToolbar.translatesAutoresizingMaskIntoConstraints = NO;
self.batchToolbar.hidden = YES;
[self.view addSubview:self.batchToolbar];
[NSLayoutConstraint activateConstraints:@[
[self.tableView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.batchToolbar.topAnchor],
[self.batchToolbar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.batchToolbar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.batchToolbar.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
]];
self.sortBtn = [[UIBarButtonItem alloc]
initWithImage:[UIImage systemImageNamed:@"arrow.up.arrow.down"]
style:UIBarButtonItemStylePlain target:self action:@selector(toggleSort)];
self.editBtn = [[UIBarButtonItem alloc]
initWithTitle:@"Select" style:UIBarButtonItemStylePlain target:self action:@selector(toggleEdit)];
self.navigationItem.rightBarButtonItems = @[self.editBtn, self.sortBtn];
[self reload];
}
- (void)toggleEdit {
BOOL entering = !self.tableView.isEditing;
[self.tableView setEditing:entering animated:YES];
self.editBtn.title = entering ? @"Done" : @"Select";
self.editBtn.style = entering ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain;
self.batchToolbar.hidden = !entering;
if (entering) [self updateToolbar];
}
- (void)updateToolbar {
UIBarButtonItem *flex = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *del = [[UIBarButtonItem alloc] initWithTitle:@"Remove" style:UIBarButtonItemStylePlain target:self action:@selector(removeSelected)];
del.tintColor = [UIColor systemRedColor];
UIBarButtonItem *kd = [[UIBarButtonItem alloc] initWithTitle:@"Keep-deleted" style:UIBarButtonItemStylePlain target:self action:@selector(batchKeepDeleted)];
self.batchToolbar.items = @[del, flex, kd];
}
- (void)removeSelected {
NSArray<NSIndexPath *> *sel = self.tableView.indexPathsForSelectedRows;
if (!sel.count) return;
for (NSIndexPath *ip in sel) {
NSDictionary *e = self.filtered[ip.row];
[SCIExcludedThreads removeThreadId:e[@"threadId"]];
}
[self toggleEdit];
[self reload];
}
- (void)batchKeepDeleted {
NSArray<NSIndexPath *> *sel = self.tableView.indexPathsForSelectedRows;
if (!sel.count) return;
UIAlertController *sheet = [UIAlertController alertControllerWithTitle:@"Set keep-deleted override" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
void (^apply)(SCIKeepDeletedOverride) = ^(SCIKeepDeletedOverride mode) {
for (NSIndexPath *ip in sel) {
NSDictionary *e = self.filtered[ip.row];
[SCIExcludedThreads setKeepDeletedOverride:mode forThreadId:e[@"threadId"]];
}
[self toggleEdit];
[self reload];
};
[sheet addAction:[UIAlertAction actionWithTitle:@"Follow default" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) {
apply(SCIKeepDeletedOverrideDefault);
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Force ON (preserve unsends)" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) {
apply(SCIKeepDeletedOverrideIncluded);
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Force OFF (allow unsends)" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) {
apply(SCIKeepDeletedOverrideExcluded);
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
sheet.popoverPresentationController.barButtonItem = self.batchToolbar.items.lastObject;
[self presentViewController:sheet animated:YES completion:nil];
}
- (void)toggleSort {
UIAlertController *sheet = [UIAlertController alertControllerWithTitle:@"Sort by"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
NSArray *titles = @[@"Recently added", @"Name (AZ)"];
for (NSInteger i = 0; i < (NSInteger)titles.count; i++) {
UIAlertAction *a = [UIAlertAction actionWithTitle:titles[i]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_) {
self.sortMode = i;
[self reload];
}];
if (i == self.sortMode) [a setValue:@YES forKey:@"checked"];
[sheet addAction:a];
}
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
sheet.popoverPresentationController.barButtonItem = self.sortBtn;
[self presentViewController:sheet animated:YES completion:nil];
}
- (void)reload {
NSArray *all = [SCIExcludedThreads allEntries];
NSString *q = [self.query lowercaseString];
if (q.length > 0) {
all = [all filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *e, id _) {
if ([[e[@"threadName"] lowercaseString] containsString:q]) return YES;
for (NSDictionary *u in (NSArray *)e[@"users"]) {
if ([[u[@"username"] lowercaseString] containsString:q]) return YES;
if ([[u[@"fullName"] lowercaseString] containsString:q]) return YES;
}
return NO;
}]];
}
if (self.sortMode == 0) {
all = [all sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
return [b[@"addedAt"] ?: @0 compare:a[@"addedAt"] ?: @0];
}];
} else {
all = [all sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
return [a[@"threadName"] ?: @"" caseInsensitiveCompare:b[@"threadName"] ?: @""];
}];
}
self.filtered = all;
BOOL bs = [SCIExcludedThreads isBlockSelectedMode];
NSString *label = bs ? @"Included chats" : @"Excluded chats";
self.title = [NSString stringWithFormat:@"%@ (%lu)", label, (unsigned long)self.filtered.count];
[self.tableView reloadData];
}
#pragma mark - Search
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
self.query = searchText;
[self reload];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { [searchBar resignFirstResponder]; }
#pragma mark - Table
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section {
return self.filtered.count;
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *reuse = @"sciExclCell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:reuse];
if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuse];
NSDictionary *e = self.filtered[indexPath.row];
NSString *name = e[@"threadName"] ?: @"(unknown)";
BOOL isGroup = [e[@"isGroup"] boolValue];
NSMutableArray *unames = [NSMutableArray array];
for (NSDictionary *u in (NSArray *)e[@"users"]) {
if (u[@"username"]) [unames addObject:[@"@" stringByAppendingString:u[@"username"]]];
}
NSString *subtitle = [unames componentsJoinedByString:@", "];
SCIKeepDeletedOverride mode = [e[@"keepDeletedOverride"] integerValue];
NSString *kdLabel = (mode == SCIKeepDeletedOverrideExcluded) ? @" • Keep-deleted: OFF"
: (mode == SCIKeepDeletedOverrideIncluded) ? @" • Keep-deleted: ON"
: @"";
if (kdLabel.length) subtitle = [subtitle stringByAppendingString:kdLabel];
cell.textLabel.text = [NSString stringWithFormat:@"%@%@", isGroup ? @"👥 " : @"", name];
cell.detailTextLabel.text = subtitle;
cell.detailTextLabel.numberOfLines = 2;
cell.accessoryType = (isGroup || tv.isEditing) ? UITableViewCellAccessoryNone : UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (tv.isEditing) return;
[tv deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *e = self.filtered[indexPath.row];
NSArray *users = e[@"users"];
if ([e[@"isGroup"] boolValue] || users.count != 1) return;
NSString *username = users.firstObject[@"username"];
if (!username) return;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"instagram://user?username=%@", username]];
if ([[UIApplication sharedApplication] canOpenURL:url])
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
}
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tv trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *e = self.filtered[indexPath.row];
NSString *tid = e[@"threadId"];
UIContextualAction *del = [UIContextualAction
contextualActionWithStyle:UIContextualActionStyleDestructive
title:@"Remove"
handler:^(UIContextualAction *_, UIView *__, void (^cb)(BOOL)) {
[SCIExcludedThreads removeThreadId:tid];
[self reload];
cb(YES);
}];
return [UISwipeActionsConfiguration configurationWithActions:@[del]];
}
- (UIContextMenuConfiguration *)tableView:(UITableView *)tv contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point {
NSDictionary *e = self.filtered[indexPath.row];
NSString *tid = e[@"threadId"];
SCIKeepDeletedOverride mode = [e[@"keepDeletedOverride"] integerValue];
return [UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *_) {
UIAction *(^kdAction)(NSString *, SCIKeepDeletedOverride) = ^UIAction *(NSString *title, SCIKeepDeletedOverride v) {
UIAction *a = [UIAction actionWithTitle:title image:nil identifier:nil
handler:^(__kindof UIAction *_) {
[SCIExcludedThreads setKeepDeletedOverride:v forThreadId:tid];
[self reload];
}];
if (v == mode) a.state = UIMenuElementStateOn;
return a;
};
UIMenu *kdMenu = [UIMenu menuWithTitle:@"Keep-deleted override"
image:[UIImage systemImageNamed:@"trash.slash"]
identifier:nil
options:0
children:@[
kdAction(@"Follow default", SCIKeepDeletedOverrideDefault),
kdAction(@"Force ON (preserve unsends)", SCIKeepDeletedOverrideIncluded),
kdAction(@"Force OFF (allow unsends)", SCIKeepDeletedOverrideExcluded),
]];
UIAction *remove = [UIAction actionWithTitle:@"Remove from list"
image:[UIImage systemImageNamed:@"trash"]
identifier:nil
handler:^(__kindof UIAction *_) {
[SCIExcludedThreads removeThreadId:tid];
[self reload];
}];
remove.attributes = UIMenuElementAttributesDestructive;
return [UIMenu menuWithChildren:@[kdMenu, remove]];
}];
}
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tv leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *e = self.filtered[indexPath.row];
NSString *tid = e[@"threadId"];
SCIKeepDeletedOverride mode = [e[@"keepDeletedOverride"] integerValue];
SCIKeepDeletedOverride next = (mode + 1) % 3;
NSString *title = (next == SCIKeepDeletedOverrideExcluded) ? @"KD: OFF"
: (next == SCIKeepDeletedOverrideIncluded) ? @"KD: ON"
: @"KD: default";
UIContextualAction *toggle = [UIContextualAction
contextualActionWithStyle:UIContextualActionStyleNormal
title:title
handler:^(UIContextualAction *_, UIView *__, void (^cb)(BOOL)) {
[SCIExcludedThreads setKeepDeletedOverride:next forThreadId:tid];
[self reload];
cb(YES);
}];
toggle.backgroundColor = [UIColor systemBlueColor];
return [UISwipeActionsConfiguration configurationWithActions:@[toggle]];
}
@end