mirror of
https://github.com/faroukbmiled/RyukGram.git
synced 2026-06-08 16:33:54 +02:00
feat: Confirm story like
feat: Confirm story emoji reaction feat: Spanish translation feat: Language switcher + import/export localization from Debug feat: Swipe down to dismiss media viewer feat: Manually add users to story/chat exclusion lists by username feat: Keep stories visually seen locally — split mode (grey ring locally, seen receipt still blockedon server) feat: Auto-scroll reels — IG default or RyukGram mode, keeps advancing after swiping back (#3) fix: Messages-only mode — tab swiping disabled fix: Settings quick-access broken in non-English languages fix: Story seen-receipt block restored on IG v425+ (Sundial uploader), per-owner, both "Block all" and "Block selected" modes fix: Block selected mode no longer marks listed stories as seen imp: Story-interaction pipeline unifies confirm + seen/advance side effects
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#import "../Utils.h"
|
||||
#import "../Downloader/Download.h"
|
||||
#import "../PhotoAlbum.h"
|
||||
#import "../Features/StoriesAndMessages/SCIExcludedStoryUsers.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
#import <Photos/Photos.h>
|
||||
@@ -23,9 +24,9 @@ extern BOOL sciIsStoryAudioEnabled(void);
|
||||
// Match keys used in the settings-entry title map for openSettingsForContext:
|
||||
static NSString *sciSettingsTitleForContext(SCIActionContext ctx) {
|
||||
switch (ctx) {
|
||||
case SCIActionContextFeed: return @"Feed";
|
||||
case SCIActionContextReels: return @"Reels";
|
||||
case SCIActionContextStories: return @"Stories";
|
||||
case SCIActionContextFeed: return SCILocalized(@"Feed");
|
||||
case SCIActionContextReels: return SCILocalized(@"Reels");
|
||||
case SCIActionContextStories: return SCILocalized(@"Stories");
|
||||
}
|
||||
return @"General";
|
||||
}
|
||||
@@ -888,6 +889,36 @@ static UIView *sciFindSubviewOfClass(UIView *root, NSString *className, int maxD
|
||||
}
|
||||
}
|
||||
|
||||
// Story user list management (add/remove from exclusion list).
|
||||
if (ctx == SCIActionContextStories && [SCIUtils getBoolPref:@"enable_story_user_exclusions"]) {
|
||||
extern NSDictionary *sciOwnerInfoForView(UIView *);
|
||||
extern void sciRefreshAllVisibleOverlays(UIViewController *);
|
||||
extern __weak UIViewController *sciActiveStoryViewerVC;
|
||||
NSDictionary *ownerInfo = sourceView ? sciOwnerInfoForView(sourceView) : nil;
|
||||
NSString *ownerPK = ownerInfo[@"pk"];
|
||||
if (ownerPK.length) {
|
||||
BOOL inList = [SCIExcludedStoryUsers isInList:ownerPK];
|
||||
BOOL bs = [SCIExcludedStoryUsers isBlockSelectedMode];
|
||||
NSString *addLabel = bs ? SCILocalized(@"Add to block list") : SCILocalized(@"Exclude from seen");
|
||||
NSString *removeLabel = bs ? SCILocalized(@"Remove from block list") : SCILocalized(@"Remove from exclude list");
|
||||
NSString *title = inList ? removeLabel : addLabel;
|
||||
NSString *icon = inList ? @"eye.fill" : @"eye.slash";
|
||||
NSString *capturedPK = [ownerPK copy];
|
||||
NSString *capturedUser = [ownerInfo[@"username"] ?: @"" copy];
|
||||
NSString *capturedName = [ownerInfo[@"fullName"] ?: @"" copy];
|
||||
[out addObject:[SCIAction actionWithTitle:title icon:icon handler:^{
|
||||
if (inList) {
|
||||
[SCIExcludedStoryUsers removePK:capturedPK];
|
||||
[SCIUtils showToastForDuration:2.0 title:bs ? SCILocalized(@"Unblocked") : SCILocalized(@"Removed from list")];
|
||||
} else {
|
||||
[SCIExcludedStoryUsers addOrUpdateEntry:@{@"pk": capturedPK, @"username": capturedUser, @"fullName": capturedName}];
|
||||
[SCIUtils showToastForDuration:2.0 title:bs ? SCILocalized(@"Added to block list") : SCILocalized(@"Added to exclude list")];
|
||||
}
|
||||
sciRefreshAllVisibleOverlays(sciActiveStoryViewerVC);
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx != SCIActionContextStories) {
|
||||
// Caption lives on the parent media (not on carousel children).
|
||||
[out addObject:[SCIAction actionWithTitle:SCILocalized(@"Copy caption")
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
#pragma mark - Container VC (PageViewController-based)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@interface _SCIMediaViewerContainerVC : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
|
||||
@interface _SCIMediaViewerContainerVC : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
|
||||
@property (nonatomic, strong) NSArray<SCIMediaViewerItem *> *items;
|
||||
@property (nonatomic, assign) NSUInteger currentIndex;
|
||||
@property (nonatomic, strong) UIPageViewController *pageVC;
|
||||
@@ -238,18 +238,16 @@
|
||||
[self.captionLabel.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-8],
|
||||
]];
|
||||
|
||||
// Swipe down to dismiss
|
||||
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleDismissPan:)];
|
||||
pan.delegate = (id<UIGestureRecognizerDelegate>)self;
|
||||
[self.view addGestureRecognizer:pan];
|
||||
|
||||
// Single tap toggles chrome
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleChrome)];
|
||||
tap.cancelsTouchesInView = NO;
|
||||
[self.pageVC.view addGestureRecognizer:tap];
|
||||
|
||||
// For photos, let double-tap zoom work without triggering single-tap
|
||||
for (UIGestureRecognizer *gr in self.pageVC.view.gestureRecognizers) {
|
||||
if ([gr isKindOfClass:[UITapGestureRecognizer class]] && ((UITapGestureRecognizer *)gr).numberOfTapsRequired == 1) {
|
||||
// Already have our tap
|
||||
}
|
||||
}
|
||||
|
||||
[self updateChrome];
|
||||
}
|
||||
|
||||
@@ -290,6 +288,45 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gr {
|
||||
if (![gr isKindOfClass:[UIPanGestureRecognizer class]]) return YES;
|
||||
CGPoint v = [gr velocityInView:self.view];
|
||||
return fabs(v.y) > fabs(v.x) && v.y > 0;
|
||||
}
|
||||
|
||||
- (void)handleDismissPan:(UIPanGestureRecognizer *)gr {
|
||||
CGFloat ty = [gr translationInView:self.view].y;
|
||||
CGFloat h = self.view.bounds.size.height;
|
||||
CGFloat progress = fmin(fmax(ty / h, 0), 1);
|
||||
|
||||
switch (gr.state) {
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
self.view.transform = CGAffineTransformMakeTranslation(0, ty);
|
||||
self.view.alpha = 1.0 - progress * 0.5;
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded:
|
||||
case UIGestureRecognizerStateCancelled: {
|
||||
CGFloat vy = [gr velocityInView:self.view].y;
|
||||
if (progress > 0.25 || vy > 800) {
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.view.transform = CGAffineTransformMakeTranslation(0, h);
|
||||
self.view.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
}];
|
||||
} else {
|
||||
[UIView animateWithDuration:0.25 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0 options:0 animations:^{
|
||||
self.view.transform = CGAffineTransformIdentity;
|
||||
self.view.alpha = 1;
|
||||
} completion:nil];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)closeTapped {
|
||||
// Pause any playing video
|
||||
UIViewController *current = self.pageVC.viewControllers.firstObject;
|
||||
@@ -424,7 +461,7 @@
|
||||
_SCIMediaViewerContainerVC *vc = [[_SCIMediaViewerContainerVC alloc] init];
|
||||
vc.items = items;
|
||||
vc.currentIndex = index;
|
||||
vc.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
||||
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
[topMostController() presentViewController:vc animated:YES completion:nil];
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user