Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/Vendor/MWPhotoBrowser/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 83 kB image not shown  

Quelle  MWPhotoBrowser.m  Sprache: unbekannt

 
Spracherkennung für: .m vermutete Sprache: Cobol {Cobol[48] JAVA[44] Rust[44]} [Methode: maximale Elemente, drei Dimensionen]

// This file is based on third party code, see below for the original author
// and original license.
// Modifications are (c) by Threema AG and licensed under the AGPLv3.

//
//  MWPhotoBrowser.m
//  MWPhotoBrowser
//
//  Created by Michael Waterfall on 14/10/2010.
//  Copyright 2010 d3i. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>
#import "MWCommon.h"
#import "MWPhotoBrowser.h"
#import "MWPhotoBrowserPrivate.h"
///***** BEGIN THREEMA MODIFICATION *********/
#import <SDWebImage/SDImageCache.h>
///***** END THREEMA MODIFICATION *********/
#import "UIImage+MWPhotoBrowser.h"
///***** BEGIN THREEMA MODIFICATION: Add AppGroup and utils *********/
#import "AppGroup.h"
#import "ThreemaUtilityObjC.h"
#import "Threema-Swift.h"
#import "ContactGroupPickerViewController.h"
#import "MediaBrowserFile.h"
@import FileUtility;
///***** END THREEMA MODIFICATION: Add AppGroup and utils *********/


#define PADDING                  10

static void * MWVideoPlayerObservation = &MWVideoPlayerObservation;

///***** BEGIN THREEMA MODIFICATION: Add delegates *********/
@interface MWPhotoBrowser () <ModalNavigationControllerDelegate, ContactGroupPickerDelegate>
@end
///***** END THREEMA MODIFICATION: Add delegates *********/

@implementation MWPhotoBrowser

#pragma mark - Init

- (id)init {
    if ((self = [super init])) {
        [self _initialisation];
    }
    return self;
}

- (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate {
    if ((self = [self init])) {
        _delegate = delegate;
 }
 return self;
}

- (id)initWithPhotos:(NSArray *)photosArray {
 if ((self = [self init])) {
  _fixedPhotosArray = photosArray;
 }
 return self;
}

- (id)initWithCoder:(NSCoder *)decoder {
 if ((self = [super initWithCoder:decoder])) {
        [self _initialisation];
 }
 return self;
}

- (void)_initialisation {
    
    // Defaults
    self.hidesBottomBarWhenPushed = YES;
    _hasBelongedToViewController = NO;
    _photoCount = NSNotFound;
    _previousLayoutBounds = CGRectZero;
    _currentPageIndex = 0;
    _previousPageIndex = NSUIntegerMax;
    _currentVideoIndex = NSUIntegerMax;
    _displayActionButton = YES;
    _displayNavArrows = NO;
    _zoomPhotosToFill = YES;
    _performingLayout = NO; // Reset on view did appear
    _rotating = NO;
    _viewIsActive = NO;
    _enableGrid = YES;
    _startOnGrid = NO;
    _enableSwipeToDismiss = YES;
    _delayToHideElements = 5;
    ///***** BEGIN THREEMA MODIFICATION: don't hide controll when voiceover is running *********/
    if (UIAccessibilityIsVoiceOverRunning()) {
        _alwaysShowControls = YES;
    } else {
        _alwaysShowControls = NO;
    }
    ///***** END THREEMA MODIFICATION: don't hide controll when voiceover is running *********/
    _visiblePages = [[NSMutableSet alloc] init];
    _recycledPages = [[NSMutableSet alloc] init];
    _photos = [[NSMutableArray alloc] init];
    _thumbPhotos = [[NSMutableArray alloc] init];
    _didSavePreviousStateOfNavBar = NO;
    _testCount = 0;
    self.automaticallyAdjustsScrollViewInsets = NO;
    
    // Listen for MWPhoto notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleMWPhotoLoadingDidEndNotification:)
                                                 name:MWPHOTO_LOADING_DID_END_NOTIFICATION
                                               object:nil];
    
    /***** BEGIN THREEMA MODIFICATION: listen for application foreground/background events to fix status bar *********/
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillEnterForeground:)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidEnterBackground:)
                                                 name:UIApplicationDidEnterBackgroundNotification
                                               object:nil];
    /***** END THREEMA MODIFICATION: listen for application foreground/background events to fix status bar *********/
}

- (void)dealloc {
    [self clearCurrentVideo: true];
    _pagingScrollView.delegate = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self releaseAllUnderlyingPhotos:NO];
    [[SDImageCache sharedImageCache] clearMemory]; // clear memory
}

- (void)releaseAllUnderlyingPhotos:(BOOL)preserveCurrent {
    // Create a copy in case this array is modified while we are looping through
    // Release photos
    NSArray *copy = [_photos copy];
    for (id p in copy) {
        if (p != [NSNull null]) {
            if (preserveCurrent && p == [self photoAtIndex:self.currentIndex]) {
                continue; // skip current
            }
            [p unloadUnderlyingImage];
        }
    }
    // Release thumbs
    copy = [_thumbPhotos copy];
    for (id p in copy) {
        if (p != [NSNull null]) {
            [p unloadUnderlyingImage];
        }
    }
}

- (void)didReceiveMemoryWarning {

 // Release any cached data, images, etc that aren't in use.
    [self releaseAllUnderlyingPhotos:YES];
 [_recycledPages removeAllObjects];
 
 // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
 
}

#pragma mark - View Loading

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    
    // Validate grid settings
    if (_startOnGrid) _enableGrid = YES;
    if (_enableGrid) {
        _enableGrid = [_delegate respondsToSelector:@selector(photoBrowser:thumbPhotoAtIndex:)];
    }
    if (!_enableGrid) _startOnGrid = NO;
 
 // View
    ///***** BEGIN THREEMA MODIFICATION *********
 self.view.backgroundColor = [Colors backgroundView];
    ///***** END THREEMA MODIFICATION *********
    self.view.clipsToBounds = YES;
 
 // Setup paging scrolling view
 CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
 _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
 _pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
 _pagingScrollView.pagingEnabled = YES;
 _pagingScrollView.delegate = self;
 _pagingScrollView.showsHorizontalScrollIndicator = NO;
 _pagingScrollView.showsVerticalScrollIndicator = NO;
    ///***** BEGIN THREEMA MODIFICATION *********
    _pagingScrollView.backgroundColor = [Colors backgroundView];
    ///***** END THREEMA MODIFICATION *********
    _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
 [self.view addSubview:_pagingScrollView];

    UIInterfaceOrientation orientation = [self currentInterfaceOrientation];

    // Toolbar
    _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:orientation]];
    ///***** BEGIN THREEMA MODIFICATION *********
    _toolbar.tintColor = UIColor.primary;
    ///***** END THREEMA MODIFICATION *********
    _toolbar.barTintColor = nil;
    [_toolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
    [_toolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsCompact];
    ///***** BEGIN THREEMA MODIFICATION*********
    _toolbar.barStyle = UIBarStyleDefault;
    ///***** END THREEMA MODIFICATION *********
    _toolbar.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
    
    // Toolbar
    _gridToolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:orientation]];
    ///***** BEGIN THREEMA MODIFICATION *********
    _gridToolbar.tintColor = [Colors backgroundView];
    ///***** END THREEMA MODIFICATION *********
    _gridToolbar.barTintColor = nil;
    [_gridToolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
    [_gridToolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsCompact];
    ///***** BEGIN THREEMA MODIFICATION *********
    _gridToolbar.barStyle = UIBarStyleDefault;
    ///***** END THREEMA MODIFICATION*********
    _gridToolbar.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
    
    // Toolbar Items
    if (self.displayNavArrows) {
        NSString *arrowPathFormat = @"MWPhotoBrowser.bundle/UIBarButtonItemArrow%@";
        UIImage *previousButtonImage = [UIImage imageForResourcePath:[NSString stringWithFormat:arrowPathFormat, @"Left"] ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]];
        UIImage *nextButtonImage = [UIImage imageForResourcePath:[NSString stringWithFormat:arrowPathFormat, @"Right"] ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]];
        _previousButton = [[UIBarButtonItem alloc] initWithImage:previousButtonImage style:UIBarButtonItemStylePlain target:self action:@selector(gotoPreviousPage)];
        _nextButton = [[UIBarButtonItem alloc] initWithImage:nextButtonImage style:UIBarButtonItemStylePlain target:self action:@selector(gotoNextPage)];
    }
    if (self.displayActionButton) {
        _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actionButtonPressed:)];
        _actionMultipleButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actionMultipleButtonPressed:)];
    }
    
    /***** BEGIN THREEMA MODIFICATION: add delete button *********/
    if (self.displayDeleteButton) {
        _deleteSingleButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteButtonPressed:)];
        _deleteMultipleButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteMultipleButtonPressed:)];
    }
    /***** END THREEMA MODIFICATION: add delete button *********/
    
    // Update
    [self reloadData: true];
    
    // Swipe to dismiss
    if (_enableSwipeToDismiss) {
        UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonPressed:)];
        swipeGesture.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp;
        [self.view addGestureRecognizer:swipeGesture];
    }
    
 // Super
    [super viewDidLoad];
 
}

