2017-04-05 20:56:22 +00:00
// === === === === === === === === === === === === === === === === === === === === === === === === === ===
// This file is part of Master Password .
// Copyright ( c ) 2011 -2017 , Maarten Billemont .
2014-03-20 00:09:25 +00:00
//
2017-04-05 20:56:22 +00:00
// Master Password is free software : you can redistribute it and / or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation , either version 3 of the License , or
// ( at your option ) any later version .
2014-03-20 00:09:25 +00:00
//
2017-04-05 20:56:22 +00:00
// Master Password is distributed in the hope that it will be useful ,
// but WITHOUT ANY WARRANTY ; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
// GNU General Public License for more details .
2014-03-20 00:09:25 +00:00
//
2017-04-05 20:56:22 +00:00
// You can find a copy of the GNU General Public License in the
// LICENSE file . Alternatively , see < http : // www . gnu . org / licenses / > .
// === === === === === === === === === === === === === === === === === === === === === === === === === ===
2014-03-20 00:09:25 +00:00
2020-09-03 13:53:08 +00:00
MP_LIBS _BEGIN
2020-02-27 04:01:44 +00:00
# import < StoreKit / StoreKit . h >
2020-09-03 13:53:08 +00:00
MP_LIBS _END
2020-02-27 04:01:44 +00:00
2017-05-07 22:36:01 +00:00
# import "MPSitesViewController.h"
2014-03-20 00:09:25 +00:00
# import "MPiOSAppDelegate.h"
# import "MPAppDelegate_Store.h"
2014-04-20 15:09:49 +00:00
# import "MPPopdownSegue.h"
2014-04-22 03:35:29 +00:00
# import "MPAppDelegate_Key.h"
2017-05-07 22:36:01 +00:00
# import "MPSiteCell.h"
2014-09-22 02:45:21 +00:00
# import "MPAnswersViewController.h"
2015-02-28 15:01:41 +00:00
# import "MPMessageViewController.h"
2014-03-20 00:09:25 +00:00
2017-04-27 06:20:49 +00:00
static const NSString * MPTransientPasswordItem = @ "MPTransientPasswordItem" ;
2014-09-29 02:15:55 +00:00
typedef NS_OPTIONS ( NSUInteger , MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 < < 0 ,
} ;
2020-04-25 21:10:20 +00:00
@ interface MPSitesViewController ( ) < NSFetchedResultsControllerDelegate >
2014-03-20 00:09:25 +00:00
2017-05-07 22:36:01 +00:00
@ property ( nonatomic , strong ) NSFetchedResultsController * fetchedResultsController ;
2020-05-23 16:14:22 +00:00
@ property ( nonatomic , strong ) NSArray * queryGroups ;
2017-05-07 22:36:01 +00:00
@ property ( nonatomic , strong ) NSCharacterSet * siteNameAcceptableCharactersSet ;
@ property ( nonatomic , strong ) NSMutableArray < NSMutableArray * > * dataSource ;
@ property ( nonatomic , weak ) UIViewController * popdownVC ;
2014-04-07 03:34:18 +00:00
2014-03-20 00:09:25 +00:00
@ end
2017-05-07 22:36:01 +00:00
@ implementation MPSitesViewController
2014-03-20 00:09:25 +00:00
2014-04-20 15:09:49 +00:00
# pragma mark - Life
2014-03-20 00:09:25 +00:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
2014-09-29 02:15:55 +00:00
NSMutableCharacterSet * siteNameAcceptableCharactersSet = [ [ NSCharacterSet alphanumericCharacterSet ] mutableCopy ] ;
[ siteNameAcceptableCharactersSet formIntersectionWithCharacterSet : [ [ NSCharacterSet uppercaseLetterCharacterSet ] invertedSet ] ] ;
[ siteNameAcceptableCharactersSet addCharactersInString : @ "@.-+~&_;:/" ] ;
2017-05-07 22:36:01 +00:00
self . siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet ;
2014-09-29 02:15:55 +00:00
2017-05-07 22:36:01 +00:00
self . dataSource = [ NSMutableArray new ] ;
2014-04-07 03:34:18 +00:00
2014-03-20 00:09:25 +00:00
self . view . backgroundColor = [ UIColor clearColor ] ;
2020-01-27 18:27:10 +00:00
if ( @ available ( iOS 11 , * ) )
self . collectionView . contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever ;
2017-05-07 22:36:01 +00:00
[ self . collectionView automaticallyAdjustInsetsForKeyboard ] ;
self . searchBar . autocapitalizationType = UITextAutocapitalizationTypeNone ;
2020-04-16 21:13:08 +00:00
self . searchBar . keyboardAppearance = UIKeyboardAppearanceDark ;
2014-03-20 00:09:25 +00:00
}
- ( void ) viewWillAppear : ( BOOL ) animated {
[ super viewWillAppear : animated ] ;
[ self registerObservers ] ;
2014-06-06 01:43:06 +00:00
[ self updateConfigKey : nil ] ;
2017-05-01 22:40:51 +00:00
static NSRegularExpression * bareHostRE = nil ;
static dispatch_once _t once = 0 ;
dispatch_once ( & once , ^ {
bareHostRE = [ NSRegularExpression regularExpressionWithPattern : @ "([^\\.]+\\.[^\\.]+)$" options : 0 error : nil ] ;
} ) ;
NSURL * pasteboardURL = nil ;
UIPasteboard * pasteboard = [ UIPasteboard generalPasteboard ] ;
2018-02-11 04:29:55 +00:00
if ( @ available ( iOS 10.0 , * ) )
2017-05-01 22:40:51 +00:00
pasteboardURL = pasteboard . hasURLs ? pasteboard . URL : nil ;
else
pasteboardURL = [ NSURL URLWithString : pasteboard . string ] ;
if ( pasteboardURL . host )
2017-06-04 13:46:29 +00:00
self . query = NSNullToNil ( [ [ pasteboardURL . host firstMatchGroupsOfExpression : bareHostRE ] firstObject ] ) ;
2017-05-01 22:40:51 +00:00
else
2017-05-07 22:36:01 +00:00
[ self reloadSites ] ;
2014-03-20 00:09:25 +00:00
}
2014-09-24 11:58:23 +00:00
- ( void ) viewDidAppear : ( BOOL ) animated {
[ super viewDidAppear : animated ] ;
[ MPiOSAppDelegate managedObjectContextPerformBlock : ^ ( NSManagedObjectContext * context ) {
MPUserEntity * activeUser = [ [ MPiOSAppDelegate get ] activeUserInContext : context ] ;
if ( ! [ MPAlgorithmDefault tryMigrateUser : activeUser inContext : context ] )
2017-04-01 04:30:25 +00:00
PearlMainQueue ( ^ {
2016-07-21 13:59:37 +00:00
[ self performSegueWithIdentifier : @ "message" sender :
[ MPMessage messageWithTitle : @ "You have sites that can be upgraded." text :
@ "Upgrading a site allows it to take advantage of the latest improvements in the Master Password algorithm.\n\n"
"When you upgrade a site, a new and stronger password will be generated for it. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any \" current password \ " fields on the website first, then press the upgrade button here to get your new site password.\n\n"
"You can then update your site's account with the new and stronger password.\n\n"
"The upgrade button can be found in the site's settings and looks like this:"
info : YES ] ] ;
2017-04-01 04:30:25 +00:00
} ) ;
2014-09-24 11:58:23 +00:00
[ context saveToStore ] ;
} ] ;
}
2014-03-20 00:09:25 +00:00
- ( void ) viewWillDisappear : ( BOOL ) animated {
[ super viewWillDisappear : animated ] ;
2014-09-27 05:27:05 +00:00
PearlRemoveNotificationObservers ( ) ;
2014-03-20 00:09:25 +00:00
}
2020-05-23 03:04:36 +00:00
- ( void ) viewDidLayoutSubviews {
2020-04-11 16:10:42 +00:00
2020-05-23 03:04:36 +00:00
[ super viewDidLayoutSubviews ] ;
2020-04-11 16:10:42 +00:00
2020-05-23 03:04:36 +00:00
if ( @ available ( iOS 11 , * ) ) {
self . collectionView . layoutMargins =
UIEdgeInsetsMake ( [ self . collectionView occludedInsets ] . top - self . view . safeAreaInsets . top , 0 , 0 , 0 ) ;
}
2020-04-11 16:10:42 +00:00
}
2014-04-20 15:09:49 +00:00
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender {
if ( [ segue . identifier isEqualToString : @ "popdown" ] )
2017-05-07 22:36:01 +00:00
self . popdownVC = segue . destinationViewController ;
2014-09-22 02:45:21 +00:00
if ( [ segue . identifier isEqualToString : @ "answers" ] )
( ( MPAnswersViewController * ) segue . destinationViewController ) . site =
2017-05-07 22:36:01 +00:00
[ [ MPSiteCell findAsSuperviewOf : sender ] siteInContext : [ MPiOSAppDelegate managedObjectContextForMainThreadIfReady ] ] ;
2015-02-28 15:01:41 +00:00
if ( [ segue . identifier isEqualToString : @ "message" ] )
( ( MPMessageViewController * ) segue . destinationViewController ) . message = sender ;
2014-04-20 15:09:49 +00:00
}
2017-03-11 16:20:15 +00:00
- ( void ) viewWillTransitionToSize : ( CGSize ) size withTransitionCoordinator : ( id < UIViewControllerTransitionCoordinator > ) coordinator {
2014-05-10 13:18:46 +00:00
2017-05-07 22:36:01 +00:00
[ self . collectionView . collectionViewLayout invalidateLayout ] ;
2017-03-11 16:20:15 +00:00
[ super viewWillTransitionToSize : size withTransitionCoordinator : coordinator ] ;
2014-05-10 13:18:46 +00:00
}
2014-03-20 00:09:25 +00:00
# pragma mark - UICollectionViewDataSource
2014-03-20 11:15:37 +00:00
- ( NSInteger ) numberOfSectionsInCollectionView : ( UICollectionView * ) collectionView {
2017-05-07 22:36:01 +00:00
return [ self . dataSource count ] ;
2014-03-20 11:15:37 +00:00
}
2014-03-20 00:09:25 +00:00
- ( NSInteger ) collectionView : ( UICollectionView * ) collectionView numberOfItemsInSection : ( NSInteger ) section {
2017-05-07 22:36:01 +00:00
return [ self . dataSource [ ( NSUInteger ) section ] count ] ;
2014-03-20 00:09:25 +00:00
}
- ( UICollectionViewCell * ) collectionView : ( UICollectionView * ) collectionView cellForItemAtIndexPath : ( NSIndexPath * ) indexPath {
2018-10-15 22:09:46 +00:00
MPSiteCell * cell = [ MPSiteCell dequeueFromCollectionView : collectionView indexPath : indexPath ] ;
2020-05-23 16:14:22 +00:00
[ cell setQueryGroups : self . queryGroups ] ;
2017-05-07 22:36:01 +00:00
id item = self . dataSource [ ( NSUInteger ) indexPath . section ] [ ( NSUInteger ) indexPath . item ] ;
2017-04-27 06:20:49 +00:00
if ( [ item isKindOfClass : [ MPSiteEntity class ] ] )
[ cell setSite : item animated : NO ] ;
else // item = = MPTransientPasswordItem
2014-07-21 03:54:32 +00:00
[ cell setTransientSite : self . query animated : NO ] ;
2014-03-20 00:09:25 +00:00
2014-07-21 03:54:32 +00:00
return cell ;
2014-03-20 00:09:25 +00:00
}
2020-05-23 23:08:43 +00:00
# pragma mark - UICollectionViewDelegateFlowLayout
- ( CGSize ) collectionView : ( UICollectionView * ) collectionView layout : ( UICollectionViewLayout * ) collectionViewLayout
sizeForItemAtIndexPath : ( NSIndexPath * ) indexPath {
UICollectionViewFlowLayout * layout = ( UICollectionViewFlowLayout * ) collectionViewLayout ;
CGFloat availableWidth = collectionView . bounds . size . width
- collectionView . layoutMargins . left - collectionView . layoutMargins . right
- layout . sectionInset . left - layout . sectionInset . right ;
CGFloat cells = MAX ( 1 , ( int ) ( ( availableWidth + layout . minimumInteritemSpacing ) / ( 318 + layout . minimumInteritemSpacing ) ) ) ;
return CGSizeMake ( ( availableWidth - layout . minimumInteritemSpacing * ( cells - 1 ) ) / cells , 100 ) ;
}
2014-07-21 03:54:32 +00:00
# pragma mark - UIScrollDelegate
- ( void ) scrollViewWillBeginDragging : ( UIScrollView * ) scrollView {
2017-05-07 22:36:01 +00:00
if ( scrollView = = self . collectionView )
for ( MPSiteCell * cell in [ self . collectionView visibleCells ] )
2014-07-21 03:54:32 +00:00
[ cell setMode : MPPasswordCellModePassword animated : YES ] ;
}
2014-05-13 11:27:11 +00:00
# pragma mark - NSFetchedResultsControllerDelegate
2017-04-27 06:20:49 +00:00
- ( void ) controllerDidChangeContent : ( NSFetchedResultsController * ) controller {
2014-04-07 03:34:18 +00:00
2017-05-07 22:36:01 +00:00
if ( controller = = self . fetchedResultsController )
2020-09-03 13:49:09 +00:00
[ self updateSites ] ;
2014-03-20 00:09:25 +00:00
}
2014-03-20 11:15:37 +00:00
# pragma mark - UISearchBarDelegate
2014-04-13 17:04:18 +00:00
- ( BOOL ) searchBarShouldBeginEditing : ( UISearchBar * ) searchBar {
2017-05-07 22:36:01 +00:00
if ( searchBar = = self . searchBar ) {
2014-04-13 17:04:18 +00:00
searchBar . text = nil ;
return YES ;
}
return NO ;
}
2014-03-20 11:15:37 +00:00
- ( void ) searchBarTextDidBeginEditing : ( UISearchBar * ) searchBar {
2017-05-07 22:36:01 +00:00
if ( searchBar = = self . searchBar ) {
[ self . searchBar setShowsCancelButton : YES animated : YES ] ;
2014-04-07 03:34:18 +00:00
[ UIView animateWithDuration : 0.3 f animations : ^ {
2017-05-07 22:36:01 +00:00
self . collectionView . backgroundColor = [ self . collectionView . backgroundColor colorWithAlphaComponent : 0.6 f ] ;
2014-04-07 03:34:18 +00:00
} ] ;
2014-06-15 23:51:03 +00:00
}
2014-03-20 11:15:37 +00:00
}
- ( void ) searchBarTextDidEndEditing : ( UISearchBar * ) searchBar {
2017-05-07 22:36:01 +00:00
if ( searchBar = = self . searchBar ) {
[ self . searchBar setShowsCancelButton : NO animated : YES ] ;
[ self reloadSites ] ;
2014-03-20 11:15:37 +00:00
[ UIView animateWithDuration : 0.3 f animations : ^ {
2017-05-07 22:36:01 +00:00
self . collectionView . backgroundColor = [ self . collectionView . backgroundColor colorWithAlphaComponent : 0 ] ;
2014-03-20 11:15:37 +00:00
} ] ;
}
}
2014-06-15 23:51:03 +00:00
- ( void ) searchBarCancelButtonClicked : ( UISearchBar * ) searchBar {
2014-09-11 04:26:01 +00:00
searchBar . text = nil ;
2014-06-15 23:51:03 +00:00
[ searchBar resignFirstResponder ] ;
}
2014-03-20 20:49:33 +00:00
- ( void ) searchBarSearchButtonClicked : ( UISearchBar * ) searchBar {
[ searchBar resignFirstResponder ] ;
}
2014-03-20 11:15:37 +00:00
- ( void ) searchBar : ( UISearchBar * ) searchBar textDidChange : ( NSString * ) searchText {
2017-05-07 22:36:01 +00:00
if ( searchBar = = self . searchBar ) {
if ( [ [ self . query stringByTrimmingCharactersInSet : self . siteNameAcceptableCharactersSet ] length ] )
2014-09-29 02:15:55 +00:00
[ self showTips : MPPasswordsBadNameTip ] ;
2014-10-23 01:17:02 +00:00
2017-05-07 22:36:01 +00:00
[ self reloadSites ] ;
2014-09-29 02:15:55 +00:00
}
2014-03-20 11:15:37 +00:00
}
2014-03-20 00:09:25 +00:00
# pragma mark - Private
2014-09-29 02:15:55 +00:00
- ( void ) showTips : ( MPPasswordsTips ) showTips {
[ UIView animateWithDuration : 0.3 f animations : ^ {
if ( showTips & MPPasswordsBadNameTip )
2017-04-21 02:29:10 +00:00
self . badNameTipContainer . visible = YES ;
2014-10-23 01:17:02 +00:00
} completion : ^ ( BOOL finished ) {
2017-04-21 02:29:10 +00:00
PearlMainQueueAfter ( 5 , ^ {
[ UIView animateWithDuration : 0.3 f animations : ^ {
if ( showTips & MPPasswordsBadNameTip )
self . badNameTipContainer . visible = NO ;
} ] ;
} ) ;
2014-09-29 02:15:55 +00:00
} ] ;
}
2017-05-28 19:32:50 +00:00
- ( NSMutableArray < NSMutableArray * > * ) createDataSource {
2014-08-22 01:51:47 +00:00
NSString * query = self . query ;
2017-04-27 06:20:49 +00:00
BOOL needTransientItem = [ query length ] > 0 ;
NSArray < id < NSFetchedResultsSectionInfo > > * sectionInfos = [ self . fetchedResultsController sections ] ;
NSMutableArray * sections = [ [ NSMutableArray alloc ] initWithCapacity : [ sectionInfos count ] ] ;
for ( id < NSFetchedResultsSectionInfo > sectionInfo in sectionInfos ) {
NSArray < MPSiteEntity * > * sites = [ sectionInfo . objects copy ] ;
[ sections addObject : sites ] ;
if ( needTransientItem )
for ( MPSiteEntity * site in sites )
if ( [ site . name isEqualToString : query ] ) {
needTransientItem = NO ;
break ;
}
2014-08-22 01:51:47 +00:00
}
2017-04-27 06:20:49 +00:00
if ( needTransientItem )
[ sections addObject : @ [ MPTransientPasswordItem ] ] ;
return sections ;
2014-08-22 01:51:47 +00:00
}
2014-03-20 00:09:25 +00:00
- ( void ) registerObservers {
2014-09-27 05:27:05 +00:00
PearlRemoveNotificationObservers ( ) ;
2017-04-29 21:50:48 +00:00
PearlAddNotificationObserver ( UIApplicationWillResignActiveNotification , nil , [ NSOperationQueue mainQueue ] ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2017-04-30 22:54:07 +00:00
[ self . view endEditing : YES ] ;
2017-05-07 22:36:01 +00:00
self . view . visible = NO ;
2014-09-27 05:27:05 +00:00
} ) ;
PearlAddNotificationObserver ( UIApplicationDidBecomeActiveNotification , nil , [ NSOperationQueue mainQueue ] ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2014-09-27 05:27:05 +00:00
[ UIView animateWithDuration : 0.7 f animations : ^ {
2017-05-07 22:36:01 +00:00
self . view . visible = YES ;
2014-09-27 05:27:05 +00:00
} ] ;
} ) ;
2017-05-01 22:40:51 +00:00
PearlAddNotificationObserver ( UIApplicationWillEnterForegroundNotification , nil , [ NSOperationQueue mainQueue ] ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2017-05-01 22:40:51 +00:00
[ self viewWillAppear : YES ] ;
} ) ;
2014-10-28 04:53:16 +00:00
PearlAddNotificationObserver ( MPSignedOutNotification , nil , nil ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2014-10-28 04:53:16 +00:00
PearlMainQueue ( ^ {
2017-05-07 22:36:01 +00:00
self . fetchedResultsController = nil ;
2017-04-29 21:50:48 +00:00
self . query = nil ;
2014-10-28 04:53:16 +00:00
} ) ;
2014-09-27 05:27:05 +00:00
} ) ;
2014-10-28 04:53:16 +00:00
PearlAddNotificationObserver ( MPCheckConfigNotification , nil , nil ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2014-10-28 04:53:16 +00:00
PearlMainQueue ( ^ {
[ self updateConfigKey : note . object ] ;
} ) ;
2014-09-27 05:27:05 +00:00
} ) ;
PearlAddNotificationObserver ( NSPersistentStoreCoordinatorStoresWillChangeNotification , nil , nil ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
self . fetchedResultsController = nil ;
[ self reloadSites ] ;
2014-09-27 05:27:05 +00:00
} ) ;
PearlAddNotificationObserver ( NSPersistentStoreCoordinatorStoresDidChangeNotification , nil , nil ,
2017-05-07 22:36:01 +00:00
^ ( MPSitesViewController * self , NSNotification * note ) {
2014-10-28 04:53:16 +00:00
PearlMainQueue ( ^ {
2017-05-07 22:36:01 +00:00
[ self reloadSites ] ;
2014-10-28 04:53:16 +00:00
[ self registerObservers ] ;
} ) ;
2014-09-27 05:27:05 +00:00
} ) ;
2014-03-20 00:09:25 +00:00
2018-02-11 04:29:55 +00:00
[ [ MPiOSAppDelegate get ] managedObjectContextChanged : ^ ( NSDictionary < NSManagedObjectID * , NSString * > * affectedObjects ) {
2017-04-29 21:50:48 +00:00
[ MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock : ^ ( NSManagedObjectContext * mainContext ) {
// TODO : either move this into the app delegate or remove the duplicate signOutAnimated : call from the app delegate .
if ( ! [ [ MPiOSAppDelegate get ] activeUserInContext : mainContext ] )
2020-04-04 20:30:14 +00:00
[ [ MPiOSAppDelegate get ] signOut ] ;
2017-04-29 21:50:48 +00:00
} ] ;
} ] ;
2014-03-20 00:09:25 +00:00
}
2014-06-06 01:43:06 +00:00
- ( void ) updateConfigKey : ( NSString * ) key {
2014-04-26 18:03:44 +00:00
2014-07-21 03:54:32 +00:00
if ( ! key || [ key isEqualToString : NSStringFromSelector ( @ selector ( dictationSearch ) ) ] )
2017-05-07 22:36:01 +00:00
self . searchBar . keyboardType = [ [ MPiOSConfig get ] . dictationSearch boolValue ] ? UIKeyboardTypeDefault : UIKeyboardTypeURL ;
2014-07-21 03:54:32 +00:00
if ( ! key || [ key isEqualToString : NSStringFromSelector ( @ selector ( hidePasswords ) ) ] )
2017-05-07 22:36:01 +00:00
[ self . collectionView reloadData ] ;
2014-04-26 18:03:44 +00:00
}
2017-05-07 22:36:01 +00:00
- ( void ) reloadSites {
2014-03-20 11:15:37 +00:00
2017-04-27 06:20:49 +00:00
[ self . fetchedResultsController . managedObjectContext performBlock : ^ {
NSString * queryString = self . query ;
2020-05-23 16:14:22 +00:00
NSMutableArray * queryGroups = [ NSMutableArray new ] ;
NSMutableString * queryPattern = [ NSMutableString new ] ;
[ queryString enumerateSubstringsInRange : NSMakeRange ( 0 , [ queryString length ] ) options : NSStringEnumerationByComposedCharacterSequences
usingBlock : ^ ( NSString * substring , NSRange substringRange , NSRange enclosingRange , BOOL * stop ) {
2020-07-12 01:43:34 +00:00
if ( substringRange . location < 20 ) {
2020-05-23 16:14:22 +00:00
[ queryGroups addObject : substring ] ;
[ queryPattern appendString : @ "*" ] ;
}
[ queryPattern appendString : substring ] ;
} ] ;
[ queryPattern appendString : @ "*" ] ;
self . queryGroups = queryGroups ;
2014-08-22 01:51:47 +00:00
2014-03-20 11:15:37 +00:00
NSError * error = nil ;
2014-04-12 18:43:41 +00:00
self . fetchedResultsController . fetchRequest . predicate =
2017-04-27 06:20:49 +00:00
[ NSPredicate predicateWithFormat : @ "name LIKE[cd] %@ AND user == %@" , queryPattern , [ MPiOSAppDelegate get ] . activeUserOID ] ;
2020-09-03 13:49:09 +00:00
if ( ! [ self . fetchedResultsController performFetch : & error ] || error )
2017-04-30 03:03:50 +00:00
MPError ( error , @ "Couldn't fetch sites." ) ;
2014-03-20 11:15:37 +00:00
2020-09-03 13:49:09 +00:00
[ self updateSites ] ;
2014-03-20 11:15:37 +00:00
} ] ;
}
2020-09-03 13:49:09 +00:00
- ( void ) updateSites {
PearlMainQueue ( ^ {
[ self . collectionView updateDataSource : self . dataSource
toSections : [ self createDataSource ]
reloadItems : @ [ MPTransientPasswordItem ] completion : ^ ( BOOL finished ) {
for ( MPSiteCell * cell in self . collectionView . visibleCells )
[ cell setQueryGroups : self . queryGroups ] ;
} ] ;
} ) ;
}
2014-03-20 00:09:25 +00:00
# pragma mark - Properties
2014-03-20 11:15:37 +00:00
- ( NSString * ) query {
2017-05-07 22:36:01 +00:00
return [ self . searchBar . text stringByTrimmingCharactersInSet : [ NSCharacterSet whitespaceCharacterSet ] ] ;
2014-03-20 11:15:37 +00:00
}
2017-04-29 21:50:48 +00:00
- ( void ) setQuery : ( NSString * ) query {
2017-05-07 22:36:01 +00:00
self . searchBar . text = [ query stringByTrimmingCharactersInSet : [ NSCharacterSet whitespaceCharacterSet ] ] ;
[ self reloadSites ] ;
2017-04-29 21:50:48 +00:00
}
2014-03-20 11:15:37 +00:00
- ( NSFetchedResultsController * ) fetchedResultsController {
2017-05-28 19:32:50 +00:00
if ( ! _fetchedResultsController ) {
2014-04-07 03:34:18 +00:00
[ MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait : ^ ( NSManagedObjectContext * mainContext ) {
2020-09-03 13:49:09 +00:00
NSFetchRequest * fetchRequest = [ MPSiteEntity fetchRequest ] ;
2014-03-20 11:15:37 +00:00
fetchRequest . sortDescriptors = @ [
2014-07-21 03:54:32 +00:00
[ [ NSSortDescriptor alloc ] initWithKey : NSStringFromSelector ( @ selector ( lastUsed ) ) ascending : NO ]
2014-03-20 11:15:37 +00:00
] ;
2020-09-03 18:31:10 +00:00
fetchRequest . predicate = [ NSPredicate predicateWithFormat : @ "user == %@" , [ MPiOSAppDelegate get ] . activeUserOID ] ;
2020-09-03 13:49:09 +00:00
2017-05-28 19:32:50 +00:00
( self . fetchedResultsController = [ [ NSFetchedResultsController alloc ]
initWithFetchRequest : fetchRequest managedObjectContext : mainContext
sectionNameKeyPath : nil cacheName : nil ] ) . delegate = self ;
2020-09-03 13:49:09 +00:00
NSError * error = nil ;
if ( ! [ self . fetchedResultsController performFetch : & error ] || error )
MPError ( error , @ "Couldn't fetch sites." ) ;
[ self updateSites ] ;
2014-03-20 11:15:37 +00:00
} ] ;
2014-09-27 05:27:05 +00:00
[ self registerObservers ] ;
2014-04-07 03:34:18 +00:00
}
2014-03-20 11:15:37 +00:00
2017-05-28 19:32:50 +00:00
return _fetchedResultsController ;
2014-03-20 11:15:37 +00:00
}
2014-03-20 00:09:25 +00:00
- ( void ) setActive : ( BOOL ) active {
2014-04-12 18:43:41 +00:00
[ self setActive : active animated : NO completion : nil ] ;
2014-03-20 00:09:25 +00:00
}
2014-07-21 03:54:32 +00:00
- ( void ) setActive : ( BOOL ) active animated : ( BOOL ) animated completion : ( void ( ^ ) ( BOOL finished ) ) completion {
2014-03-20 00:09:25 +00:00
_active = active ;
2014-04-12 18:43:41 +00:00
[ UIView animateWithDuration : animated ? 0.4 f : 0 animations : ^ {
2020-01-14 18:59:32 +00:00
[ self . navigationBarToTopConstraint withPriority : active ? 1 : UILayoutPriorityDefaultHigh ] ;
[ self . sitesToBottomConstraint withPriority : active ? 1 : UILayoutPriorityDefaultHigh ] ;
2014-08-22 02:27:47 +00:00
[ self . view layoutIfNeeded ] ;
2014-04-12 18:43:41 +00:00
} completion : completion ] ;
2014-03-20 00:09:25 +00:00
}
# pragma mark - Actions
2014-04-20 15:09:49 +00:00
- ( IBAction ) dismissPopdown : ( id ) sender {
2014-04-12 18:43:41 +00:00
2017-05-07 22:36:01 +00:00
if ( self . popdownVC )
[ [ [ MPPopdownSegue alloc ] initWithIdentifier : @ "unwind-popdown" source : self . popdownVC destination : self ] perform ] ;
2014-04-20 15:09:49 +00:00
else
self . popdownToTopConstraint . priority = UILayoutPriorityDefaultHigh ;
2014-04-12 18:43:41 +00:00
}
2014-03-20 00:09:25 +00:00
@ end