mirror of
https://github.com/faroukbmiled/RyukGram.git
synced 2026-06-08 00:13:54 +02:00
86eaa95019
### Features - **Open Instagram links in app (Safari extension)** — bundled Safari web extension (sideload IPA only). Enable in Safari → Extensions; instagram.com links open in the app. - **Localization** — every user-facing string flows through a central translation layer. Globe button in Settings; missing keys fall back to English. Ships English only — see the "Translating RyukGram" section in the README to add more. - **Action buttons** — context-aware menus on feed, reels, and stories (expand, repost, download, copy caption, etc.) with per-context default tap action and carousel/multi-story bulk download - **Enhanced HD downloads** — up to 1080p via DASH + FFmpegKit with quality picker, preview playback, encoding-speed options, and 720p fallback - **Repost**, **media viewer**, **media zoom** (long-press), **download pill** (frosted glass, stacks concurrent downloads) - **Fake location** — overrides CoreLocation app-wide, map picker + saved presets, optional quick-toggle button on the Friends Map - **Messages-only mode** — strips every tab except DM inbox + profile - **Launch tab** — pick which tab the app opens to - Full last active date in DMs — show full date instead of "Active 2h ago" - Custom date format — 12 formats with per-surface toggles (feed, notes/comments/stories, DMs) - Send files in DMs (experimental) - View story mentions - Hide suggested stories - Story tray long-press actions — view HD profile picture from the tray menu - Advance on story reply — auto-skip to next story after sending a reply or reaction - Mark story as seen on reply or emoji reaction - Hide metrics (likes, comments, shares counts) - Hide messages tab - Hide voice/video call buttons in DM thread header (independent toggles) - Disable app haptics - Disable reels tab refresh - Disable disappearing messages mode in DMs - Follow indicator — shows whether the profile user follows you - Copy note text on long press - Zoom profile photo — long press opens full-screen viewer - Notes actions — copy text, download GIF/audio from notes long-press menu - Confirm unfollow - Feed refresh controls — disable background refresh, home button refresh, and home button scroll ### Improvements - Default tap action: added copy URL, repost, and view mentions options; dynamic menu generation per context - Settings pages reordered: General → Feed → Stories → Reels → Messages → Profile → Navigation → Saving → Confirmations - Fake location picker: native Apple Maps-style UI (search, long-press to drop pin, current location) - Liquid glass floating tab bar + dynamic sizing - Upload audio: FFmpegKit re-encode + trim for any audio/video input - Settings reorganized with per-context action button config; new Profile page - Highlight cover: full-screen viewer replaces direct download - Switched HD encoder to `h264_videotoolbox` (hardware) — no GPL FFmpegKit required - Legacy long-press download deprecated (off by default), replaced by action buttons ### Fixes - Hide suggested stories no longer removes followed users' stories on scroll - Settings search bar transparency with liquid glass off; auto-deactivates on push - HD download cancel: tapping pill aborts in-flight downloads + FFmpeg sessions cleanly - Download pill stuck state on background/foreground, progress reset per download - Disappearing messages mode confirmation not firing on swipe - Detailed color picker not working on story draw `†` - DM seen toggle menu not updating after tap - Reel refresh confirmation appearing on first app launch `†` - Reels action button displacing profile pictures on photo reels - Disappearing DM media download (expand, share, save to Photos with progress pill) - Carousel "Download all" not showing item count in feed - Encoding speed setting being ignored for HD downloads - Various upstream SCInsta merges (Meta AI hiding, suggested chats hiding, notes tray) — marked `†` > `†` Merged from upstream [SCInsta](https://github.com/SoCuul/SCInsta) by SoCuul ### Credits - Thanks to [@erupts0](https://github.com/erupts0) (John) for testing and feature suggestions - Thanks to [@euoradan](https://t.me/euoradan) (Radan) for experimental Instagram feature flag research - Safari extension forked/cleaned from [BillyCurtis/OpenInstagramSafariExtension](https://github.com/BillyCurtis/OpenInstagramSafariExtension) ### Known Issues - Preserved unsent messages can't be removed via "Delete for you"; pull-to-refresh clears them (warning available in settings) - "Delete for you" detection uses a ~2s window after the local action — a real unsend landing in that window may be missed (rare)
438 lines
19 KiB
Objective-C
438 lines
19 KiB
Objective-C
#import "SCIMediaViewer.h"
|
|
#import "../Utils.h"
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <AVKit/AVKit.h>
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
#pragma mark - Data model
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@implementation SCIMediaViewerItem
|
|
+ (instancetype)itemWithVideoURL:(NSURL *)videoURL photoURL:(NSURL *)photoURL caption:(NSString *)caption {
|
|
SCIMediaViewerItem *i = [SCIMediaViewerItem new];
|
|
i.videoURL = videoURL;
|
|
i.photoURL = photoURL;
|
|
i.caption = caption;
|
|
return i;
|
|
}
|
|
@end
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
#pragma mark - Single photo page
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@interface _SCIPhotoPageVC : UIViewController <UIScrollViewDelegate>
|
|
@property (nonatomic, strong) NSURL *photoURL;
|
|
@property (nonatomic, strong) UIScrollView *scrollView;
|
|
@property (nonatomic, strong) UIImageView *imageView;
|
|
@property (nonatomic, strong) UIActivityIndicatorView *spinner;
|
|
@end
|
|
|
|
@implementation _SCIPhotoPageVC
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
self.view.backgroundColor = [UIColor blackColor];
|
|
|
|
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
|
|
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
self.scrollView.delegate = self;
|
|
self.scrollView.minimumZoomScale = 1.0;
|
|
self.scrollView.maximumZoomScale = 5.0;
|
|
self.scrollView.showsVerticalScrollIndicator = NO;
|
|
self.scrollView.showsHorizontalScrollIndicator = NO;
|
|
[self.view addSubview:self.scrollView];
|
|
|
|
self.imageView = [[UIImageView alloc] initWithFrame:self.scrollView.bounds];
|
|
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
|
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.scrollView addSubview:self.imageView];
|
|
|
|
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
|
|
self.spinner.color = [UIColor whiteColor];
|
|
self.spinner.center = self.view.center;
|
|
self.spinner.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
|
|
| UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
|
|
[self.view addSubview:self.spinner];
|
|
[self.spinner startAnimating];
|
|
|
|
NSURL *url = [self.photoURL copy];
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
NSData *data = [NSData dataWithContentsOfURL:url];
|
|
UIImage *img = data ? [UIImage imageWithData:data] : nil;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self.spinner stopAnimating];
|
|
if (img) self.imageView.image = img;
|
|
});
|
|
});
|
|
|
|
// Double-tap to zoom
|
|
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
|
|
doubleTap.numberOfTapsRequired = 2;
|
|
[self.scrollView addGestureRecognizer:doubleTap];
|
|
}
|
|
|
|
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)sv { return self.imageView; }
|
|
|
|
- (void)handleDoubleTap:(UITapGestureRecognizer *)gr {
|
|
if (self.scrollView.zoomScale > 1.0) {
|
|
[self.scrollView setZoomScale:1.0 animated:YES];
|
|
} else {
|
|
CGPoint pt = [gr locationInView:self.imageView];
|
|
CGRect rect = CGRectMake(pt.x - 50, pt.y - 50, 100, 100);
|
|
[self.scrollView zoomToRect:rect animated:YES];
|
|
}
|
|
}
|
|
|
|
- (UIImage *)currentImage { return self.imageView.image; }
|
|
|
|
@end
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
#pragma mark - Single video page
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@interface _SCIVideoPageVC : UIViewController
|
|
@property (nonatomic, strong) NSURL *videoURL;
|
|
@property (nonatomic, strong) AVPlayerViewController *playerVC;
|
|
@end
|
|
|
|
@implementation _SCIVideoPageVC
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
self.view.backgroundColor = [UIColor blackColor];
|
|
|
|
AVPlayer *player = [AVPlayer playerWithURL:self.videoURL];
|
|
self.playerVC = [[AVPlayerViewController alloc] init];
|
|
self.playerVC.player = player;
|
|
self.playerVC.showsPlaybackControls = YES;
|
|
|
|
[self addChildViewController:self.playerVC];
|
|
self.playerVC.view.frame = self.view.bounds;
|
|
self.playerVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.view addSubview:self.playerVC.view];
|
|
[self.playerVC didMoveToParentViewController:self];
|
|
|
|
[player play];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
[self.playerVC.player pause];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
#pragma mark - Container VC (PageViewController-based)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@interface _SCIMediaViewerContainerVC : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
|
|
@property (nonatomic, strong) NSArray<SCIMediaViewerItem *> *items;
|
|
@property (nonatomic, assign) NSUInteger currentIndex;
|
|
@property (nonatomic, strong) UIPageViewController *pageVC;
|
|
@property (nonatomic, strong) UIView *topBar;
|
|
@property (nonatomic, strong) UIButton *closeBtn;
|
|
@property (nonatomic, strong) UILabel *counterLabel;
|
|
@property (nonatomic, strong) UIButton *shareBtn;
|
|
@property (nonatomic, strong) UIView *bottomBar;
|
|
@property (nonatomic, strong) UILabel *captionLabel;
|
|
@property (nonatomic, assign) BOOL chromeVisible;
|
|
@property (nonatomic, assign) BOOL captionExpanded;
|
|
@end
|
|
|
|
@implementation _SCIMediaViewerContainerVC
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
self.view.backgroundColor = [UIColor blackColor];
|
|
self.chromeVisible = YES;
|
|
|
|
// Page view controller
|
|
self.pageVC = [[UIPageViewController alloc]
|
|
initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
|
|
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
|
|
options:nil];
|
|
self.pageVC.dataSource = self.items.count > 1 ? self : nil;
|
|
self.pageVC.delegate = self;
|
|
|
|
UIViewController *firstPage = [self viewControllerForIndex:self.currentIndex];
|
|
if (firstPage) [self.pageVC setViewControllers:@[firstPage] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
|
|
|
|
[self addChildViewController:self.pageVC];
|
|
self.pageVC.view.frame = self.view.bounds;
|
|
self.pageVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.view addSubview:self.pageVC.view];
|
|
[self.pageVC didMoveToParentViewController:self];
|
|
|
|
// Top bar
|
|
self.topBar = [[UIView alloc] init];
|
|
self.topBar.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:self.topBar];
|
|
|
|
UIImageSymbolConfiguration *cfg = [UIImageSymbolConfiguration configurationWithPointSize:17 weight:UIImageSymbolWeightSemibold];
|
|
|
|
self.closeBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
[self.closeBtn setImage:[UIImage systemImageNamed:@"xmark" withConfiguration:cfg] forState:UIControlStateNormal];
|
|
self.closeBtn.tintColor = [UIColor whiteColor];
|
|
self.closeBtn.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.closeBtn addTarget:self action:@selector(closeTapped) forControlEvents:UIControlEventTouchUpInside];
|
|
[self.topBar addSubview:self.closeBtn];
|
|
|
|
self.shareBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
[self.shareBtn setImage:[UIImage systemImageNamed:@"square.and.arrow.up" withConfiguration:cfg] forState:UIControlStateNormal];
|
|
self.shareBtn.tintColor = [UIColor whiteColor];
|
|
self.shareBtn.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.shareBtn addTarget:self action:@selector(shareTapped) forControlEvents:UIControlEventTouchUpInside];
|
|
[self.topBar addSubview:self.shareBtn];
|
|
|
|
self.counterLabel = [[UILabel alloc] init];
|
|
self.counterLabel.textColor = [UIColor whiteColor];
|
|
self.counterLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightSemibold];
|
|
self.counterLabel.textAlignment = NSTextAlignmentCenter;
|
|
self.counterLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.topBar addSubview:self.counterLabel];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[self.topBar.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
|
|
[self.topBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
|
[self.topBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
|
|
[self.topBar.heightAnchor constraintEqualToConstant:44],
|
|
[self.closeBtn.leadingAnchor constraintEqualToAnchor:self.topBar.leadingAnchor constant:16],
|
|
[self.closeBtn.centerYAnchor constraintEqualToAnchor:self.topBar.centerYAnchor],
|
|
[self.shareBtn.trailingAnchor constraintEqualToAnchor:self.topBar.trailingAnchor constant:-16],
|
|
[self.shareBtn.centerYAnchor constraintEqualToAnchor:self.topBar.centerYAnchor],
|
|
[self.counterLabel.centerXAnchor constraintEqualToAnchor:self.topBar.centerXAnchor],
|
|
[self.counterLabel.centerYAnchor constraintEqualToAnchor:self.topBar.centerYAnchor],
|
|
]];
|
|
|
|
// Bottom bar (caption — tap to expand/collapse)
|
|
self.bottomBar = [[UIView alloc] init];
|
|
self.bottomBar.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6];
|
|
self.bottomBar.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:self.bottomBar];
|
|
|
|
self.captionLabel = [[UILabel alloc] init];
|
|
self.captionLabel.textColor = [UIColor whiteColor];
|
|
self.captionLabel.font = [UIFont systemFontOfSize:14];
|
|
self.captionLabel.numberOfLines = 3; // collapsed
|
|
self.captionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
self.captionLabel.userInteractionEnabled = YES;
|
|
[self.bottomBar addSubview:self.captionLabel];
|
|
|
|
UITapGestureRecognizer *captionTap = [[UITapGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(toggleCaption)];
|
|
[self.captionLabel addGestureRecognizer:captionTap];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[self.bottomBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
|
[self.bottomBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
|
|
[self.bottomBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
|
|
[self.captionLabel.topAnchor constraintEqualToAnchor:self.bottomBar.topAnchor constant:12],
|
|
[self.captionLabel.leadingAnchor constraintEqualToAnchor:self.bottomBar.leadingAnchor constant:16],
|
|
[self.captionLabel.trailingAnchor constraintEqualToAnchor:self.bottomBar.trailingAnchor constant:-16],
|
|
[self.captionLabel.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-8],
|
|
]];
|
|
|
|
// 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];
|
|
}
|
|
|
|
- (void)updateChrome {
|
|
SCIMediaViewerItem *item = self.items[self.currentIndex];
|
|
|
|
// Counter (hide for single items)
|
|
if (self.items.count > 1) {
|
|
self.counterLabel.text = [NSString stringWithFormat:@"%lu / %lu", (unsigned long)(self.currentIndex + 1), (unsigned long)self.items.count];
|
|
self.counterLabel.hidden = NO;
|
|
} else {
|
|
self.counterLabel.hidden = YES;
|
|
}
|
|
|
|
// Caption
|
|
if (item.caption.length) {
|
|
self.captionLabel.text = item.caption;
|
|
self.bottomBar.hidden = NO;
|
|
} else {
|
|
self.bottomBar.hidden = YES;
|
|
}
|
|
}
|
|
|
|
- (void)toggleChrome {
|
|
self.chromeVisible = !self.chromeVisible;
|
|
[UIView animateWithDuration:0.25 animations:^{
|
|
CGFloat a = self.chromeVisible ? 1.0 : 0.0;
|
|
self.topBar.alpha = a;
|
|
self.bottomBar.alpha = a;
|
|
}];
|
|
}
|
|
|
|
- (void)toggleCaption {
|
|
self.captionExpanded = !self.captionExpanded;
|
|
[UIView animateWithDuration:0.25 animations:^{
|
|
self.captionLabel.numberOfLines = self.captionExpanded ? 0 : 3;
|
|
[self.view layoutIfNeeded];
|
|
}];
|
|
}
|
|
|
|
- (void)closeTapped {
|
|
// Pause any playing video
|
|
UIViewController *current = self.pageVC.viewControllers.firstObject;
|
|
if ([current isKindOfClass:[_SCIVideoPageVC class]]) {
|
|
[(((_SCIVideoPageVC *)current).playerVC.player) pause];
|
|
}
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
- (void)shareTapped {
|
|
SCIMediaViewerItem *item = self.items[self.currentIndex];
|
|
NSMutableArray *shareItems = [NSMutableArray array];
|
|
|
|
UIViewController *current = self.pageVC.viewControllers.firstObject;
|
|
if ([current isKindOfClass:[_SCIPhotoPageVC class]]) {
|
|
UIImage *img = [(_SCIPhotoPageVC *)current currentImage];
|
|
if (img) [shareItems addObject:img];
|
|
}
|
|
|
|
// For videos or if no image loaded, share the URL
|
|
if (!shareItems.count) {
|
|
NSURL *url = item.videoURL ?: item.photoURL;
|
|
if (url) [shareItems addObject:url];
|
|
}
|
|
|
|
if (!shareItems.count) return;
|
|
|
|
UIActivityViewController *vc = [[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
|
|
vc.popoverPresentationController.sourceView = self.shareBtn;
|
|
[self presentViewController:vc animated:YES completion:nil];
|
|
}
|
|
|
|
// ─── Page data source ───
|
|
|
|
- (UIViewController *)viewControllerForIndex:(NSUInteger)idx {
|
|
if (idx >= self.items.count) return nil;
|
|
SCIMediaViewerItem *item = self.items[idx];
|
|
|
|
if (item.videoURL) {
|
|
_SCIVideoPageVC *vc = [[_SCIVideoPageVC alloc] init];
|
|
vc.videoURL = item.videoURL;
|
|
vc.view.tag = (NSInteger)idx;
|
|
return vc;
|
|
} else if (item.photoURL) {
|
|
_SCIPhotoPageVC *vc = [[_SCIPhotoPageVC alloc] init];
|
|
vc.photoURL = item.photoURL;
|
|
vc.view.tag = (NSInteger)idx;
|
|
return vc;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (UIViewController *)pageViewController:(UIPageViewController *)pvc viewControllerBeforeViewController:(UIViewController *)vc {
|
|
NSInteger idx = vc.view.tag;
|
|
if (idx <= 0) return nil;
|
|
return [self viewControllerForIndex:idx - 1];
|
|
}
|
|
|
|
- (UIViewController *)pageViewController:(UIPageViewController *)pvc viewControllerAfterViewController:(UIViewController *)vc {
|
|
NSInteger idx = vc.view.tag;
|
|
if (idx + 1 >= (NSInteger)self.items.count) return nil;
|
|
return [self viewControllerForIndex:idx + 1];
|
|
}
|
|
|
|
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished
|
|
previousViewControllers:(NSArray<UIViewController *> *)prev transitionCompleted:(BOOL)completed {
|
|
if (!completed) return;
|
|
UIViewController *current = pvc.viewControllers.firstObject;
|
|
self.currentIndex = (NSUInteger)current.view.tag;
|
|
|
|
// Pause previous video
|
|
for (UIViewController *p in prev) {
|
|
if ([p isKindOfClass:[_SCIVideoPageVC class]]) {
|
|
[((_SCIVideoPageVC *)p).playerVC.player pause];
|
|
}
|
|
}
|
|
// Play new video
|
|
if ([current isKindOfClass:[_SCIVideoPageVC class]]) {
|
|
[((_SCIVideoPageVC *)current).playerVC.player play];
|
|
}
|
|
|
|
[self updateChrome];
|
|
}
|
|
|
|
- (BOOL)prefersStatusBarHidden { return YES; }
|
|
- (BOOL)prefersHomeIndicatorAutoHidden { return YES; }
|
|
|
|
@end
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
#pragma mark - Public API
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@implementation SCIMediaViewer
|
|
|
|
+ (void)presentNativeVideoPlayer:(NSURL *)url {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
|
|
playerVC.player = [AVPlayer playerWithURL:url];
|
|
playerVC.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
[topMostController() presentViewController:playerVC animated:YES completion:^{
|
|
[playerVC.player play];
|
|
}];
|
|
});
|
|
}
|
|
|
|
+ (void)showItem:(SCIMediaViewerItem *)item {
|
|
if (!item) { [SCIUtils showErrorHUDWithDescription:SCILocalized(@"No media to show")]; return; }
|
|
|
|
// Single video → native AVPlayerViewController directly (no wrapper)
|
|
if (item.videoURL) {
|
|
[self presentNativeVideoPlayer:item.videoURL];
|
|
return;
|
|
}
|
|
|
|
// Single photo → use our photo viewer container
|
|
[self showItems:@[item] startIndex:0];
|
|
}
|
|
|
|
+ (void)showItems:(NSArray<SCIMediaViewerItem *> *)items startIndex:(NSUInteger)index {
|
|
if (!items.count) { [SCIUtils showErrorHUDWithDescription:SCILocalized(@"No media to show")]; return; }
|
|
if (index >= items.count) index = 0;
|
|
|
|
// Single video item → native player
|
|
if (items.count == 1 && items[0].videoURL) {
|
|
[self presentNativeVideoPlayer:items[0].videoURL];
|
|
return;
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
_SCIMediaViewerContainerVC *vc = [[_SCIMediaViewerContainerVC alloc] init];
|
|
vc.items = items;
|
|
vc.currentIndex = index;
|
|
vc.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
|
[topMostController() presentViewController:vc animated:YES completion:nil];
|
|
});
|
|
}
|
|
|
|
+ (void)showWithVideoURL:(NSURL *)videoURL photoURL:(NSURL *)photoURL caption:(NSString *)caption {
|
|
[self showItem:[SCIMediaViewerItem itemWithVideoURL:videoURL photoURL:photoURL caption:caption]];
|
|
}
|
|
|
|
@end
|