- (void)performLayout {
    
    // Setup
    _performingLayout = YES;
    NSUInteger numberOfPhotos = [self numberOfPhotos];
    
 // Setup pages
    [_visiblePages removeAllObjects];
    [_recycledPages removeAllObjects];
    
    // Navigation buttons
    if ([self.navigationController.viewControllers objectAtIndex:0] == self || self.navigationController == nil) {
        if (@available(iOS 26, *)) {
            // We're first on stack so show done button
            _doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(doneButtonPressed:)];
            // Set appearance
            [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
            [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsCompact];
            [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
            [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsCompact];
            [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateNormal];
            [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateHighlighted];
            /***** BEGIN THREEMA MODIFICATION: 'done' on right *********/
            self.navigationItem.rightBarButtonItem = _doneButton;
            /***** END THREEMA MODIFICATION: 'done' on left *********/
        }
        else {
            if ([self.navigationController.viewControllers objectAtIndex:0] == self || self.navigationController == nil) {
                // We're first on stack so show done button
                _doneButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"done", nil) style:UIBarButtonItemStylePlain target:self action:@selector(doneButtonPressed:)];
                // Set appearance
                [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
                [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsCompact];
                [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
                [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsCompact];
                [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateNormal];
                [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateHighlighted];
                /***** BEGIN THREEMA MODIFICATION: 'done' on left *********/
                self.navigationItem.leftBarButtonItem = _doneButton;
                /***** END THREEMA MODIFICATION: 'done' on left *********/
            }
        }
    } else {
        // We're not first so show back button
        UIViewController *previousViewController = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2];
        NSString *backButtonTitle = previousViewController.navigationItem.backBarButtonItem ? previousViewController.navigationItem.backBarButtonItem.title : previousViewController.title;
        UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:backButtonTitle style:UIBarButtonItemStylePlain target:nil action:nil];
        // Appearance
        [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsCompact];
        [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
        [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsCompact];
        [newBackButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateNormal];
        [newBackButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateHighlighted];
        _previousViewControllerBackButton = previousViewController.navigationItem.backBarButtonItem; // remember previous
        previousViewController.navigationItem.backBarButtonItem = newBackButton;
    }

    // Toolbar items
    BOOL hasItems = NO;
    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil];
    fixedSpace.width = 32; // To balance action button
    UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
    NSMutableArray *items = [[NSMutableArray alloc] init];

    // Left button - Grid
    if (_enableGrid) {
        hasItems = YES;
        ///***** BEGIN THREEMA MODIFICATION: don't hide controll when voiceover is running *********/
        UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/UIBarButtonItemGrid" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] style:UIBarButtonItemStylePlain target:self action:@selector(showGridAnimated)];
        item.accessibilityLabel = NSLocalizedString(@"media_overview", nil);
        [items addObject:item];
        ///***** END THREEMA MODIFICATION: don't hide controll when voiceover is running *********/
    } else {
        [items addObject:fixedSpace];
    }

    // Middle - Nav
    if (_previousButton && _nextButton && numberOfPhotos > 1) {
        hasItems = YES;
        [items addObject:flexSpace];
        [items addObject:_previousButton];
        [items addObject:flexSpace];
        /***** BEGIN THREEMA MODIFICATION: add delete single button *********/
        if (_deleteSingleButton) {
            [items addObject:_deleteSingleButton];
            [items addObject:flexSpace];
        }
        /***** END THREEMA MODIFICATION: add delete single button *********/
        [items addObject:_nextButton];
        [items addObject:flexSpace];
    } else {
        [items addObject:flexSpace];
        /***** BEGIN THREEMA MODIFICATION: add delete single button *********/
        if (_deleteSingleButton) {
            [items addObject:_deleteSingleButton];
            [items addObject:flexSpace];
        }
        /***** END THREEMA MODIFICATION: add delete single button *********/
    }

    // Right - Action
    if (_actionButton && !(!hasItems && !self.navigationItem.rightBarButtonItem)) {
        [items addObject:_actionButton];
    } else {
        // We're not showing the toolbar so try and show in top right
        if (_actionButton)
            self.navigationItem.rightBarButtonItem = _actionButton;
        [items addObject:fixedSpace];
    }

    // Toolbar visibility
    [_toolbar setItems:items];
    BOOL hideToolbar = YES;
    for (UIBarButtonItem* item in _toolbar.items) {
        if (item != fixedSpace && item != flexSpace) {
            hideToolbar = NO;
            break;
        }
    }
    if (hideToolbar) {
        [_toolbar removeFromSuperview];
    } else {
        [self.view addSubview:_toolbar];
    }
    
    ///***** BEGIN THREEMA MODIFICATION: Add grid toolbar *********/

    // Grid toolbar items
    NSMutableArray *gridItems = [[NSMutableArray alloc] init];

    // Left button - Delete multiple
     if (_deleteMultipleButton) {
         [gridItems addObject:_deleteMultipleButton];
     } else {
         [gridItems addObject:fixedSpace];
     }

    // Middle - empty
    [gridItems addObject:flexSpace];
    
    // Right - Action
    if (_actionMultipleButton) {
        [gridItems addObject:_actionMultipleButton];
    } else {
        [gridItems addObject:fixedSpace];
    }

    // GridToolbar visibility
    [_gridToolbar setItems:gridItems];
    [_gridToolbar removeFromSuperview];
    
    ///***** END THREEMA MODIFICATION: Add grid toolbar *********/
    
    // Update nav
 [self updateNavigation];
    
    // Content offset
 _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex];
    [self tilePages];
    _performingLayout = NO;
    
}

- (BOOL)presentingViewControllerPrefersStatusBarHidden {
    UIViewController *presenting = self.presentingViewController;
    if (presenting) {
        if ([presenting isKindOfClass:[UINavigationController class]]) {
            presenting = [(UINavigationController *)presenting topViewController];
        }
    } else {
        // We're in a navigation controller so get previous one!
        if (self.navigationController && self.navigationController.viewControllers.count > 1) {
            presenting = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2];
        }
    }
    if (presenting) {
        return [presenting prefersStatusBarHidden];
    } else {
        return NO;
    }
}

#pragma mark - Appearance

- (void)viewWillAppear:(BOOL)animated {
    
 // Super
 [super viewWillAppear:animated];
    
    // Status bar
    if (!_viewHasAppearedInitially) {
        _leaveStatusBarAlone = [self presentingViewControllerPrefersStatusBarHidden];
    }
    // Set style]
    if (!_leaveStatusBarAlone && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
        _previousStatusBarStyle = self.preferredStatusBarStyle;
        _currentStatusBarStyle = UIStatusBarStyleLightContent;
        [self setNeedsStatusBarAppearanceUpdate];
    }

    // Navigation bar appearance
    if (!_viewIsActive && [self.navigationController.viewControllers objectAtIndex:0] != self) {
        [self storePreviousNavBarAppearance];
    }
    [self setNavBarAppearance:animated];
    
    // Update UI
 [self hideControlsAfterDelay];
    
    // Initial appearance
    if (!_viewHasAppearedInitially) {
        if (_startOnGrid) {
            [self showGrid:NO];
        }
    }
    
    // If rotation occured while we're presenting a modal
    // and the index changed, make sure we show the right one now
    if (_currentPageIndex != _pageIndexBeforeRotation) {
        [self jumpToPageAtIndex:_pageIndexBeforeRotation animated:NO];
    }
    
    // Layout
/***** BEGIN THREEMA MODIFICATION: ios 11 bugfix *********/
    [self layoutVisiblePages];
/***** END THREEMA MODIFICATION: ios 11 bu gfix *********/
    [self.view setNeedsLayout];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    _viewIsActive = YES;
    
    // Autoplay if first is video
    if (!_viewHasAppearedInitially) {
        if (_autoPlayOnAppear) {
            MWPhoto *photo = [self photoAtIndex:_currentPageIndex];
            if ([photo respondsToSelector:@selector(isVideo)] && photo.isVideo) {
                [self playVideoAtIndex:_currentPageIndex];
            }
        }
    }
    
    _viewHasAppearedInitially = YES;        
}

/***** BEGIN THREEMA MODIFICATION: show controls/status bar when moving into background to avoid iOS 8 back button bug *********/
- (void)applicationDidEnterBackground:(NSNotification*)notification {
    [self setControlsHidden:NO animated:NO permanent:YES];
}

- (void)applicationWillEnterForeground:(NSNotification*)notification {
    [self hideControlsAfterDelay];
}
/***** END THREEMA MODIFICATION: show controls/status bar when moving into background to avoid iOS 8 back button bug *********/

- (void)viewWillDisappear:(BOOL)animated {
    
    // Detect if rotation occurs while we're presenting a modal
    _pageIndexBeforeRotation = _currentPageIndex;
    
    // Check that we're disappearing for good
    // self.isMovingFromParentViewController just doesn't work, ever. Or self.isBeingDismissed
    if ((_doneButton && self.navigationController.isBeingDismissed) ||
        ([self.navigationController.viewControllers objectAtIndex:0] != self && ![self.navigationController.viewControllers containsObject:self])) {

        // State
        _viewIsActive = NO;
        [self clearCurrentVideo: true]; // Clear current playing video
        
        // Bar state / appearance
        [self restorePreviousNavBarAppearance:animated];
        
    }
    
    // Controls
    [self.navigationController.navigationBar.layer removeAllAnimations]; // Stop all animations on nav bar
    [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending toggles from taps
    [self setControlsHidden:NO animated:NO permanent:YES];
    
    // Status bar
    if (!_leaveStatusBarAlone && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
        _currentStatusBarStyle = _previousStatusBarStyle;
        [self setNeedsStatusBarAppearanceUpdate];
    }

 // Super
 [super viewWillDisappear:animated];
    
}

///***** BEGIN THREEMA MODIFICATION: show controls/status bar when moving into background to avoid iOS 8 back button bug *********/
//- (void)applicationDidEnterBackground:(NSNotification*)notification {
//    [self setControlsHidden:NO animated:NO permanent:YES];
//}
//
//- (void)applicationWillEnterForeground:(NSNotification*)notification {
//        [self hideControlsAfterDelay];
//}
///***** END THREEMA MODIFICATION: show controls/status bar when moving into background to avoid iOS 8 back button bug *********/

- (void)willMoveToParentViewController:(UIViewController *)parent {
    if (parent && _hasBelongedToViewController) {
        [NSException raise:@"MWPhotoBrowser Instance Reuse" format:@"MWPhotoBrowser instances cannot be reused."];
    }
}

- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (!parent) _hasBelongedToViewController = YES;
}

#pragma mark - Nav Bar Appearance

- (void)setNavBarAppearance:(BOOL)animated {
    [self.navigationController setNavigationBarHidden:NO animated:animated];
    UINavigationBar *navBar = self.navigationController.navigationBar;
    navBar.tintColor = [UIColor whiteColor];
    navBar.barTintColor = nil;
    /***** BEGINN THREEMA MODIFICATION *********/
    navBar.shadowImage = nil;
    navBar.translucent = YES;
    /***** END THREEMA MODIFICATION *********/
    navBar.barStyle = UIBarStyleBlack;
    navBar.translucent = YES;
    [navBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    [navBar setBackgroundImage:nil forBarMetrics:UIBarMetricsCompact];
}

- (void)storePreviousNavBarAppearance {
    _didSavePreviousStateOfNavBar = YES;
    _previousNavBarBarTintColor = self.navigationController.navigationBar.barTintColor;
    _previousNavBarTranslucent = self.navigationController.navigationBar.translucent;
    _previousNavBarTintColor = self.navigationController.navigationBar.tintColor;
    _previousNavBarHidden = self.navigationController.navigationBarHidden;
    _previousNavBarStyle = self.navigationController.navigationBar.barStyle;
    _previousNavigationBarBackgroundImageDefault = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
    _previousNavigationBarBackgroundImageLandscapePhone = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsCompact];
}

- (void)restorePreviousNavBarAppearance:(BOOL)animated {
    if (_didSavePreviousStateOfNavBar) {
        [self.navigationController setNavigationBarHidden:_previousNavBarHidden animated:animated];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        navBar.tintColor = _previousNavBarTintColor;
        navBar.translucent = _previousNavBarTranslucent;
        navBar.barTintColor = _previousNavBarBarTintColor;
        navBar.barStyle = _previousNavBarStyle;
        [navBar setBackgroundImage:_previousNavigationBarBackgroundImageDefault forBarMetrics:UIBarMetricsDefault];
        [navBar setBackgroundImage:_previousNavigationBarBackgroundImageLandscapePhone forBarMetrics:UIBarMetricsCompact];
        // Restore back button if we need to
        if (_previousViewControllerBackButton) {
            UIViewController *previousViewController = [self.navigationController topViewController]; // We've disappeared so previous is now top
            previousViewController.navigationItem.backBarButtonItem = _previousViewControllerBackButton;
            _previousViewControllerBackButton = nil;
        }
    }
}

#pragma mark - Layout

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
}

- (void)layoutVisiblePages {
    
 // Flag
 _performingLayout = YES;
 
 // Toolbar
    UIInterfaceOrientation orientation = [self currentInterfaceOrientation];

 _toolbar.frame = [self frameForToolbarAtOrientation:orientation];
    _gridToolbar.frame = [self frameForToolbarAtOrientation:orientation];

 // Remember index
 NSUInteger indexPriorToLayout = _currentPageIndex;
 
 // Get paging scroll view frame to determine if anything needs changing
 CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
    
 // Frame needs changing
    if (!_skipNextPagingScrollViewPositioning) {
        _pagingScrollView.frame = pagingScrollViewFrame;
    }
    _skipNextPagingScrollViewPositioning = NO;
 
 // Recalculate contentSize based on current orientation
 _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
 
 // Adjust frames and configuration of each visible page
 for (MWZoomingScrollView *page in _visiblePages) {
        NSUInteger index = page.index;
  page.frame = [self frameForPageAtIndex:index];
        if (page.captionView) {
            page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index];
        }
        if (page.selectedButton) {
            page.selectedButton.frame = [self frameForSelectedButton:page.selectedButton atIndex:index];
        }
        if (page.playButton) {
            page.playButton.frame = [self frameForPlayButton:page.playButton atIndex:index];
        }
        
        // Adjust scales if bounds has changed since last time
        if (!CGRectEqualToRect(_previousLayoutBounds, self.view.bounds)) {
            // Update zooms for new bounds
            [page setMaxMinZoomScalesForCurrentBounds];
            _previousLayoutBounds = self.view.bounds;
        }

 }
    
    // Adjust video loading indicator if it's visible
    [self positionVideoLoadingIndicator];
 
 // Adjust contentOffset to preserve page location based on values collected prior to location
 _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout];
 [self didStartViewingPageAtIndex:_currentPageIndex]; // initial
    
 // Reset
 _currentPageIndex = indexPriorToLayout;
 _performingLayout = NO;
    
}

#pragma mark - Rotation

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    
 // Remember page index before rotation
 _pageIndexBeforeRotation = _currentPageIndex;
 _rotating = YES;
    
    /***** BEGIN THREEMA MODIFICATION: Remove this code, its not needed anymore *********/
    // In iOS 7 the nav bar gets shown after rotation, but might as well do this for everything!
//    if ([self areControlsHidden]) {
//        // Force hidden
//        self.navigationController.navigationBarHidden = YES;
//    }
    /***** END THREEMA MODIFICATION: Remove this code, its not needed anymore 8.0 *********/
 
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
 
 // Perform layout
 _currentPageIndex = _pageIndexBeforeRotation;
 
 // Delay control holding
 [self hideControlsAfterDelay];
    
    // Layout
    [self layoutVisiblePages];
 
    /***** BEGIN THREEMA MODIFICATION: refresh status bar visibility when rotating on iOS 8.0 *********/
    [self setNeedsStatusBarAppearanceUpdate];
    /***** END THREEMA MODIFICATION: refresh status bar visibility when rotating on iOS 8.0 *********/
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
 _rotating = NO;
    // Ensure nav bar isn't re-displayed
    if ([self areControlsHidden]) {
        self.navigationController.navigationBarHidden = NO;
        self.navigationController.navigationBar.alpha = 0;
    }
}

#pragma mark - Data

- (NSUInteger)currentIndex {
    return _currentPageIndex;
}

/***** BEGIN THREEMA MODIFICATION: update for delete files *********/
- (void)reloadData:(BOOL)updateLayout {
    
    // Reset
    _photoCount = NSNotFound;
    
    // Get data
    NSUInteger numberOfPhotos = [self numberOfPhotos];
    [self releaseAllUnderlyingPhotos:YES];
    [_photos removeAllObjects];
    [_thumbPhotos removeAllObjects];
    for (int i = 0; i < numberOfPhotos; i++) {
        [_photos addObject:[NSNull null]];
        [_thumbPhotos addObject:[NSNull null]];
    }

    // Update current page index
    if (numberOfPhotos > 0) {
        _currentPageIndex = MAX(0, MIN(_currentPageIndex, numberOfPhotos - 1));
    } else {
        _currentPageIndex = 0;
    }
    
    if (_gridController) {
        _toolbar.alpha = 0.0;
    }
    
    // Update layout
    if ([self isViewLoaded] && updateLayout) {
        _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
        
        while (_pagingScrollView.subviews.count) {
            [[_pagingScrollView.subviews lastObject] removeFromSuperview];
        }
        [self performLayout];
        [self.view setNeedsLayout];
    }
}
/***** END THREEMA MODIFICATION: update for delete files *********/

- (NSUInteger)numberOfPhotos {
    if (_photoCount == NSNotFound) {
        if ([_delegate respondsToSelector:@selector(numberOfPhotosInPhotoBrowser:)]) {
            _photoCount = [_delegate numberOfPhotosInPhotoBrowser:self];
        } else if (_fixedPhotosArray) {
            _photoCount = _fixedPhotosArray.count;
        }
    }
    if (_photoCount == NSNotFound) _photoCount = 0;
    return _photoCount;
}

- (id<MWPhoto>)photoAtIndex:(NSUInteger)index {
    id <MWPhoto> photo = nil;
    if (index < _photos.count) {
        if ([_photos objectAtIndex:index] == [NSNull null]) {
            if ([_delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:)]) {
                photo = [_delegate photoBrowser:self photoAtIndex:index];
            } else if (_fixedPhotosArray && index < _fixedPhotosArray.count) {
                photo = [_fixedPhotosArray objectAtIndex:index];
            }
            if (photo) [_photos replaceObjectAtIndex:index withObject:photo];
        } else {
            photo = [_photos objectAtIndex:index];
        }
    }
    return photo;
}

- (id<MWPhoto>)thumbPhotoAtIndex:(NSUInteger)index {
    id <MWPhoto> photo = nil;
    if (index < _thumbPhotos.count) {
        if ([_thumbPhotos objectAtIndex:index] == [NSNull null]) {
            if ([_delegate respondsToSelector:@selector(photoBrowser:thumbPhotoAtIndex:)]) {
                photo = [_delegate photoBrowser:self thumbPhotoAtIndex:index];
            }
            if (photo) [_thumbPhotos replaceObjectAtIndex:index withObject:photo];
        } else {
            photo = [_thumbPhotos objectAtIndex:index];
        }
    }
    return photo;
}

- (MWCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index {
    MWCaptionView *captionView = nil;
    if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) {
        captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index];
    } else {
        id <MWPhoto> photo = [self photoAtIndex:index];
        if ([photo respondsToSelector:@selector(caption)]) {
            if ([photo caption]) captionView = [[MWCaptionView alloc] initWithPhoto:photo];
        }
    }
    captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha
    return captionView;
}

- (BOOL)photoIsSelectedAtIndex:(NSUInteger)index {
    BOOL value = NO;
    if (_displaySelectionButtons) {
        if ([self.delegate respondsToSelector:@selector(photoBrowser:isPhotoSelectedAtIndex:)]) {
            value = [self.delegate photoBrowser:self isPhotoSelectedAtIndex:index];
        }
    }
    return value;
}

- (void)setPhotoSelected:(BOOL)selected atIndex:(NSUInteger)index {
    if (_displaySelectionButtons) {
        if ([self.delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:selectedChanged:)]) {
            [self.delegate photoBrowser:self photoAtIndex:index selectedChanged:selected];
        }
        if ([self.delegate respondsToSelector:@selector(mediaSelectionCount)]) {
            NSUInteger *selectionCount = [self.delegate mediaSelectionCount];
            _deleteMultipleButton.enabled = selectionCount > 0;
            _actionMultipleButton.enabled = selectionCount > 0;
        }
    }
}

- (UIImage *)imageForPhoto:(id<MWPhoto>)photo {
 if (photo) {
  // Get image or obtain in background
  if ([photo underlyingImage]) {
   return [photo underlyingImage];
  } else {
            [photo loadUnderlyingImageAndNotify];
  }
 }
 return nil;
}

- (void)loadAdjacentPhotosIfNecessary:(id<MWPhoto>)photo {
    MWZoomingScrollView *page = [self pageDisplayingPhoto:photo];
    if (page) {
        // If page is current page then initiate loading of previous and next pages
        NSUInteger pageIndex = page.index;
        if (_currentPageIndex == pageIndex) {
            if (pageIndex > 0) {
                // Preload index - 1
                id <MWPhoto> photo = [self photoAtIndex:pageIndex-1];
                if (![photo underlyingImage]) {
                    [photo loadUnderlyingImageAndNotify];
                    MWLog(@"Pre-loading image at index %lu", (unsigned long)pageIndex-1);
                }
            }
            if (pageIndex < [self numberOfPhotos] - 1) {
                // Preload index + 1
                id <MWPhoto> photo = [self photoAtIndex:pageIndex+1];
                if (![photo underlyingImage]) {
                    [photo loadUnderlyingImageAndNotify];
                    MWLog(@"Pre-loading image at index %lu", (unsigned long)pageIndex+1);
                }
            }
        }
    }
}

#pragma mark - MWPhoto Loading Notification

- (void)handleMWPhotoLoadingDidEndNotification:(NSNotification *)notification {
    id <MWPhoto> photo = [notification object];
    MWZoomingScrollView *page = [self pageDisplayingPhoto:photo];
    if (page) {
        if ([photo underlyingImage]) {
            // Successful load
            [page displayImage];
            [self loadAdjacentPhotosIfNecessary:photo];
        } else {
            
            // Failed to load
            [page displayImageFailure];
        }
        // Update nav
        [self updateNavigation];
    }
}

#pragma mark - Paging

- (void)tilePages {
 
 // Calculate which pages should be visible
 // Ignore padding as paging bounces encroach on that
 // and lead to false page loads
 CGRect visibleBounds = _pagingScrollView.bounds;
 NSInteger iFirstIndex = (NSInteger)floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds));
 NSInteger iLastIndex  = (NSInteger)floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds));
    if (iFirstIndex < 0) iFirstIndex = 0;
    if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1;
    if (iLastIndex < 0) iLastIndex = 0;
    if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1;
 
//    // Recycle no longer needed pages
    NSInteger pageIndex;
    for (MWZoomingScrollView *page in _visiblePages) {
        pageIndex = page.index;
        if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) {
            [_recycledPages addObject:page];
            [page.captionView removeFromSuperview];
            [page.selectedButton removeFromSuperview];
            [page.playButton removeFromSuperview];
            [page prepareForReuse];
            [page removeFromSuperview];
            MWLog(@"Removed page at index %lu", (unsigned long)pageIndex);
        }
    }
    [_visiblePages minusSet:_recycledPages];
    while (_recycledPages.count > 2) // Only keep 2 recycled pages
        [_recycledPages removeObject:[_recycledPages anyObject]];
 
 // Add missing pages
 for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) {
  if (![self isDisplayingPageForIndex:index]) {
            
            // Add new page
            MWZoomingScrollView *page = [self dequeueRecycledPage];
            if (!page) {
                page = [[MWZoomingScrollView alloc] initWithPhotoBrowser:self];
            }
            [_visiblePages addObject:page];
   [self configurePage:page forIndex:index];

   [_pagingScrollView addSubview:page];
   MWLog(@"Added page at index %lu", (unsigned long)index);
            
            // Add caption
            MWCaptionView *captionView = [self captionViewForPhotoAtIndex:index];
            if (captionView) {
                captionView.frame = [self frameForCaptionView:captionView atIndex:index];
                [_pagingScrollView addSubview:captionView];
                page.captionView = captionView;
            }
            
            // Add play button if needed
            if (page.displayingVideo) {
                UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
                [playButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/PlayButtonOverlayLarge" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateNormal];
                [playButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/PlayButtonOverlayLargeTap" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateHighlighted];
                [playButton addTarget:self action:@selector(playButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
                [playButton sizeToFit];
                playButton.frame = [self frameForPlayButton:playButton atIndex:index];
                [_pagingScrollView addSubview:playButton];
                page.playButton = playButton;
            }
            
            // Add selected button
            if (self.displaySelectionButtons) {
                UIButton *selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
                [selectedButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedOff" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateNormal];
                UIImage *selectedOnImage;
                if (self.customImageSelectedIcon) {
                    ///***** BEGIN THREEMA MODIFICATION: use image instead of name *********/
                    selectedOnImage = self.customImageSelectedIcon;
                    ///***** END THREEMA MODIFICATION: use image instead of name *********/
                } else {
                    selectedOnImage = [UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedOn" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]];
                }
                [selectedButton setImage:selectedOnImage forState:UIControlStateSelected];
                [selectedButton sizeToFit];
                selectedButton.adjustsImageWhenHighlighted = NO;
                [selectedButton addTarget:self action:@selector(selectedButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
                selectedButton.frame = [self frameForSelectedButton:selectedButton atIndex:index];
                [_pagingScrollView addSubview:selectedButton];
                page.selectedButton = selectedButton;
                selectedButton.selected = [self photoIsSelectedAtIndex:index];
                /***** BEGIN THREEMA MODIFICATION: *********/
                selectedButton.accessibilityLabel = NSLocalizedString(@"mwphotobrowser_select", nil);
                /***** END THREEMA MODIFICATION: *********/
            }
            
  }
 }
 
}

- (void)updateVisiblePageStates {
    NSSet *copy = [_visiblePages copy];
    for (MWZoomingScrollView *page in copy) {
        
        // Update selection
        page.selectedButton.selected = [self photoIsSelectedAtIndex:page.index];
        
    }
}

- (BOOL)isDisplayingPageForIndex:(NSUInteger)index {
 for (MWZoomingScrollView *page in _visiblePages)
  if (page.index == index) return YES;
 return NO;
}

- (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index {
 MWZoomingScrollView *thePage = nil;
 for (MWZoomingScrollView *page in _visiblePages) {
  if (page.index == index) {
   thePage = page; break;
  }
 }
 return thePage;
}

- (MWZoomingScrollView *)pageDisplayingPhoto:(id<MWPhoto>)photo {
 MWZoomingScrollView *thePage = nil;
 for (MWZoomingScrollView *page in _visiblePages) {
  if (page.photo == photo) {
   thePage = page; break;
  }
 }
 return thePage;
}

- (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index {
 page.frame = [self frameForPageAtIndex:index];
    page.index = index;
    page.photo = [self photoAtIndex:index];
}

- (MWZoomingScrollView *)dequeueRecycledPage {
 MWZoomingScrollView *page = [_recycledPages anyObject];
 if (page) {
  [_recycledPages removeObject:page];
 }
 return page;
}

// Handle page changes
- (void)didStartViewingPageAtIndex:(NSUInteger)index {
    
    // Handle 0 photos
    if (![self numberOfPhotos]) {
        // Show controls
        [self setControlsHidden:NO animated:YES permanent:YES];
        return;
    }
    
    // Handle video on page change
    if (!_rotating && index != _currentVideoIndex) {
        [self clearCurrentVideo: true];
    }
    
    // Release images further away than +/-1
    NSUInteger i;
    if (index > 0) {
        // Release anything < index - 1
        for (i = 0; i < index-1; i++) { 
            id photo = [_photos objectAtIndex:i];
            if (photo != [NSNull null]) {
                [photo unloadUnderlyingImage];
                [_photos replaceObjectAtIndex:i withObject:[NSNull null]];
                MWLog(@"Released underlying image at index %lu", (unsigned long)i);
            }
        }
    }
    if (index < [self numberOfPhotos] - 1) {
        // Release anything > index + 1
        for (i = index + 2; i < _photos.count; i++) {
            id photo = [_photos objectAtIndex:i];
            if (photo != [NSNull null]) {
                [photo unloadUnderlyingImage];
                [_photos replaceObjectAtIndex:i withObject:[NSNull null]];
                MWLog(@"Released underlying image at index %lu", (unsigned long)i);
            }
        }
    }
    
    // Load adjacent images if needed and the photo is already
    // loaded. Also called after photo has been loaded in background
    id <MWPhoto> currentPhoto = [self photoAtIndex:index];
    if ([currentPhoto underlyingImage]) {
        // photo loaded so load ajacent now
        [self loadAdjacentPhotosIfNecessary:currentPhoto];
    }
    
    /***** BEGIN THREEMA MODIFICATION: always show controls for video *********/
    if ([currentPhoto showControls] && !_peeking) {
        [self setControlsHidden:NO animated:YES permanent:YES];
    } else {
        [self hideControlsAfterDelay];
    }
    /***** END THREEMA MODIFICATION: always show controls for video *********/
    
    // Notify delegate
    if (index != _previousPageIndex) {
        if ([_delegate respondsToSelector:@selector(photoBrowser:didDisplayPhotoAtIndex:)])
            [_delegate photoBrowser:self didDisplayPhotoAtIndex:index];
        _previousPageIndex = index;
    }
    
    // Update nav
    [self updateNavigation];
    
}

#pragma mark - Frame Calculations

- (CGRect)frameForPagingScrollView {
    CGRect frame = self.view.bounds;// [[UIScreen mainScreen] bounds];
    frame.origin.x -= PADDING;
    frame.size.width += (2 * PADDING);
    return CGRectIntegral(frame);
}

- (CGRect)frameForPageAtIndex:(NSUInteger)index {
    // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in
    // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's
    // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape
    // because it has a rotation transform applied.
    CGRect bounds = _pagingScrollView.bounds;
    CGRect pageFrame = bounds;
    pageFrame.size.width -= (2 * PADDING);
    pageFrame.origin.x = (bounds.size.width * index) + PADDING;
    return CGRectIntegral(pageFrame);
}

- (CGSize)contentSizeForPagingScrollView {
    // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above.
    CGRect bounds = _pagingScrollView.bounds;
    return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height);
}

- (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index {
 CGFloat pageWidth = _pagingScrollView.bounds.size.width;
 CGFloat newOffset = index * pageWidth;
 return CGPointMake(newOffset, 0);
}

- (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation {
    CGFloat height = 44;
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone &&
        UIInterfaceOrientationIsLandscape(orientation)) height = 32;
    CGFloat adjust = 0;
    UIEdgeInsets safeArea = [[[UIApplication sharedApplication] windows] firstObject].safeAreaInsets;
    adjust = safeArea.bottom;
    return CGRectIntegral(CGRectMake(0, self.view.bounds.size.height - height - adjust, self.view.bounds.size.width, height));
}

- (CGRect)frameForCaptionView:(MWCaptionView *)captionView atIndex:(NSUInteger)index {
    CGRect pageFrame = [self frameForPageAtIndex:index];
    CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)];
    UIInterfaceOrientation orientation = [self currentInterfaceOrientation];
    CGRect toolBarRect = [self frameForToolbarAtOrientation:orientation];
    CGFloat originY;
    // BEGIN THREEMA MODIFICATION
    if (@available(iOS 26.0, *)) {
        originY = toolBarRect.origin.y - captionSize.height - 20;
    } else {
        originY = toolBarRect.origin.y - captionSize.height;
    }
    
    CGRect captionFrame = CGRectMake(pageFrame.origin.x,
                                     originY,
                                     pageFrame.size.width,
                                     captionSize.height);
    // END THREEMA MODIFICATION
    return CGRectIntegral(captionFrame);
}

- (CGRect)frameForSelectedButton:(UIButton *)selectedButton atIndex:(NSUInteger)index {
    CGRect pageFrame = [self frameForPageAtIndex:index];
    CGFloat padding = 20;
    CGFloat yOffset = 0;
    if (![self areControlsHidden]) {
        UINavigationBar *navBar = self.navigationController.navigationBar;
        yOffset = navBar.frame.origin.y + navBar.frame.size.height;
    }
    /***** BEGIN THREEMA MODIFICATION: change button to bottom right instead of top right *********/
    CGRect selectedButtonFrame = CGRectMake(pageFrame.origin.x + pageFrame.size.width - selectedButton.frame.size.width - padding,
                                            pageFrame.origin.y + pageFrame.size.height - selectedButton.frame.size.height - padding,
                                            selectedButton.frame.size.width,
                                            selectedButton.frame.size.height);
    /***** END THREEMA MODIFICATION: change button to bottom right instead of top right *********/
    return CGRectIntegral(selectedButtonFrame);
}

- (CGRect)frameForPlayButton:(UIButton *)playButton atIndex:(NSUInteger)index {
    CGRect pageFrame = [self frameForPageAtIndex:index];
    return CGRectMake(floorf(CGRectGetMidX(pageFrame) - playButton.frame.size.width / 2),
                      floorf(CGRectGetMidY(pageFrame) - playButton.frame.size.height / 2),
                      playButton.frame.size.width,
                      playButton.frame.size.height);
}

#pragma mark - UIScrollView Delegate

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
 
    // Checks
 if (!_viewIsActive || _performingLayout || _rotating) return;
 
 // Tile pages
    [self tilePages];
 
 // Calculate current page
 CGRect visibleBounds = _pagingScrollView.bounds;
 NSInteger index = (NSInteger)(floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds)));
    if (index < 0) index = 0;
 if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1;
 NSUInteger previousCurrentPage = _currentPageIndex;
 _currentPageIndex = index;
 if (_currentPageIndex != previousCurrentPage) {
        [self didStartViewingPageAtIndex:index];
    }
 
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
 // Hide controls when dragging begins
    /***** BEGIN THREEMA MODIFICATION: do not hide controls when begin dragging *********/
// [self setControlsHidden:YES animated:YES permanent:NO];
    /***** END THREEMA MODIFICATION: do not hide controls when begin dragging *********/
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
 // Update nav when page changes
 [self updateNavigation];
}

#pragma mark - Navigation

- (void)updateNavigation {
    
 // Title
    NSUInteger numberOfPhotos = [self numberOfPhotos];
    if (_gridController) {
        if (_gridController.selectionMode) {
            self.title = NSLocalizedString(@"mw_select_photos_title", nil);
        } else {
            NSString *photosText;
            if (numberOfPhotos == 1) {
                photosText = NSLocalizedString(@"photo", @"Used in the context: '1 photo'");
            } else {
                photosText = NSLocalizedString(@"photos", @"Used in the context: '3 photos'");
            }
            self.title = [NSString stringWithFormat:@"%lu %@", (unsigned long)numberOfPhotos, photosText];
        }
    } else if (numberOfPhotos > 1) {
        if ([_delegate respondsToSelector:@selector(photoBrowser:titleForPhotoAtIndex:)]) {
            self.title = [_delegate photoBrowser:self titleForPhotoAtIndex:_currentPageIndex];
        } else {
            self.title = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), NSLocalizedString(@"of", @"Used in the context: 'Showing 1 of 3 items'"), (unsigned long)numberOfPhotos];
        }
 } else {
  self.title = nil;
 }
 
 // Buttons
 _previousButton.enabled = (_currentPageIndex > 0);
 _nextButton.enabled = (_currentPageIndex < numberOfPhotos - 1);
    
//    // Disable action button if there is no image or it's a video
//    MWPhoto *photo = [self photoAtIndex:_currentPageIndex];
//    if ([photo underlyingImage] == nil || ([photo respondsToSelector:@selector(isVideo)] && photo.isVideo)) {
//        _actionButton.enabled = NO;
//        _actionButton.tintColor = [UIColor clearColor]; // Tint to hide button
//    } else {
//        _actionButton.enabled = YES;
//        _actionButton.tintColor = nil;
//    }
 
}

- (void)jumpToPageAtIndex:(NSUInteger)index animated:(BOOL)animated {
 
 // Change page
 if (index < [self numberOfPhotos]) {
  CGRect pageFrame = [self frameForPageAtIndex:index];
        [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:animated];
  [self updateNavigation];
 }
 
 // Update timer to give more time
 [self hideControlsAfterDelay];
 
}

- (void)gotoPreviousPage {
    [self showPreviousPhotoAnimated:NO];
}
- (void)gotoNextPage {
    [self showNextPhotoAnimated:NO];
}

- (void)showPreviousPhotoAnimated:(BOOL)animated {
    [self jumpToPageAtIndex:_currentPageIndex-1 animated:animated];
}

- (void)showNextPhotoAnimated:(BOOL)animated {
    [self jumpToPageAtIndex:_currentPageIndex+1 animated:animated];
}

#pragma mark - Interactions

- (void)selectedButtonTapped:(id)sender {
    UIButton *selectedButton = (UIButton *)sender;
    selectedButton.selected = !selectedButton.selected;
    NSUInteger index = NSUIntegerMax;
    for (MWZoomingScrollView *page in _visiblePages) {
        if (page.selectedButton == selectedButton) {
            index = page.index;
            break;
        }
    }
    if (index != NSUIntegerMax) {
        [self setPhotoSelected:selectedButton.selected atIndex:index];
    }
}

- (void)playButtonTapped:(id)sender {
    // Ignore if we're already playing a video
    if (_currentVideoIndex != NSUIntegerMax) {
        return;
    }
    NSUInteger index = [self indexForPlayButton:sender];
    if (index != NSUIntegerMax) {
        if (!_currentVideoPlayerViewController) {
            [self playVideoAtIndex:index];
        }
    }
}

- (NSUInteger)indexForPlayButton:(UIView *)playButton {
    NSUInteger index = NSUIntegerMax;
    for (MWZoomingScrollView *page in _visiblePages) {
        if (page.playButton == playButton) {
            index = page.index;
            break;
        }
    }
    return index;
}

#pragma mark - Video

- (void)playVideoAtIndex:(NSUInteger)index {
    id photo = [self photoAtIndex:index];
    if ([photo respondsToSelector:@selector(getVideoURL:)]) {
        
        // Valid for playing
        [self clearCurrentVideo: true];
        _currentVideoIndex = index;
        [self setVideoLoadingIndicatorVisible:YES atPageIndex:index];
        
        ///***** BEGIN THREEMA MODIFICATION: ignore mute switch *********/
        NSInteger state = [[VoIPCallStateManager shared] currentCallState];
        if (state == CallStateIdle && !NavigationBarPromptHandler.isGroupCallActive) {
            _prevAudioCategory = [[AVAudioSession sharedInstance] category];
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        }
        ///***** END THREEMA MODIFICATION: ignore mute switch *********/

        // Get video and play
        typeof(self) __weak weakSelf = self;
        [photo getVideoURL:^(NSURL *url) {
            dispatch_async(dispatch_get_main_queue(), ^{
                // If the video is not playing anymore then bail
                typeof(self) strongSelf = weakSelf;
                if (!strongSelf) return;
                if (strongSelf->_currentVideoIndex != index || !strongSelf->_viewIsActive) {
                    return;
                }
                if (url) {
                    [weakSelf _playVideo:url atPhotoIndex:index];
                } else {
                    if ([_delegate respondsToSelector:@selector(photoBrowser:objectIDAtIndex:)]) {
                       NSManagedObjectID * objectID = [_delegate photoBrowser:self objectIDAtIndex:index];
                        BlobManagerObjCWrapper *manager = [[BlobManagerObjCWrapper alloc] init];
                        [manager syncBlobsFor:objectID onCompletion:^(enum BlobManagerObjCResult result){
                            NSAssert(result != BlobManagerObjCResultUploaded, @"We never upload a file in this case");
                            dispatch_async(dispatch_get_main_queue(), ^{
                                [weakSelf setVideoLoadingIndicatorVisible:NO atPageIndex:index];
                                [weakSelf playVideoAtIndex:index];
                            });
                        }];
                    } else  {
                        [weakSelf setVideoLoadingIndicatorVisible:NO atPageIndex:index];
                    }
                }
            });
        }];
        
    }
}

- (void)_playVideo:(NSURL *)videoURL atPhotoIndex:(NSUInteger)index {

    AVPlayer *player = [AVPlayer playerWithURL:videoURL];
    _currentVideoPlayerViewController = [AVPlayerViewController new];
    _currentVideoPlayerViewController.player = player;
    ///***** BEGIN THREEMA MODIFICATION: ignore mute switch *********/
    _currentVideoPlayerViewController.delegate = self;
    ///***** END THREEMA MODIFICATION: ignore mute switch *********/
    [self presentViewController:_currentVideoPlayerViewController animated:YES completion:^{
        [_currentVideoPlayerViewController.player play];
        [self clearCurrentVideo: false];
    }];
}

///***** BEGIN THREEMA MODIFICATION: reset audio *********/
- (void)clearCurrentVideo:(BOOL)audioFree {
    [_currentVideoLoadingIndicator removeFromSuperview];
    _currentVideoPlayerViewController = nil;
    _currentVideoLoadingIndicator = nil;
    [[self pageDisplayedAtIndex:_currentVideoIndex] playButton].hidden = NO;
    _currentVideoIndex = NSUIntegerMax;
    NSInteger state = [[VoIPCallStateManager shared] currentCallState];
    if (audioFree == true && state == CallStateIdle &&
        !NavigationBarPromptHandler.isGroupCallActive) {
        [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
    }
}
///***** END THREEMA MODIFICATION: reset audio *********/

- (void)setVideoLoadingIndicatorVisible:(BOOL)visible atPageIndex:(NSUInteger)pageIndex {
    if (_currentVideoLoadingIndicator && !visible) {
        [_currentVideoLoadingIndicator removeFromSuperview];
        _currentVideoLoadingIndicator = nil;
        [[self pageDisplayedAtIndex:pageIndex] playButton].hidden = NO;
    } else if (!_currentVideoLoadingIndicator && visible) {
        _currentVideoLoadingIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero];
        [_currentVideoLoadingIndicator sizeToFit];
        [_currentVideoLoadingIndicator startAnimating];
        [_pagingScrollView addSubview:_currentVideoLoadingIndicator];
        [self positionVideoLoadingIndicator];
        [[self pageDisplayedAtIndex:pageIndex] playButton].hidden = YES;
    }
}

- (void)positionVideoLoadingIndicator {
    if (_currentVideoLoadingIndicator && _currentVideoIndex != NSUIntegerMax) {
        CGRect frame = [self frameForPageAtIndex:_currentVideoIndex];
        _currentVideoLoadingIndicator.center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
    }
}

#pragma mark - Grid

- (void)showGridAnimated {
    [self showGrid:YES];
}

- (void)showGrid:(BOOL)animated {

    if (_gridController) return;
    
    // Clear video
    [self clearCurrentVideo: true];
    
    // Init grid controller
    _gridController = [[MWGridViewController alloc] init];
    
    /***** BEGIN THREEMA MODIFICATION: add select button *********/
    _selectButton = [[UIBarButtonItem alloc] initWithTitle:[BundleUtil localizedStringForKey:@"mwphotobrowser_select"] style:UIBarButtonItemStylePlain target:self action:@selector(selectButtonPressed:)];
    _selectButton.enabled = [self numberOfPhotos] > 0;
    self.navigationItem.rightBarButtonItem = _selectButton;
    /***** END THREEMA MODIFICATION: add select button *********/
    
    _gridController.browser = self;
    _gridController.selectionMode = _displaySelectionButtons;
    _gridController.view.frame = self.view.bounds;
    _gridController.view.frame = CGRectOffset(_gridController.view.frame, 0, (self.startOnGrid ? -1 : 1) * self.view.bounds.size.height);

    // Stop specific layout being triggered
    _skipNextPagingScrollViewPositioning = YES;
    
    // Add as a child view controller
    [self addChildViewController:_gridController];
    [self.view addSubview:_gridController.view];
    
    // Perform any adjustments
    [_gridController.view layoutIfNeeded];
    [_gridController adjustOffsetsAsRequired];
    
    // Hide action button on nav bar if it exists
    if (self.navigationItem.rightBarButtonItem == _actionButton) {
        _gridPreviousRightNavItem = _actionButton;
        [self.navigationItem setRightBarButtonItem:nil animated:YES];
    } else {
        _gridPreviousRightNavItem = nil;
    }
    
    // Update
    [self updateNavigation];
    [self setControlsHidden:NO animated:YES permanent:YES];
    
    // Animate grid in and photo scroller out
    [_gridController willMoveToParentViewController:self];
    [UIView animateWithDuration:animated ? 0.3 : 0 animations:^(void) {
        _gridController.view.frame = self.view.bounds;
        CGRect newPagingFrame = [self frameForPagingScrollView];
        newPagingFrame = CGRectOffset(newPagingFrame, 0, (self.startOnGrid ? 1 : -1) * newPagingFrame.size.height);
        _pagingScrollView.frame = newPagingFrame;
    } completion:^(BOOL finished) {
        [_gridController didMoveToParentViewController:self];
    }];
    
}

- (void)hideGrid {
    
    if (!_gridController) return;
    
    /***** BEGIN THREEMA MODIFICATION: add select button *********/
    self.navigationItem.rightBarButtonItem = nil;
    /***** END THREEMA MODIFICATION: add select button *********/
    
    // Restore action button if it was removed
    if (_gridPreviousRightNavItem == _actionButton && _actionButton) {
        [self.navigationItem setRightBarButtonItem:_gridPreviousRightNavItem animated:YES];
    }
    
    // Position prior to hide animation
    CGRect newPagingFrame = [self frameForPagingScrollView];
    newPagingFrame = CGRectOffset(newPagingFrame, 0, (self.startOnGrid ? 1 : -1) * newPagingFrame.size.height);
    _pagingScrollView.frame = newPagingFrame;
    
    // Remember and remove controller now so things can detect a nil grid controller
    MWGridViewController *tmpGridController = _gridController;
    _gridController = nil;
    
    // Update
    [self updateNavigation];
    [self updateVisiblePageStates];
    
    // Animate, hide grid and show paging scroll view
    [UIView animateWithDuration:0.3 animations:^{
        tmpGridController.view.frame = CGRectOffset(self.view.bounds, 0, (self.startOnGrid ? -1 : 1) * self.view.bounds.size.height);
        _pagingScrollView.frame = [self frameForPagingScrollView];
    } completion:^(BOOL finished) {
        [tmpGridController willMoveToParentViewController:nil];
        [tmpGridController.view removeFromSuperview];
        [tmpGridController removeFromParentViewController];
        [self setControlsHidden:NO animated:YES permanent:NO]; // retrigger timer
    }];

}

#pragma mark - Control Hiding / Showing

// If permanent then we don't set timers to hide again
// Fades all controls on iOS 5 & 6, and iOS 7 controls slide and fade
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent {
    
    // Force visible
    if (![self numberOfPhotos] || _gridController || _alwaysShowControls)
        hidden = NO;
    
    /***** BEGIN THREEMA MODIFICATION: Hide toolbar only when it's a image *********/
    id currentFile = [self photoAtIndex:_currentPageIndex];
    if ([currentFile isKindOfClass:[MediaBrowserFile class]]) {
        MediaBrowserFile *mediaBrowserFile = (MediaBrowserFile *)currentFile;
        if (![mediaBrowserFile canHideToolBar]) {
            hidden = NO;
        }
    }
    /***** END THREEMA MODIFICATION: Hide toolbar only when it's a image *********/
    
    // Cancel any timers
    [self cancelControlHiding];
    
    // Animations & positions
    CGFloat animatonOffset = 20;
    CGFloat animationDuration = (animated ? 0.35 : 0);
    
    // Status bar
    if (!_leaveStatusBarAlone) {

        // Hide status bar
        _statusBarShouldBeHidden = hidden;

        [UIView animateWithDuration:animationDuration animations:^{
            [self setNeedsStatusBarAppearanceUpdate];
        }];
    }

    UIInterfaceOrientation orientation = [self currentInterfaceOrientation];

    // Toolbar, nav bar and captions
    // Pre-appear animation positions for sliding
    if ([self areControlsHidden] && !hidden && animated) {

        // Toolbar
        _toolbar.frame = CGRectOffset([self frameForToolbarAtOrientation:orientation], 0, animatonOffset);
        _gridToolbar.frame = CGRectOffset([self frameForToolbarAtOrientation:orientation], 0, animatonOffset);

        // Captions
        for (MWZoomingScrollView *page in _visiblePages) {
            if (page.captionView) {
                MWCaptionView *v = page.captionView;
                // Pass any index, all we're interested in is the Y
                CGRect captionFrame = [self frameForCaptionView:v atIndex:0];
                captionFrame.origin.x = v.frame.origin.x; // Reset X
                v.frame = CGRectOffset(captionFrame, 0, animatonOffset);
            }
        }
    }
    [UIView animateWithDuration:animationDuration animations:^(void) {
        
        CGFloat alpha = hidden ? 0 : 1;

        // Nav bar slides up on it's own on iOS 7+
        [self.navigationController.navigationBar setAlpha:alpha];
        
        // Toolbar
        _toolbar.frame = [self frameForToolbarAtOrientation:orientation];
        _gridToolbar.frame = [self frameForToolbarAtOrientation:orientation];
        if (hidden) _toolbar.frame = CGRectOffset(_toolbar.frame, 0, animatonOffset);
        _toolbar.alpha = alpha;

        // Captions
        for (MWZoomingScrollView *page in _visiblePages) {
            if (page.captionView) {
                MWCaptionView *v = page.captionView;
                // Pass any index, all we're interested in is the Y
                CGRect captionFrame = [self frameForCaptionView:v atIndex:0];
                captionFrame.origin.x = v.frame.origin.x; // Reset X
                if (hidden) captionFrame = CGRectOffset(captionFrame, 0, animatonOffset);
                v.frame = captionFrame;
                v.alpha = alpha;
            }
        }
        
        // Selected buttons
        for (MWZoomingScrollView *page in _visiblePages) {
            if (page.selectedButton) {
                UIButton *v = page.selectedButton;
                CGRect newFrame = [self frameForSelectedButton:v atIndex:0];
                newFrame.origin.x = v.frame.origin.x;
                v.frame = newFrame;
            }
        }

    } completion:^(BOOL finished) {}];
    
 // Control hiding timer
 // Will cancel existing timer but only begin hiding if
 // they are visible
 if (!permanent) [self hideControlsAfterDelay];
 
}

- (BOOL)prefersStatusBarHidden {
    if (!_leaveStatusBarAlone) {
        return _statusBarShouldBeHidden;
    } else {
        return [self presentingViewControllerPrefersStatusBarHidden];
    }
}

- (UIStatusBarStyle)preferredStatusBarStyle {
    return _currentStatusBarStyle ?: UIStatusBarStyleDefault;
}

- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
    return UIStatusBarAnimationSlide;
}

- (void)cancelControlHiding {
 // If a timer exists then cancel and release
 if (_controlVisibilityTimer) {
  [_controlVisibilityTimer invalidate];
  _controlVisibilityTimer = nil;
 }
}

// Enable/disable control visibility timer
- (void)hideControlsAfterDelay {
 if (![self areControlsHidden]) {
        [self cancelControlHiding];
  _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:self.delayToHideElements target:self selector:@selector(hideControls) userInfo:nil repeats:NO];
 }
}

- (BOOL)areControlsHidden { return (_toolbar.alpha == 0); }
- (void)hideControls { [self setControlsHidden:YES animated:YES permanent:NO]; }
- (void)showControls { [self setControlsHidden:NO animated:YES permanent:NO]; }
- (void)toggleControls { [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO]; } 

#pragma mark - Properties

- (void)setCurrentPhotoIndex:(NSUInteger)index {
    // Validate
    NSUInteger photoCount = [self numberOfPhotos];
    if (photoCount == 0) {
        index = 0;
    } else {
        if (index >= photoCount)
            index = [self numberOfPhotos]-1;
    }
    _currentPageIndex = index;
 if ([self isViewLoaded]) {
        [self jumpToPageAtIndex:index animated:NO];
        if (!_viewIsActive)
            [self tilePages]; // Force tiling if view is not visible
    }
}

#pragma mark - Misc

- (void)doneButtonPressed:(id)sender {
    // Only if we're modal and there's a done button
    if (_doneButton) {
        // Dismiss view controller
        if ([_delegate respondsToSelector:@selector(photoBrowserDidFinishModalPresentation:)]) {
            // Call delegate method and let them dismiss us
            [_delegate photoBrowserDidFinishModalPresentation:self];
        } else  {
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
}

#pragma mark - Actions

- (void)actionButtonPressed:(id)sender {

    // Only react when image has loaded
    id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
    if ([self numberOfPhotos] > 0 && [photo underlyingImage]) {
        
        // If they have defined a delegate method then just message them
        if ([self.delegate respondsToSelector:@selector(photoBrowser:actionButtonPressedForPhotoAtIndex:)]) {
            // Let delegate handle things
            [self.delegate photoBrowser:self actionButtonPressedForPhotoAtIndex:_currentPageIndex];
        } else {
            // Share current photo
            [self shareMedia:photo];
        }
        
        // Keep controls hidden
        [self setControlsHidden:NO animated:YES permanent:YES];
    }
    
}

/***** BEGIN THREEMA MODIFICATION: add delete button *********/
- (void)deleteButtonPressed:(id)sender {
    if ([self numberOfPhotos] > 0) {
        if ([self.delegate respondsToSelector:@selector(photoBrowser:deleteButton:pressedForPhotoAtIndex:)]) {
            [self.delegate photoBrowser:self deleteButton:_deleteSingleButton pressedForPhotoAtIndex:_currentPageIndex];
        }
    }
}

- (void)selectButtonPressed:(id)sender {
    if (!self.displaySelectionButtons) {
        self.displaySelectionButtons = YES;
        self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[BundleUtil localizedStringForKey:@"mwphotobrowser_select_all"] style:UIBarButtonItemStylePlain target:self action:@selector(selectAllButtonPressed:)];
        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"cancel", nil) style:UIBarButtonItemStylePlain target:self action:@selector(selectButtonPressed:)];
        _deleteMultipleButton.enabled = false;
        _actionMultipleButton.enabled = false;
        [self.view addSubview:_gridToolbar];
    } else {
        self.displaySelectionButtons = NO;
        
        self.navigationItem.leftBarButtonItem = _doneButton;
        self.navigationItem.rightBarButtonItem = _selectButton;
        [_gridToolbar removeFromSuperview];
    }
    if ([self.delegate respondsToSelector:@selector(photoBrowserResetSelection:)]) {
        [self.delegate photoBrowserResetSelection:self];
    }

    [_gridController setSelectionMode:self.displaySelectionButtons];
    [[_gridController collectionView] reloadData];
}

- (void)selectAllButtonPressed:(id)sender {
    if ([self.delegate respondsToSelector:@selector(photoBrowserSelectAll:)]) {
        [self.delegate photoBrowserSelectAll:self];
    }
    [_gridController setSelectionMode:self.displaySelectionButtons];
    [[_gridController collectionView] reloadData];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[BundleUtil localizedStringForKey:@"mwphotobrowser_deselect_all"] style:UIBarButtonItemStylePlain target:self action:@selector(deselectAllButtonPressed:)];
    _deleteMultipleButton.enabled = true;
    _actionMultipleButton.enabled = true;
}

- (void)deselectAllButtonPressed:(id)sender {
    if ([self.delegate respondsToSelector:@selector(photoBrowserResetSelection:)]) {
        [self.delegate photoBrowserResetSelection:self];
    }
    [_gridController setSelectionMode:self.displaySelectionButtons];
    [[_gridController collectionView] reloadData];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[BundleUtil localizedStringForKey:@"mwphotobrowser_select_all"] style:UIBarButtonItemStylePlain target:self action:@selector(selectAllButtonPressed:)];
    _deleteMultipleButton.enabled = false;
    _actionMultipleButton.enabled = false;
}

- (void)deleteMultipleButtonPressed:(id)sender {
    if ([self numberOfPhotos] > 0) {
        NSString *actionTitle;
        NSUInteger selectionCount = [self.delegate mediaSelectionCount];
        if (selectionCount == 0) {
            /* clear all */
            actionTitle = NSLocalizedString(@"media_delete_all_confirm", nil);
        } else {
            actionTitle = NSLocalizedString(@"media_delete_selected_confirm", nil);
        }
        UIAlertController *deleteActionSheet = [UIAlertController alertControllerWithTitle:actionTitle message:nil preferredStyle:UIAlertControllerStyleAlert];
        [deleteActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
            [self showProgressHUDWithMessage:[NSString stringWithFormat:@"%@...", NSLocalizedString(@"delete", "")]];
            if ([self.delegate respondsToSelector:@selector(photoBrowser:deleteButton:)]) {
                [self.delegate photoBrowser:self deleteButton:_deleteMultipleButton];
                
                [self reloadData:YES];
                [self updateNavigation];
            }
        }]];
        [deleteActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
        deleteActionSheet.popoverPresentationController.barButtonItem = self.navigationItem.leftBarButtonItem;
        [self presentViewController:deleteActionSheet animated:YES completion:nil];
    }
}

- (void)actionMultipleButtonPressed:(id)sender {    
    MDMSetup *mdmSetup = [MDMSetup new];
    if ([mdmSetup disableShareMedia] == true) {
        // do nothing
    } else {
        NSSet *selectedPhotosIndexes = [self.delegate mediaPhotoSelection];
        
        NSMutableArray *allSelectedPhotos = [NSMutableArray new];
        [selectedPhotosIndexes enumerateObjectsUsingBlock:^(NSNumber *index, BOOL * _Nonnull stop) {
            MWPhoto *photo = [self photoAtIndex:[index unsignedIntegerValue]];
            NSURL *photoUrl = [photo urlForExportData:[index stringValue]];
            if (photoUrl != nil) {
                [allSelectedPhotos addObject:photoUrl];
            }
        }];
        
        if (allSelectedPhotos.count == 0) {
            return;
        }

        ForwardURLsUIActivity *forwardUrlActivity = [UIActivityHelperFactory makeForwardURLsUIActivity];
        self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:allSelectedPhotos applicationActivities:@[forwardUrlActivity]];
        
        // Show
        typeof(self) __weak weakSelf = self;
        // iOS 8 - Set the Anchor Point for the popover
        self.activityViewController.popoverPresentationController.barButtonItem = _actionButton;
        
        NSUserDefaults *defaults = [AppGroup userDefaults];
        [defaults setDouble:[ThreemaUtilityObjC systemUptime] forKey:@"UIActivityViewControllerOpenTime"];
        [defaults synchronize];
        
        [self.activityViewController setCompletionWithItemsHandler:^(UIActivityType  _Nullable activityType, BOOL completed, NSArray * _Nullable returnedItems, NSError * _Nullable activityError) {
            
            weakSelf.activityViewController = nil;
            [weakSelf hideControlsAfterDelay];
            [weakSelf hideProgressHUD:YES];
            
            NSUserDefaults *defaults = [AppGroup userDefaults];
            [defaults removeObjectForKey:@"UIActivityViewControllerOpenTime"];
        }];
        
        UIViewController *fakeVC=[[UIViewController alloc] init];
        [self presentViewController:self.activityViewController animated:YES completion:nil];
    }
}

- (void)finishedDeleteMedia {
    [self selectButtonPressed:nil];
    [self hideProgressHUD:true];
    [self reloadData:false];
}
/***** END THREEMA MODIFICATION: add delete button *********/

#pragma mark - Action Progress

- (MBProgressHUD *)progressHUD {
    if (!_progressHUD) {
        _progressHUD = [[MBProgressHUD alloc] initWithView:self.view];
        _progressHUD.minSize = CGSizeMake(120, 120);
        _progressHUD.minShowTime = 1;
        [self.view addSubview:_progressHUD];
    }
    return _progressHUD;
}

- (void)showProgressHUDWithMessage:(NSString *)message {
    self.progressHUD.label.text = message;
    self.progressHUD.mode = MBProgressHUDModeIndeterminate;
    [self.progressHUD showAnimated:YES];
    self.navigationController.navigationBar.userInteractionEnabled = NO;
}

- (void)hideProgressHUD:(BOOL)animated {
    [self.progressHUD hideAnimated:animated];
    self.navigationController.navigationBar.userInteractionEnabled = YES;
}

- (void)showProgressHUDCompleteMessage:(NSString *)message {
    if (message) {
        if (self.progressHUD.isHidden) [self.progressHUD showAnimated:YES];
        self.progressHUD.label.text = message;
        self.progressHUD.mode = MBProgressHUDModeCustomView;
        [self.progressHUD hideAnimated:YES afterDelay:1.5];
    } else {
        [self.progressHUD hideAnimated:YES];
    }
    self.navigationController.navigationBar.userInteractionEnabled = YES;
}

/***** BEGIN THREEMA MODIFICATION: add peeking *********/
- (BOOL)peeking {
    return _peeking;
}

- (void)setPeeking:(BOOL)peeking {
    _peeking = peeking;
    [self setControlsHidden:peeking animated:!peeking permanent:NO];
}
/***** END THREEMA MODIFICATION: add peeking *********/


///***** BEGIN THREEMA MODIFICATION: ignore mute switch *********/
#pragma mark - AVPlayerViewControllerDelegate

- (void)playerViewControllerWillBeginDismissalTransition:(AVPlayerViewController *)playerViewController {
    NSInteger state = [[VoIPCallStateManager shared] currentCallState];
    if (state == CallStateIdle && !NavigationBarPromptHandler.isGroupCallActive) {
        [[AVAudioSession sharedInstance] setCategory:_prevAudioCategory error:nil];
    }
}

///***** END THREEMA MODIFICATION: ignore mute switch *********/

- (void)shareMedia:(MWPhoto *)item {
    
    /***** BEGIN THREEMA MODIFICATION: use UIActivityHelperFactory, MDM share restriction *********/

    MDMSetup *mdmSetup = [MDMSetup new];
    if ([mdmSetup disableShareMedia] == true) {
        ModalNavigationController *navigationController = [ContactGroupPickerViewController pickerFromStoryboardWithDelegate:self];
        ContactGroupPickerViewController *picker = (ContactGroupPickerViewController *)navigationController.topViewController;
        picker.enableMultiselection = true;
        picker.enableTextInput = true;
        picker.submitOnSelect = false;
        
        [self presentViewController:navigationController animated:YES completion:nil];
    } else {
        NSURL *photoUrl = [item urlForExportData:@"file"];
        if (photoUrl == nil) {
            [self setControlsHidden:NO animated:YES permanent:YES];
            return;
        }
        NSArray *items = [NSArray arrayWithObject: photoUrl];
        
        ForwardURLsUIActivity *forwardUrlActivity = [UIActivityHelperFactory makeForwardURLsUIActivity];
        self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:@[forwardUrlActivity]];
                        
        // Show
        typeof(self) __weak weakSelf = self;
        // iOS 8 - Set the Anchor Point for the popover
        self.activityViewController.popoverPresentationController.barButtonItem = _actionButton;
        
        NSUserDefaults *defaults = [AppGroup userDefaults];
        [defaults setDouble:[ThreemaUtilityObjC systemUptime] forKey:@"UIActivityViewControllerOpenTime"];
        [defaults synchronize];
        
        [self.activityViewController setCompletionWithItemsHandler:^(UIActivityType  _Nullable activityType, BOOL completed, NSArray * _Nullable returnedItems, NSError * _Nullable activityError) {
            
            weakSelf.activityViewController = nil;
            [weakSelf hideControlsAfterDelay];
            [weakSelf hideProgressHUD:YES];
            
            NSUserDefaults *defaults = [AppGroup userDefaults];
            [defaults removeObjectForKey:@"UIActivityViewControllerOpenTime"];
        }];
        
        UIViewController *fakeVC=[[UIViewController alloc] init];
        [self presentViewController:self.activityViewController animated:YES completion:nil];
    }
    
    ///***** END THREEMA MODIFICATION: use UIActivityHelperFactory, MDM share restriction *********/
}

- (void)showAlert:(NSString *)title message:(NSString *)message {
    ///***** BEGIN THREEMA MODIFICATION: Use own AlertController *********/
    [UIAlertTemplate showOKAlertWithOwner:self title:title message:message actionOk:nil];
    ///***** END THREEMA MODIFICATION: Use own AlertController *********/
}

///***** BEGIN THREEMA MODIFICATION: ContactGroupPickerDelegate *********/
#pragma mark - Contact picker delegate

- (void)contactPicker:(ContactGroupPickerViewController*)contactPicker didPickConversations:(NSSet *)conversations renderType:(NSNumber *)renderType sendAsFile:(BOOL)sendAsFile {
    id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
    if ([self numberOfPhotos] > 0 && [photo underlyingImage]) {
        NSString *filename = [[FileUtility new] getTemporarySendableFileNameWithBase:@"image"];
        NSURL *photoUrl = [photo urlForExportData:filename];
        
        for (ConversationEntity *conversation in conversations) {
            [URLSender sendURL:photoUrl asFile:sendAsFile caption:contactPicker.additionalTextToSend conversation:conversation];
        }
        [contactPicker dismissViewControllerAnimated:YES completion:nil];
    } else {
        [contactPicker dismissViewControllerAnimated:YES completion:nil];
    }
}

- (void)contactPickerDidCancel:(ContactGroupPickerViewController*)contactPicker {
    [contactPicker dismissViewControllerAnimated:YES completion:nil];
}
///***** END THREEMA MODIFICATION: ContactGroupPickerDelegate *********/

///***** BEGIN THREEMA MODIFICATION: ModalNavigationControllerDelegate *********/

#pragma mark - ModalNavigationControllerDelegate

- (void)didDismissModalNavigationController {
    // No op
}

///***** END THREEMA MODIFICATION: ModalNavigationControllerDelegate *********/

#pragma mark - Helpers

- (UIInterfaceOrientation)currentInterfaceOrientation {
    if (self.view.window) {
        return [[[[self.view window] windowScene] effectiveGeometry] interfaceOrientation] ?: UIInterfaceOrientationUnknown;
    } else {
        UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
        if (window.windowScene) {
            return window.windowScene.effectiveGeometry.interfaceOrientation;
        }
        return UIInterfaceOrientationUnknown;
    }
}

@end

[Dauer der Verarbeitung: 0.22 Sekunden, vorverarbeitet 2026-11-04]