Initial rewrite/redesign of compose screen
Still pending: - Posting videos - Image resizing - Attachment drawer thing
This commit is contained in:
parent
5b3a3d9729
commit
b8430be00c
Binary file not shown.
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// GMAlbumsViewCell.h
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 22/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <Photos/Photos.h>
|
||||
|
||||
@interface GMAlbumsViewCell : UITableViewCell
|
||||
|
||||
@property (strong) PHFetchResult *assetsFetchResults;
|
||||
@property (strong) PHAssetCollection *assetCollection;
|
||||
|
||||
//The labels
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UILabel *infoLabel;
|
||||
//The imageView
|
||||
@property (nonatomic, strong) UIImageView *imageView1;
|
||||
@property (nonatomic, strong) UIImageView *imageView2;
|
||||
@property (nonatomic, strong) UIImageView *imageView3;
|
||||
//Video additional information
|
||||
@property (nonatomic, strong) UIImageView *videoIcon;
|
||||
@property (nonatomic, strong) UIImageView *slowMoIcon;
|
||||
@property (nonatomic, strong) UIView *gradientView;
|
||||
@property (nonatomic, strong) CAGradientLayer *gradient;
|
||||
//Selection overlay
|
||||
|
||||
- (void)setVideoLayout:(BOOL)isVideo;
|
||||
@end
|
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// GMAlbumsViewCell.m
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 22/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import "GMAlbumsViewCell.h"
|
||||
#import "GMAlbumsViewController.h"
|
||||
#import "GMImagePickerController.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@implementation GMAlbumsViewCell
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
}
|
||||
|
||||
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
||||
{
|
||||
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
|
||||
{
|
||||
// self.isAccessibilityElement = YES;
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
|
||||
// Border width of 1 pixel:
|
||||
float borderWidth = 1.0/[UIScreen mainScreen].scale;
|
||||
|
||||
// ImageView
|
||||
_imageView3 = [UIImageView new];
|
||||
_imageView3.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
|
||||
[_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||
[_imageView3.layer setBorderWidth: borderWidth];
|
||||
_imageView3.clipsToBounds = YES;
|
||||
_imageView3.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
_imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.contentView addSubview:_imageView3];
|
||||
|
||||
// ImageView
|
||||
_imageView2 = [UIImageView new];
|
||||
_imageView2.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
|
||||
[_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||
[_imageView2.layer setBorderWidth: borderWidth];
|
||||
_imageView2.clipsToBounds = YES;
|
||||
_imageView2.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
_imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.contentView addSubview:_imageView2];
|
||||
|
||||
// ImageView
|
||||
_imageView1 = [UIImageView new];
|
||||
_imageView1.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
|
||||
[_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||
[_imageView1.layer setBorderWidth: borderWidth];
|
||||
_imageView1.clipsToBounds = YES;
|
||||
_imageView1.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
_imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.contentView addSubview:_imageView1];
|
||||
|
||||
|
||||
// The video gradient, label & icon
|
||||
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
|
||||
UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
|
||||
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
|
||||
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
|
||||
_gradient = [CAGradientLayer layer];
|
||||
_gradient.frame = _gradientView.bounds;
|
||||
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
|
||||
_gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
|
||||
[_gradientView.layer insertSublayer:_gradient atIndex:0];
|
||||
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||
_gradientView.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
[self.imageView1 addSubview:_gradientView];
|
||||
_gradientView.hidden = YES;
|
||||
|
||||
// VideoIcon
|
||||
_videoIcon = [UIImageView new];
|
||||
_videoIcon.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
|
||||
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMAlbumsViewCell.class] compatibleWithTraitCollection:nil];
|
||||
_videoIcon.clipsToBounds = YES;
|
||||
_videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.imageView1 addSubview:_videoIcon];
|
||||
_videoIcon.hidden = NO;
|
||||
|
||||
// TextLabel
|
||||
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
|
||||
self.textLabel.numberOfLines = 1;
|
||||
|
||||
self.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
|
||||
self.detailTextLabel.numberOfLines = 1;
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.textLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.textLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8, self.textLabel.frame.size.height);
|
||||
self.detailTextLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.detailTextLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8 - kAlbumImageToTextSpace, self.detailTextLabel.frame.size.height);
|
||||
|
||||
}
|
||||
- (void)setVideoLayout:(BOOL)isVideo
|
||||
{
|
||||
// TODO : Add additional icons for slowmo, burst, etc...
|
||||
if (isVideo) {
|
||||
_videoIcon.hidden = NO;
|
||||
_gradientView.hidden = NO;
|
||||
} else {
|
||||
_videoIcon.hidden = YES;
|
||||
_gradientView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
|
||||
{
|
||||
[super setSelected:selected animated:animated];
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// GMAlbumsViewController.h
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// Measuring IOS8 Photos APP at @2x (iPhone5s):
|
||||
// The rows are 180px/90pts
|
||||
// Left image border is 21px/10.5pts
|
||||
// Separation between image and text is 42px/21pts (double the previouse one)
|
||||
// The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
|
||||
// The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
||||
// The third image measures 123px/61.5pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
||||
|
||||
static int kAlbumRowHeight = 90;
|
||||
static int kAlbumLeftToImageSpace = 10;
|
||||
static int kAlbumImageToTextSpace = 21;
|
||||
static float const kAlbumGradientHeight = 20.0f;
|
||||
static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
|
||||
static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
|
||||
static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
|
||||
|
||||
|
||||
@interface GMAlbumsViewController : UITableViewController
|
||||
|
||||
- (void)selectAllAlbumsCell;
|
||||
|
||||
@end
|
|
@ -0,0 +1,416 @@
|
|||
//
|
||||
// GMAlbumsViewController.m
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import "GMImagePickerController.h"
|
||||
#import "GMAlbumsViewController.h"
|
||||
#import "GMGridViewCell.h"
|
||||
#import "GMGridViewController.h"
|
||||
#import "GMAlbumsViewCell.h"
|
||||
|
||||
#include <Photos/Photos.h>
|
||||
|
||||
@interface GMAlbumsViewController() <PHPhotoLibraryChangeObserver>
|
||||
|
||||
@property (strong,nonatomic) NSArray *collectionsFetchResults;
|
||||
@property (strong,nonatomic) NSArray *collectionsLocalizedTitles;
|
||||
@property (strong,nonatomic) NSArray *collectionsFetchResultsAssets;
|
||||
@property (strong,nonatomic) NSArray *collectionsFetchResultsTitles;
|
||||
@property (nonatomic, weak) GMImagePickerController *picker;
|
||||
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation GMAlbumsViewController
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super initWithStyle:UITableViewStylePlain]) {
|
||||
self.preferredContentSize = kPopoverContentSize;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static NSString *const AllPhotosReuseIdentifier = @"AllPhotosCell";
|
||||
static NSString *const CollectionCellReuseIdentifier = @"CollectionCell";
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
||||
|
||||
// Navigation bar customization
|
||||
if (self.picker.customNavigationBarPrompt) {
|
||||
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
||||
}
|
||||
|
||||
self.imageManager = [[PHCachingImageManager alloc] init];
|
||||
|
||||
// Table view aspect
|
||||
self.tableView.rowHeight = kAlbumRowHeight;
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
|
||||
// Buttons
|
||||
NSDictionary *barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
||||
|
||||
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self.picker
|
||||
action:@selector(dismiss:)];
|
||||
if (self.picker.useCustomFontForNavigationBar) {
|
||||
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||
}
|
||||
|
||||
if (self.picker.allowsMultipleSelection) {
|
||||
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
||||
style:UIBarButtonItemStyleDone
|
||||
target:self.picker
|
||||
action:@selector(finishPickingAssets:)];
|
||||
if (self.picker.useCustomFontForNavigationBar) {
|
||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||
}
|
||||
|
||||
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
||||
}
|
||||
|
||||
// Bottom toolbar
|
||||
self.toolbarItems = self.picker.toolbarItems;
|
||||
|
||||
// Title
|
||||
if (!self.picker.title) {
|
||||
self.title = NSLocalizedStringFromTableInBundle(@"picker.navigation.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Navigation bar default title");
|
||||
} else {
|
||||
self.title = self.picker.title;
|
||||
}
|
||||
|
||||
// Fetch PHAssetCollections:
|
||||
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
|
||||
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
|
||||
self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
|
||||
self.collectionsLocalizedTitles = @[NSLocalizedStringFromTableInBundle(@"picker.table.smart-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Smart Albums"),NSLocalizedStringFromTableInBundle(@"picker.table.user-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Albums")];
|
||||
|
||||
[self updateFetchResults];
|
||||
|
||||
// Register for changes
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
||||
|
||||
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
||||
{
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
return self.picker.pickerStatusBarStyle;
|
||||
}
|
||||
|
||||
- (void)selectAllAlbumsCell {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
|
||||
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
-(void)updateFetchResults
|
||||
{
|
||||
//What I do here is fetch both the albums list and the assets of each album.
|
||||
//This way I have acces to the number of items in each album, I can load the 3
|
||||
//thumbnails directly and I can pass the fetched result to the gridViewController.
|
||||
|
||||
self.collectionsFetchResultsAssets=nil;
|
||||
self.collectionsFetchResultsTitles=nil;
|
||||
|
||||
//Fetch PHAssetCollections:
|
||||
PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
|
||||
PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
|
||||
|
||||
//All album: Sorted by descending creation date.
|
||||
NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
|
||||
{
|
||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
|
||||
[allFetchResultArray addObject:assetsFetchResult];
|
||||
[allFetchResultLabel addObject:NSLocalizedStringFromTableInBundle(@"picker.table.all-photos-label", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"All photos")];
|
||||
}
|
||||
|
||||
//User albums:
|
||||
NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
|
||||
for(PHCollection *collection in topLevelUserCollections)
|
||||
{
|
||||
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||||
{
|
||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||||
|
||||
//Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
|
||||
|
||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||||
[userFetchResultArray addObject:assetsFetchResult];
|
||||
[userFetchResultLabel addObject:collection.localizedTitle];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Smart albums: Sorted by descending creation date.
|
||||
NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
|
||||
for(PHCollection *collection in smartAlbums)
|
||||
{
|
||||
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||||
{
|
||||
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||||
|
||||
//Smart collections are PHAssetCollectionType=2;
|
||||
if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
|
||||
{
|
||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||||
|
||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||||
if(assetsFetchResult.count>0)
|
||||
{
|
||||
[smartFetchResultArray addObject:assetsFetchResult];
|
||||
[smartFetchResultLabel addObject:collection.localizedTitle];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.collectionsFetchResultsAssets= @[allFetchResultArray,smartFetchResultArray,userFetchResultArray];
|
||||
self.collectionsFetchResultsTitles= @[allFetchResultLabel,smartFetchResultLabel,userFetchResultLabel];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Accessors
|
||||
|
||||
- (GMImagePickerController *)picker
|
||||
{
|
||||
return (GMImagePickerController *)self.navigationController.parentViewController;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskAllButUpsideDown;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return (NSInteger)self.collectionsFetchResultsAssets.count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||||
return (NSInteger)fetchResult.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
static NSString *CellIdentifier = @"Cell";
|
||||
|
||||
GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||||
if (cell == nil) {
|
||||
cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
|
||||
// Increment the cell's tag
|
||||
NSInteger currentTag = cell.tag + 1;
|
||||
cell.tag = currentTag;
|
||||
|
||||
// Set the label
|
||||
cell.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize];
|
||||
cell.textLabel.text = (self.collectionsFetchResultsTitles[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||||
cell.textLabel.textColor = self.picker.pickerTextColor;
|
||||
|
||||
// Retrieve the pre-fetched assets for this album:
|
||||
PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||||
|
||||
// Display the number of assets
|
||||
if (self.picker.displayAlbumsNumberOfAssets) {
|
||||
cell.detailTextLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||||
cell.detailTextLabel.text = [self tableCellSubtitle:assetsFetchResult];
|
||||
cell.detailTextLabel.textColor = self.picker.pickerTextColor;
|
||||
}
|
||||
|
||||
// Set the 3 images (if exists):
|
||||
if ([assetsFetchResult count] > 0) {
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
|
||||
//Compute the thumbnail pixel size:
|
||||
CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
|
||||
PHAsset *asset = assetsFetchResult[0];
|
||||
[cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
|
||||
[self.imageManager requestImageForAsset:asset
|
||||
targetSize:tableCellThumbnailSize1
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (cell.tag == currentTag) {
|
||||
cell.imageView1.image = result;
|
||||
}
|
||||
}];
|
||||
|
||||
// Second & third images:
|
||||
// TODO: Only preload the 3pixels height visible frame!
|
||||
if ([assetsFetchResult count] > 1) {
|
||||
//Compute the thumbnail pixel size:
|
||||
CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
|
||||
PHAsset *asset = assetsFetchResult[1];
|
||||
[self.imageManager requestImageForAsset:asset
|
||||
targetSize:tableCellThumbnailSize2
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (cell.tag == currentTag) {
|
||||
cell.imageView2.image = result;
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
cell.imageView2.image = nil;
|
||||
}
|
||||
|
||||
if ([assetsFetchResult count] > 2) {
|
||||
CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
|
||||
PHAsset *asset = assetsFetchResult[2];
|
||||
[self.imageManager requestImageForAsset:asset
|
||||
targetSize:tableCellThumbnailSize3
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (cell.tag == currentTag) {
|
||||
cell.imageView3.image = result;
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
cell.imageView3.image = nil;
|
||||
}
|
||||
} else {
|
||||
[cell setVideoLayout:NO];
|
||||
cell.imageView3.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||
cell.imageView2.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||
cell.imageView1.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
|
||||
// Init the GMGridViewController
|
||||
GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
|
||||
// Set the title
|
||||
gridViewController.title = cell.textLabel.text;
|
||||
// Use the prefetched assets!
|
||||
gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:(NSUInteger)indexPath.section] objectAtIndex:(NSUInteger)indexPath.row];
|
||||
|
||||
// Remove selection so it looks better on slide in
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:true];
|
||||
|
||||
// Push GMGridViewController
|
||||
[self.navigationController pushViewController:gridViewController animated:YES];
|
||||
}
|
||||
|
||||
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
|
||||
{
|
||||
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
|
||||
// header.contentView.backgroundColor = [UIColor clearColor];
|
||||
// header.backgroundView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// Default is a bold font, but keep this styled as a normal font
|
||||
header.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||||
header.textLabel.textColor = self.picker.pickerTextColor;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
//Tip: Returning nil hides the section header!
|
||||
|
||||
NSString *title = nil;
|
||||
if (section > 0) {
|
||||
// Only show title for non-empty sections:
|
||||
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||||
if (fetchResult.count > 0) {
|
||||
title = self.collectionsLocalizedTitles[(NSUInteger)(section - 1)];
|
||||
}
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - PHPhotoLibraryChangeObserver
|
||||
|
||||
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
||||
{
|
||||
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
NSMutableArray *updatedCollectionsFetchResults = nil;
|
||||
|
||||
for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
|
||||
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
|
||||
if (changeDetails) {
|
||||
if (!updatedCollectionsFetchResults) {
|
||||
updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
|
||||
}
|
||||
[updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
|
||||
}
|
||||
}
|
||||
|
||||
// This only affects to changes in albums level (add/remove/edit album)
|
||||
if (updatedCollectionsFetchResults) {
|
||||
self.collectionsFetchResults = updatedCollectionsFetchResults;
|
||||
}
|
||||
|
||||
// However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
|
||||
// Maybe some checks could be done here , but for now is OKey.
|
||||
[self updateFetchResults];
|
||||
[self.tableView reloadData];
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Cell Subtitle
|
||||
|
||||
- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
|
||||
{
|
||||
// Just return the number of assets. Album app does this:
|
||||
return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
|
||||
}
|
||||
|
||||
@end
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// GMGridViewCell.h
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <Photos/Photos.h>
|
||||
|
||||
|
||||
@interface GMGridViewCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic, strong) PHAsset *asset;
|
||||
//The imageView
|
||||
@property (nonatomic, strong) UIImageView *imageView;
|
||||
//Video additional information
|
||||
@property (nonatomic, strong) UIImageView *videoIcon;
|
||||
@property (nonatomic, strong) UILabel *videoDuration;
|
||||
@property (nonatomic, strong) UIView *gradientView;
|
||||
@property (nonatomic, strong) CAGradientLayer *gradient;
|
||||
//Selection overlay
|
||||
@property (nonatomic) BOOL shouldShowSelection;
|
||||
@property (nonatomic, strong) UIView *coverView;
|
||||
@property (nonatomic, strong) UIButton *selectedButton;
|
||||
|
||||
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
|
||||
|
||||
- (void)bind:(PHAsset *)asset;
|
||||
|
||||
@end
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// GMGridViewCell.m
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import "GMGridViewCell.h"
|
||||
|
||||
|
||||
@interface GMGridViewCell ()
|
||||
@end
|
||||
|
||||
|
||||
@implementation GMGridViewCell
|
||||
|
||||
static UIFont *titleFont;
|
||||
static CGFloat titleHeight;
|
||||
static UIImage *videoIcon;
|
||||
static UIColor *titleColor;
|
||||
static UIImage *checkedIcon;
|
||||
static UIColor *selectedColor;
|
||||
static UIColor *disabledColor;
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
titleFont = [UIFont systemFontOfSize:12];
|
||||
titleHeight = 20.0f;
|
||||
videoIcon = [UIImage imageNamed:@"GMImagePickerVideo"];
|
||||
titleColor = [UIColor whiteColor];
|
||||
checkedIcon = [UIImage imageNamed:@"CTAssetsPickerChecked"];
|
||||
selectedColor = [UIColor colorWithWhite:1 alpha:0.3];
|
||||
disabledColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
|
||||
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.opaque = NO;
|
||||
self.enabled = YES;
|
||||
|
||||
CGFloat cellSize = self.contentView.bounds.size.width;
|
||||
|
||||
// The image view
|
||||
_imageView = [UIImageView new];
|
||||
_imageView.frame = CGRectMake(0, 0, cellSize, cellSize);
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
}*/
|
||||
_imageView.clipsToBounds = YES;
|
||||
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
[self addSubview:_imageView];
|
||||
|
||||
|
||||
// The video gradient, label & icon
|
||||
float x_offset = 4.0f;
|
||||
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
|
||||
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.8];
|
||||
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, self.bounds.size.height-titleHeight, self.bounds.size.width, titleHeight)];
|
||||
_gradient = [CAGradientLayer layer];
|
||||
_gradient.frame = _gradientView.bounds;
|
||||
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[botGradient CGColor], nil];
|
||||
[_gradientView.layer insertSublayer:_gradient atIndex:0];
|
||||
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||
_gradientView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_gradientView];
|
||||
_gradientView.hidden = YES;
|
||||
|
||||
_videoIcon = [UIImageView new];
|
||||
_videoIcon.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
|
||||
_videoIcon.contentMode = UIViewContentModeLeft;
|
||||
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil];
|
||||
_videoIcon.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
|
||||
[self addSubview:_videoIcon];
|
||||
_videoIcon.hidden = YES;
|
||||
|
||||
_videoDuration = [UILabel new];
|
||||
_videoDuration.font = titleFont;
|
||||
_videoDuration.textColor = titleColor;
|
||||
_videoDuration.textAlignment = NSTextAlignmentRight;
|
||||
_videoDuration.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
|
||||
_videoDuration.contentMode = UIViewContentModeRight;
|
||||
_videoDuration.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_videoDuration.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
|
||||
[self addSubview:_videoDuration];
|
||||
_videoDuration.hidden = YES;
|
||||
|
||||
// Selection overlay & icon
|
||||
_coverView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
_coverView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_coverView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
_coverView.backgroundColor = [UIColor colorWithRed:0.24 green:0.47 blue:0.85 alpha:0.6];
|
||||
[self addSubview:_coverView];
|
||||
_coverView.hidden = YES;
|
||||
|
||||
_selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_selectedButton.frame = CGRectMake(2*self.bounds.size.width/3, 0*self.bounds.size.width/3, self.bounds.size.width/3, self.bounds.size.width/3);
|
||||
_selectedButton.contentMode = UIViewContentModeTopRight;
|
||||
_selectedButton.adjustsImageWhenHighlighted = NO;
|
||||
[_selectedButton setImage:nil forState:UIControlStateNormal];
|
||||
_selectedButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_selectedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
[_selectedButton setImage:[UIImage imageNamed:@"GMSelected" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil] forState:UIControlStateSelected];
|
||||
_selectedButton.hidden = NO;
|
||||
_selectedButton.userInteractionEnabled = NO;
|
||||
[self addSubview:_selectedButton];
|
||||
}
|
||||
|
||||
// Note: the views above are created in case this is toggled per cell, on the fly, etc.!
|
||||
self.shouldShowSelection = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Required to resize the CAGradientLayer because it does not support auto resizing.
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_gradient.frame = _gradientView.bounds;
|
||||
}
|
||||
|
||||
- (void)bind:(PHAsset *)asset
|
||||
{
|
||||
self.asset = asset;
|
||||
|
||||
if (self.asset.mediaType == PHAssetMediaTypeVideo) {
|
||||
_videoIcon.hidden = NO;
|
||||
_videoDuration.hidden = NO;
|
||||
_gradientView.hidden = NO;
|
||||
_videoDuration.text = [self getDurationWithFormat:self.asset.duration];
|
||||
} else {
|
||||
_videoIcon.hidden = YES;
|
||||
_videoDuration.hidden = YES;
|
||||
_gradientView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Override setSelected
|
||||
- (void)setSelected:(BOOL)selected
|
||||
{
|
||||
[super setSelected:selected];
|
||||
|
||||
if (!self.shouldShowSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
_coverView.hidden = !selected;
|
||||
_selectedButton.selected = selected;
|
||||
}
|
||||
|
||||
-(NSString*)getDurationWithFormat:(NSTimeInterval)duration
|
||||
{
|
||||
NSInteger ti = (NSInteger)duration;
|
||||
NSInteger seconds = ti % 60;
|
||||
NSInteger minutes = (ti / 60) % 60;
|
||||
//NSInteger hours = (ti / 3600);
|
||||
return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// GMGridViewController.h
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "GMImagePickerController.h"
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <Photos/Photos.h>
|
||||
|
||||
|
||||
@interface GMGridViewController : UICollectionViewController
|
||||
|
||||
@property (strong,nonatomic) PHFetchResult *assetsFetchResults;
|
||||
|
||||
-(id)initWithPicker:(GMImagePickerController *)picker;
|
||||
|
||||
@end
|
|
@ -0,0 +1,611 @@
|
|||
//
|
||||
// GMGridViewController.m
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import "GMGridViewController.h"
|
||||
#import "GMImagePickerController.h"
|
||||
#import "GMAlbumsViewController.h"
|
||||
#import "GMGridViewCell.h"
|
||||
|
||||
#include <Photos/Photos.h>
|
||||
|
||||
|
||||
//Helper methods
|
||||
@implementation NSIndexSet (Convenience)
|
||||
- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
|
||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
|
||||
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForItem:(NSInteger)idx inSection:(NSInteger)section]];
|
||||
}];
|
||||
return indexPaths;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation UICollectionView (Convenience)
|
||||
- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
|
||||
NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
|
||||
if (allLayoutAttributes.count == 0) { return nil; }
|
||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
|
||||
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
|
||||
NSIndexPath *indexPath = layoutAttributes.indexPath;
|
||||
[indexPaths addObject:indexPath];
|
||||
}
|
||||
return indexPaths;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface GMImagePickerController ()
|
||||
|
||||
- (void)finishPickingAssets:(id)sender;
|
||||
- (void)dismiss:(id)sender;
|
||||
- (NSString *)toolbarTitle;
|
||||
- (UIView *)noAssetsView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface GMGridViewController () <PHPhotoLibraryChangeObserver>
|
||||
|
||||
@property (nonatomic, weak) GMImagePickerController *picker;
|
||||
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
||||
@property (assign, nonatomic) CGRect previousPreheatRect;
|
||||
|
||||
@end
|
||||
|
||||
static CGSize AssetGridThumbnailSize;
|
||||
NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
|
||||
|
||||
@implementation GMGridViewController
|
||||
{
|
||||
CGFloat screenWidth;
|
||||
CGFloat screenHeight;
|
||||
UICollectionViewFlowLayout *portraitLayout;
|
||||
UICollectionViewFlowLayout *landscapeLayout;
|
||||
}
|
||||
|
||||
-(id)initWithPicker:(GMImagePickerController *)picker
|
||||
{
|
||||
//Custom init. The picker contains custom information to create the FlowLayout
|
||||
self.picker = picker;
|
||||
|
||||
//Ipad popover is not affected by rotation!
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
screenWidth = CGRectGetWidth(picker.view.bounds);
|
||||
screenHeight = CGRectGetHeight(picker.view.bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
|
||||
{
|
||||
screenHeight = CGRectGetWidth(picker.view.bounds);
|
||||
screenWidth = CGRectGetHeight(picker.view.bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
screenWidth = CGRectGetWidth(picker.view.bounds);
|
||||
screenHeight = CGRectGetHeight(picker.view.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
|
||||
if (self = [super initWithCollectionViewLayout:layout])
|
||||
{
|
||||
//Compute the thumbnail pixel size:
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
//NSLog(@"This is @%fx scale device", scale);
|
||||
if(scale >= 3)
|
||||
{
|
||||
scale = 2;
|
||||
}
|
||||
|
||||
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
||||
|
||||
self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection;
|
||||
|
||||
[self.collectionView registerClass:GMGridViewCell.class
|
||||
forCellWithReuseIdentifier:GMGridViewCellIdentifier];
|
||||
|
||||
self.preferredContentSize = kPopoverContentSize;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
[self setupViews];
|
||||
|
||||
// Navigation bar customization
|
||||
if (self.picker.customNavigationBarPrompt) {
|
||||
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
||||
}
|
||||
|
||||
self.imageManager = [[PHCachingImageManager alloc] init];
|
||||
[self resetCachedAssets];
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
||||
|
||||
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
||||
{
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self setupButtons];
|
||||
[self setupToolbar];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
[self updateCachedAssets];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self resetCachedAssets];
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
return self.picker.pickerStatusBarStyle;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
||||
{
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
return;
|
||||
}
|
||||
|
||||
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
|
||||
|
||||
//Update the AssetGridThumbnailSize:
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
||||
|
||||
[self resetCachedAssets];
|
||||
//This is optional. Reload visible thumbnails:
|
||||
for (GMGridViewCell *cell in [self.collectionView visibleCells]) {
|
||||
NSInteger currentTag = cell.tag;
|
||||
[self.imageManager requestImageForAsset:cell.asset
|
||||
targetSize:AssetGridThumbnailSize
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info)
|
||||
{
|
||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||
if (cell.tag == currentTag) {
|
||||
[cell.imageView setImage:result];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
[self.collectionView setCollectionViewLayout:layout animated:YES];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
- (void)setupViews
|
||||
{
|
||||
self.collectionView.backgroundColor = [UIColor clearColor];
|
||||
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
||||
}
|
||||
|
||||
- (void)setupButtons
|
||||
{
|
||||
if (self.picker.allowsMultipleSelection) {
|
||||
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
||||
style:UIBarButtonItemStyleDone
|
||||
target:self.picker
|
||||
action:@selector(finishPickingAssets:)];
|
||||
|
||||
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
||||
} else {
|
||||
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
||||
style:UIBarButtonItemStyleDone
|
||||
target:self.picker
|
||||
action:@selector(dismiss:)];
|
||||
}
|
||||
if (self.picker.useCustomFontForNavigationBar) {
|
||||
if (self.picker.useCustomFontForNavigationBar) {
|
||||
NSDictionary* barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)setupToolbar
|
||||
{
|
||||
self.toolbarItems = self.picker.toolbarItems;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Collection View Layout
|
||||
|
||||
- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
|
||||
{
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
if(!portraitLayout)
|
||||
{
|
||||
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing);
|
||||
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
||||
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
||||
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
||||
portraitLayout.minimumLineSpacing = spaceWidth;
|
||||
}
|
||||
return portraitLayout;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(UIInterfaceOrientationIsLandscape(orientation))
|
||||
{
|
||||
if(!landscapeLayout)
|
||||
{
|
||||
landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||
int cellTotalUsableWidth = (int)(screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing);
|
||||
landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
|
||||
double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
|
||||
double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
|
||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
|
||||
landscapeLayout.minimumLineSpacing = spaceWidth;
|
||||
}
|
||||
return landscapeLayout;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!portraitLayout)
|
||||
{
|
||||
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1) * self.picker.minimumInteritemSpacing);
|
||||
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
||||
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
||||
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
||||
portraitLayout.minimumLineSpacing = spaceWidth;
|
||||
}
|
||||
return portraitLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Collection View Data Source
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
|
||||
forIndexPath:indexPath];
|
||||
|
||||
// Increment the cell's tag
|
||||
NSInteger currentTag = cell.tag + 1;
|
||||
cell.tag = currentTag;
|
||||
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
NSLog(@"Image manager: Requesting FIT image for iPad");
|
||||
[self.imageManager requestImageForAsset:asset
|
||||
targetSize:AssetGridThumbnailSize
|
||||
contentMode:PHImageContentModeAspectFit
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
|
||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||
if (cell.tag == currentTag) {
|
||||
[cell.imageView setImage:result];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else*/
|
||||
{
|
||||
//NSLog(@"Image manager: Requesting FILL image for iPhone");
|
||||
[self.imageManager requestImageForAsset:asset
|
||||
targetSize:AssetGridThumbnailSize
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
|
||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||
if (cell.tag == currentTag) {
|
||||
[cell.imageView setImage:result];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
[cell bind:asset];
|
||||
|
||||
cell.shouldShowSelection = self.picker.allowsMultipleSelection;
|
||||
|
||||
// Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)]) {
|
||||
cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
|
||||
} else {
|
||||
cell.enabled = YES;
|
||||
}
|
||||
|
||||
// Setting `selected` property blocks further deselection. Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
|
||||
if ([self.picker.selectedAssets containsObject:asset]) {
|
||||
cell.selected = YES;
|
||||
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
||||
} else {
|
||||
cell.selected = NO;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Collection View Delegate
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
||||
|
||||
if (!cell.isEnabled) {
|
||||
return NO;
|
||||
} else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)]) {
|
||||
return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
[self.picker selectAsset:asset];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)]) {
|
||||
[self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) {
|
||||
return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
[self.picker deselectAsset:asset];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)]) {
|
||||
[self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) {
|
||||
return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) {
|
||||
[self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
|
||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) {
|
||||
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
NSInteger count = (NSInteger)self.assetsFetchResults.count;
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - PHPhotoLibraryChangeObserver
|
||||
|
||||
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
||||
{
|
||||
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// check if there are changes to the assets (insertions, deletions, updates)
|
||||
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
|
||||
if (collectionChanges) {
|
||||
|
||||
// get the new fetch result
|
||||
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
|
||||
|
||||
UICollectionView *collectionView = self.collectionView;
|
||||
|
||||
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
|
||||
// we need to reload all if the incremental diffs are not available
|
||||
[collectionView reloadData];
|
||||
|
||||
} else {
|
||||
// if we have incremental diffs, tell the collection view to animate insertions and deletions
|
||||
[collectionView performBatchUpdates:^{
|
||||
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
|
||||
if ([removedIndexes count]) {
|
||||
[collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||
}
|
||||
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
|
||||
if ([insertedIndexes count]) {
|
||||
[collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||
if (self.picker.showCameraButton && self.picker.autoSelectCameraImages) {
|
||||
for (NSIndexPath *path in [insertedIndexes aapl_indexPathsFromIndexesWithSection:0]) {
|
||||
[self collectionView:collectionView didSelectItemAtIndexPath:path];
|
||||
}
|
||||
}
|
||||
}
|
||||
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
|
||||
if ([changedIndexes count]) {
|
||||
[collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||
}
|
||||
} completion:NULL];
|
||||
}
|
||||
|
||||
[self resetCachedAssets];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
[self updateCachedAssets];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Asset Caching
|
||||
|
||||
- (void)resetCachedAssets
|
||||
{
|
||||
[self.imageManager stopCachingImagesForAllAssets];
|
||||
self.previousPreheatRect = CGRectZero;
|
||||
}
|
||||
|
||||
- (void)updateCachedAssets
|
||||
{
|
||||
BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
|
||||
if (!isViewVisible) { return; }
|
||||
|
||||
// The preheat window is twice the height of the visible rect
|
||||
CGRect preheatRect = self.collectionView.bounds;
|
||||
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
|
||||
|
||||
// If scrolled by a "reasonable" amount...
|
||||
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
|
||||
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
|
||||
|
||||
// Compute the assets to start caching and to stop caching.
|
||||
NSMutableArray *addedIndexPaths = [NSMutableArray array];
|
||||
NSMutableArray *removedIndexPaths = [NSMutableArray array];
|
||||
|
||||
[self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
|
||||
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
|
||||
[removedIndexPaths addObjectsFromArray:indexPaths];
|
||||
} addedHandler:^(CGRect addedRect) {
|
||||
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
|
||||
[addedIndexPaths addObjectsFromArray:indexPaths];
|
||||
}];
|
||||
|
||||
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
|
||||
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
|
||||
|
||||
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
|
||||
targetSize:AssetGridThumbnailSize
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil];
|
||||
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
|
||||
targetSize:AssetGridThumbnailSize
|
||||
contentMode:PHImageContentModeAspectFill
|
||||
options:nil];
|
||||
|
||||
self.previousPreheatRect = preheatRect;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
|
||||
{
|
||||
if (CGRectIntersectsRect(newRect, oldRect)) {
|
||||
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
|
||||
CGFloat oldMinY = CGRectGetMinY(oldRect);
|
||||
CGFloat newMaxY = CGRectGetMaxY(newRect);
|
||||
CGFloat newMinY = CGRectGetMinY(newRect);
|
||||
if (newMaxY > oldMaxY) {
|
||||
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
|
||||
addedHandler(rectToAdd);
|
||||
}
|
||||
if (oldMinY > newMinY) {
|
||||
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
|
||||
addedHandler(rectToAdd);
|
||||
}
|
||||
if (newMaxY < oldMaxY) {
|
||||
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
|
||||
removedHandler(rectToRemove);
|
||||
}
|
||||
if (oldMinY < newMinY) {
|
||||
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
|
||||
removedHandler(rectToRemove);
|
||||
}
|
||||
} else {
|
||||
addedHandler(newRect);
|
||||
removedHandler(oldRect);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
if (indexPaths.count == 0) { return nil; }
|
||||
|
||||
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||
[assets addObject:asset];
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
|
||||
@end
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// GMImagePicker.h
|
||||
// GMImagePicker
|
||||
//
|
||||
// Created by Shadowfacts on 1/14/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for GMImagePicker.
|
||||
FOUNDATION_EXPORT double GMImagePickerVersionNumber;
|
||||
|
||||
//! Project version string for GMImagePicker.
|
||||
FOUNDATION_EXPORT const unsigned char GMImagePickerVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <GMImagePicker/PublicHeader.h>
|
||||
|
||||
|
||||
#import <GMImagePicker/GMImagePickerController.h>
|
||||
#import <GMImagePicker/GMAlbumsViewCell.h>
|
||||
#import <GMImagePicker/GMAlbumsViewController.h>
|
||||
#import <GMImagePicker/GMGridViewCell.h>
|
||||
#import <GMImagePicker/GMGridViewController.h>
|
|
@ -0,0 +1,332 @@
|
|||
//
|
||||
// GMImagePickerController.h
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
|
||||
//This is the default image picker size!
|
||||
//static CGSize const kPopoverContentSize = {320, 480};
|
||||
//However, the iPad is 1024x768 so it can allow popups up to 768!
|
||||
static CGSize const kPopoverContentSize = {480, 720};
|
||||
|
||||
|
||||
@protocol GMImagePickerControllerDelegate;
|
||||
|
||||
|
||||
/**
|
||||
* A controller that allows picking multiple photos and videos from user's photo library.
|
||||
*/
|
||||
@interface GMImagePickerController : UIViewController
|
||||
|
||||
/**
|
||||
* The assets picker’s delegate object.
|
||||
*/
|
||||
@property (nonatomic, weak) id <GMImagePickerControllerDelegate> delegate;
|
||||
|
||||
/**
|
||||
* It contains the selected `PHAsset` objects. The order of the objects is the selection order.
|
||||
*
|
||||
* You can add assets before presenting the picker to show the user some preselected assets.
|
||||
*/
|
||||
@property (nonatomic, strong) NSMutableArray *selectedAssets;
|
||||
|
||||
|
||||
/** UI Customizations **/
|
||||
|
||||
/**
|
||||
* Determines which smart collections are displayed (int array of enum: PHAssetCollectionSubtypeSmartAlbum)
|
||||
* The default smart collections are:
|
||||
* - Favorites
|
||||
* - RecentlyAdded
|
||||
* - Videos
|
||||
* - SlomoVideos
|
||||
* - Timelapses
|
||||
* - Bursts
|
||||
* - Panoramas
|
||||
*/
|
||||
@property (nonatomic, strong) NSArray* customSmartCollections;
|
||||
|
||||
/**
|
||||
* Determines which media types are allowed (int array of enum: PHAssetMediaType)
|
||||
* This defaults to all media types (view, audio and images)
|
||||
* This can override customSmartCollections behavior (ie, remove video-only smart collections)
|
||||
*/
|
||||
@property (nonatomic, strong) NSArray* mediaTypes;
|
||||
|
||||
/**
|
||||
* If set, it displays a this string instead of the localised default of "Done" on the done button. Note also that this
|
||||
* is not used when a single selection is active since the selection of the chosen photo closes the VC thus rendering
|
||||
* the button pointless.
|
||||
*/
|
||||
@property (nonatomic) NSString* customDoneButtonTitle;
|
||||
|
||||
/**
|
||||
* If set, it displays this string instead of the localised default of "Cancel" on the cancel button
|
||||
*/
|
||||
@property (nonatomic) NSString* customCancelButtonTitle;
|
||||
|
||||
/**
|
||||
* If set, it displays a prompt in the navigation bar
|
||||
*/
|
||||
@property (nonatomic) NSString* customNavigationBarPrompt;
|
||||
|
||||
/**
|
||||
* Determines whether or not a toolbar with info about user selection is shown.
|
||||
* The InfoToolbar is visible by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL displaySelectionInfoToolbar;
|
||||
|
||||
/**
|
||||
* Determines whether or not the number of assets is shown in the Album list.
|
||||
* The number of assets is visible by default.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL displayAlbumsNumberOfAssets;
|
||||
|
||||
/**
|
||||
* Automatically disables the "Done" button if nothing is selected. Defaults to YES.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL autoDisableDoneButton;
|
||||
|
||||
/**
|
||||
* Use the picker either for miltiple image selections, or just a single selection. In the case of a single selection
|
||||
* the VC is closed on selection so the Done button is neither displayed or used. Default is YES.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelection;
|
||||
|
||||
/**
|
||||
* In the case where allowsMultipleSelection = NO, set this to YES to have the user confirm their selection. Default is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL confirmSingleSelection;
|
||||
|
||||
/**
|
||||
* If set, it displays this string (if confirmSingleSelection = YES) instead of the localised default.
|
||||
*/
|
||||
@property (nonatomic) NSString *confirmSingleSelectionPrompt;
|
||||
|
||||
/**
|
||||
* True to always show the toolbar, with a camera button allowing new photos to be taken. False to auto show/hide the
|
||||
* toolbar, and have no camera button. Default is false. If true, this renders displaySelectionInfoToolbar a no-op.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL showCameraButton;
|
||||
|
||||
/**
|
||||
* True to auto select the image(s) taken with the camera if showCameraButton = YES. In the case of allowsMultipleSelection = YES,
|
||||
* this will trigger the selection handler too.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL autoSelectCameraImages;
|
||||
|
||||
/**
|
||||
* If set, the user is allowed to edit captured still images
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL allowsEditingCameraImages;
|
||||
|
||||
/**
|
||||
* Grid customizations:
|
||||
*
|
||||
* - colsInPortrait: Number of columns in portrait (3 by default)
|
||||
* - colsInLandscape: Number of columns in landscape (5 by default)
|
||||
* - minimumInteritemSpacing: Horizontal and vertical minimum space between grid cells (2.0 by default)
|
||||
*/
|
||||
@property (nonatomic) NSInteger colsInPortrait;
|
||||
@property (nonatomic) NSInteger colsInLandscape;
|
||||
@property (nonatomic) double minimumInteritemSpacing;
|
||||
|
||||
/**
|
||||
* UI customizations:
|
||||
*
|
||||
* - pickerBackgroundColor: The colour for all backgrounds; behind the table and cells. Defaults to [UIColor whiteColor]
|
||||
* - pickerTextColor: The color for text in the views. This needs to work with pickerBackgroundColor! Default of darkTextColor
|
||||
* - toolbarBackgroundColor: The background color of the toolbar. Defaults to nil.
|
||||
* - toolbarBarTintColor: The color for the background tint of the toolbar. Defaults to nil.
|
||||
* - toolbarTextColor: The color of the text on the toolbar
|
||||
* - toolbarTintColor: The tint colour used for any buttons on the toolbar
|
||||
* - navigationBarBackgroundColor: The background of the navigation bar. Defaults to nil.
|
||||
* - navigationBarBarTintColor: The color for the background tint of the navigation bar. Defaults to nil.
|
||||
* - navigationBarTextColor: The color for the text in the navigation bar. Defaults to [UIColor darkTextColor]
|
||||
* - navigationBarTintColor: The tint color used for any buttons on the navigation Bar
|
||||
* - pickerFontName: The font to use everywhere. Defaults to HelveticaNeue. It is advised if you set this to check, and possibly set, appropriately the custom font sizes. For font information, check http://www.iosfonts.com/
|
||||
* - pickerFontName: The font to use everywhere. Defaults to HelveticaNeue-Bold. It is advised if you set this to check, and possibly set, appropriately the custom font sizes.
|
||||
* - pickerFontNormalSize: The size of the custom font used in most places. Defaults to 14.0f
|
||||
* - pickerFontHeaderSize: The size of the custom font for album names. Defaults to 17.0f
|
||||
* - pickerStatusBarsStyle: On iPhones this will matter if custom navigation bar colours are being used. Defaults to UIStatusBarStyleDefault
|
||||
* - useCustomFontForNavigationBar: True to use the custom font (or it's default) in the navigation bar, false to leave to iOS Defaults.
|
||||
* - arrangeSmartCollectionsFirst: True will put the users smart collections above their albums, false will set it opposite. Default is NO.
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *pickerBackgroundColor;
|
||||
@property (nonatomic, strong) UIColor *pickerTextColor;
|
||||
@property (nonatomic, strong) UIColor *toolbarBackgroundColor;
|
||||
@property (nonatomic, strong) UIColor *toolbarBarTintColor;
|
||||
@property (nonatomic, strong) UIColor *toolbarTextColor;
|
||||
@property (nonatomic, strong) UIColor *toolbarTintColor;
|
||||
@property (nonatomic, strong) UIColor *navigationBarBackgroundColor;
|
||||
@property (nonatomic, strong) UIColor *navigationBarBarTintColor;
|
||||
@property (nonatomic, strong) UIColor *navigationBarTextColor;
|
||||
@property (nonatomic, strong) UIColor *navigationBarTintColor;
|
||||
@property (nonatomic, strong) NSString *pickerFontName;
|
||||
@property (nonatomic, strong) NSString *pickerBoldFontName;
|
||||
@property (nonatomic) CGFloat pickerFontNormalSize;
|
||||
@property (nonatomic) CGFloat pickerFontHeaderSize;
|
||||
@property (nonatomic) UIStatusBarStyle pickerStatusBarStyle;
|
||||
@property (nonatomic) BOOL useCustomFontForNavigationBar;
|
||||
@property (nonatomic) BOOL arrangeSmartCollectionsFirst;
|
||||
|
||||
/**
|
||||
* A reference to the navigation controller used to manage the whole picking process
|
||||
*/
|
||||
@property (nonatomic, strong) UINavigationController *navigationController;
|
||||
|
||||
/**
|
||||
* Managing Asset Selection
|
||||
*/
|
||||
- (void)selectAsset:(PHAsset *)asset;
|
||||
- (void)deselectAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* User finish Actions
|
||||
*/
|
||||
- (void)dismiss:(id)sender;
|
||||
- (void)finishPickingAssets:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@protocol GMImagePickerControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* @name Closing the Picker
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tells the delegate that the user finish picking photos or videos.
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param assets An array containing picked PHAssets objects.
|
||||
*/
|
||||
|
||||
- (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets;
|
||||
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Tells the delegate that the user cancelled the pick operation.
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
*/
|
||||
- (void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker;
|
||||
|
||||
|
||||
/**
|
||||
* @name Enabling Assets
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ask the delegate if the specified asset should be shown.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset to be shown.
|
||||
*
|
||||
* @return `YES` if the asset should be shown or `NO` if it should not.
|
||||
*/
|
||||
|
||||
- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* Ask the delegate if the specified asset should be enabled for selection.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset to be enabled.
|
||||
*
|
||||
* @return `YES` if the asset should be enabled or `NO` if it should not.
|
||||
*/
|
||||
- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldEnableAsset:(PHAsset *)asset;
|
||||
|
||||
|
||||
/**
|
||||
* @name Managing the Selected Assets
|
||||
*/
|
||||
|
||||
/**
|
||||
* Asks the delegate if the specified asset should be selected.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset to be selected.
|
||||
*
|
||||
* @return `YES` if the asset should be selected or `NO` if it should not.
|
||||
*
|
||||
*/
|
||||
- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldSelectAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* Tells the delegate that the asset was selected.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset that was selected.
|
||||
*
|
||||
*/
|
||||
- (void)assetsPickerController:(GMImagePickerController *)picker didSelectAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* Asks the delegate if the specified asset should be deselected.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset to be deselected.
|
||||
*
|
||||
* @return `YES` if the asset should be deselected or `NO` if it should not.
|
||||
*
|
||||
*/
|
||||
- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldDeselectAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* Tells the delegate that the item at the specified path was deselected.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset that was deselected.
|
||||
*
|
||||
*/
|
||||
- (void)assetsPickerController:(GMImagePickerController *)picker didDeselectAsset:(PHAsset *)asset;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @name Managing Asset Highlighting
|
||||
*/
|
||||
|
||||
/**
|
||||
* Asks the delegate if the specified asset should be highlighted.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset to be highlighted.
|
||||
*
|
||||
* @return `YES` if the asset should be highlighted or `NO` if it should not.
|
||||
*/
|
||||
- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldHighlightAsset:(PHAsset *)asset;
|
||||
|
||||
/**
|
||||
* Tells the delegate that asset was highlighted.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset that was highlighted.
|
||||
*
|
||||
*/
|
||||
- (void)assetsPickerController:(GMImagePickerController *)picker didHighlightAsset:(PHAsset *)asset;
|
||||
|
||||
|
||||
/**
|
||||
* Tells the delegate that the highlight was removed from the asset.
|
||||
*
|
||||
* @param picker The controller object managing the assets picker interface.
|
||||
* @param asset The asset that had its highlight removed.
|
||||
*
|
||||
*/
|
||||
- (void)assetsPickerController:(GMImagePickerController *)picker didUnhighlightAsset:(PHAsset *)asset;
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
|
@ -0,0 +1,388 @@
|
|||
//
|
||||
// GMImagePickerController.m
|
||||
// GMPhotoPicker
|
||||
//
|
||||
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import "GMImagePickerController.h"
|
||||
#import "GMAlbumsViewController.h"
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
@interface GMImagePickerController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIAlertViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation GMImagePickerController
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_selectedAssets = [[NSMutableArray alloc] init];
|
||||
|
||||
// Default values:
|
||||
_displaySelectionInfoToolbar = YES;
|
||||
_displayAlbumsNumberOfAssets = YES;
|
||||
_autoDisableDoneButton = YES;
|
||||
_allowsMultipleSelection = YES;
|
||||
_confirmSingleSelection = NO;
|
||||
_showCameraButton = NO;
|
||||
|
||||
// Grid configuration:
|
||||
_colsInPortrait = 3;
|
||||
_colsInLandscape = 5;
|
||||
_minimumInteritemSpacing = 2.0;
|
||||
|
||||
// Sample of how to select the collections you want to display:
|
||||
_customSmartCollections = @[@(PHAssetCollectionSubtypeSmartAlbumFavorites),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumRecentlyAdded),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumVideos),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumSlomoVideos),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumTimelapses),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumBursts),
|
||||
@(PHAssetCollectionSubtypeSmartAlbumPanoramas)];
|
||||
// If you don't want to show smart collections, just put _customSmartCollections to nil;
|
||||
//_customSmartCollections=nil;
|
||||
|
||||
// Which media types will display
|
||||
_mediaTypes = @[@(PHAssetMediaTypeAudio),
|
||||
@(PHAssetMediaTypeVideo),
|
||||
@(PHAssetMediaTypeImage)];
|
||||
|
||||
self.preferredContentSize = kPopoverContentSize;
|
||||
|
||||
// UI Customisation
|
||||
_pickerBackgroundColor = [UIColor whiteColor];
|
||||
_pickerTextColor = [UIColor darkTextColor];
|
||||
_pickerFontName = @"HelveticaNeue";
|
||||
_pickerBoldFontName = @"HelveticaNeue-Bold";
|
||||
_pickerFontNormalSize = 14.0f;
|
||||
_pickerFontHeaderSize = 17.0f;
|
||||
|
||||
_navigationBarBackgroundColor = [UIColor whiteColor];
|
||||
_navigationBarTextColor = [UIColor darkTextColor];
|
||||
_navigationBarTintColor = [UIColor darkTextColor];
|
||||
|
||||
_toolbarBarTintColor = [UIColor whiteColor];
|
||||
_toolbarTextColor = [UIColor darkTextColor];
|
||||
_toolbarTintColor = [UIColor darkTextColor];
|
||||
|
||||
_pickerStatusBarStyle = UIStatusBarStyleDefault;
|
||||
|
||||
[self setupNavigationController];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// Ensure nav and toolbar customisations are set. Defaults are in place, but the user may have changed them
|
||||
self.view.backgroundColor = _pickerBackgroundColor;
|
||||
|
||||
_navigationController.toolbar.translucent = YES;
|
||||
_navigationController.toolbar.barTintColor = _toolbarBarTintColor;
|
||||
_navigationController.toolbar.tintColor = _toolbarTintColor;
|
||||
|
||||
_navigationController.navigationBar.backgroundColor = _navigationBarBackgroundColor;
|
||||
_navigationController.navigationBar.tintColor = _navigationBarTintColor;
|
||||
NSDictionary *attributes;
|
||||
if (_useCustomFontForNavigationBar) {
|
||||
attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor,
|
||||
NSFontAttributeName : [UIFont fontWithName:_pickerBoldFontName size:_pickerFontHeaderSize]};
|
||||
} else {
|
||||
attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor};
|
||||
}
|
||||
_navigationController.navigationBar.titleTextAttributes = attributes;
|
||||
|
||||
[self updateToolbar];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
return _pickerStatusBarStyle;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setup Navigation Controller
|
||||
|
||||
- (void)setupNavigationController
|
||||
{
|
||||
GMAlbumsViewController *albumsViewController = [[GMAlbumsViewController alloc] init];
|
||||
_navigationController = [[UINavigationController alloc] initWithRootViewController:albumsViewController];
|
||||
_navigationController.delegate = self;
|
||||
[_navigationController.navigationBar setTranslucent:NO];
|
||||
[_navigationController willMoveToParentViewController:self];
|
||||
[_navigationController.view setFrame:self.view.frame];
|
||||
[self.view addSubview:_navigationController.view];
|
||||
[self addConstraintsToChildViewControllersView:_navigationController.view];
|
||||
[self addChildViewController:_navigationController];
|
||||
[_navigationController didMoveToParentViewController:self];
|
||||
}
|
||||
|
||||
- (void)addConstraintsToChildViewControllersView:(UIView *)view {
|
||||
view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSArray * hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(view)];
|
||||
NSLayoutConstraint * topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
|
||||
NSLayoutConstraint * bottomConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
|
||||
[view.superview addConstraints:@[topConstraint,bottomConstraint]];
|
||||
[view.superview addConstraints:hConstraints];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIAlertViewDelegate
|
||||
|
||||
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
if (buttonIndex == 1) {
|
||||
// Only if OK was pressed do we want to completge the selection
|
||||
[self finishPickingAssets:self];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Select / Deselect Asset
|
||||
|
||||
- (void)selectAsset:(PHAsset *)asset
|
||||
{
|
||||
[self.selectedAssets insertObject:asset atIndex:self.selectedAssets.count];
|
||||
[self updateDoneButton];
|
||||
|
||||
if (!self.allowsMultipleSelection) {
|
||||
if (self.confirmSingleSelection) {
|
||||
NSString *message = self.confirmSingleSelectionPrompt ? self.confirmSingleSelectionPrompt : [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.confirm.message", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Do you want to select the image you tapped on?")];
|
||||
|
||||
[[[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.confirm.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Are You Sure?")]
|
||||
message:message
|
||||
delegate:self
|
||||
cancelButtonTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.no", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"No")]
|
||||
otherButtonTitles:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.yes", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Yes")], nil] show];
|
||||
} else {
|
||||
[self finishPickingAssets:self];
|
||||
}
|
||||
} else if (self.displaySelectionInfoToolbar || self.showCameraButton) {
|
||||
[self updateToolbar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deselectAsset:(PHAsset *)asset
|
||||
{
|
||||
[self.selectedAssets removeObjectAtIndex:[self.selectedAssets indexOfObject:asset]];
|
||||
if (self.selectedAssets.count == 0) {
|
||||
[self updateDoneButton];
|
||||
}
|
||||
|
||||
if (self.displaySelectionInfoToolbar || self.showCameraButton) {
|
||||
[self updateToolbar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDoneButton
|
||||
{
|
||||
if (!self.allowsMultipleSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
|
||||
for (UIViewController *viewController in nav.viewControllers) {
|
||||
viewController.navigationItem.rightBarButtonItem.enabled = (self.autoDisableDoneButton ? self.selectedAssets.count > 0 : TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateToolbar
|
||||
{
|
||||
if (!self.allowsMultipleSelection && !self.showCameraButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
|
||||
for (UIViewController *viewController in nav.viewControllers) {
|
||||
NSUInteger index = 1;
|
||||
if (_showCameraButton) {
|
||||
index++;
|
||||
}
|
||||
[[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateNormal];
|
||||
[[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateDisabled];
|
||||
[[viewController.toolbarItems objectAtIndex:index] setTitle:[self toolbarTitle]];
|
||||
[viewController.navigationController setToolbarHidden:(self.selectedAssets.count == 0 && !self.showCameraButton) animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - User finish Actions
|
||||
|
||||
- (void)dismiss:(id)sender
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(assetsPickerControllerDidCancel:)]) {
|
||||
[self.delegate assetsPickerControllerDidCancel:self];
|
||||
}
|
||||
|
||||
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)finishPickingAssets:(id)sender
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingAssets:)]) {
|
||||
[self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedAssets];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Toolbar Title
|
||||
|
||||
- (NSPredicate *)predicateOfAssetType:(PHAssetMediaType)type
|
||||
{
|
||||
return [NSPredicate predicateWithBlock:^BOOL(PHAsset *asset, NSDictionary *bindings) {
|
||||
return (asset.mediaType == type);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)toolbarTitle
|
||||
{
|
||||
if (self.selectedAssets.count == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSPredicate *photoPredicate = [self predicateOfAssetType:PHAssetMediaTypeImage];
|
||||
NSPredicate *videoPredicate = [self predicateOfAssetType:PHAssetMediaTypeVideo];
|
||||
|
||||
NSInteger nImages = [self.selectedAssets filteredArrayUsingPredicate:photoPredicate].count;
|
||||
NSInteger nVideos = [self.selectedAssets filteredArrayUsingPredicate:videoPredicate].count;
|
||||
|
||||
if (nImages > 0 && nVideos > 0) {
|
||||
return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-items", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Items Selected" ), @(nImages + nVideos)];
|
||||
} else if (nImages > 1) {
|
||||
return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-photos", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Photos Selected"), @(nImages)];
|
||||
} else if (nImages == 1) {
|
||||
return NSLocalizedStringFromTableInBundle(@"picker.selection.single-photo", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"1 Photo Selected" );
|
||||
} else if (nVideos > 1) {
|
||||
return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-videos", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Videos Selected"), @(nVideos)];
|
||||
} else if (nVideos == 1) {
|
||||
return NSLocalizedStringFromTableInBundle(@"picker.selection.single-video", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"1 Video Selected");
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Toolbar Items
|
||||
|
||||
- (void)cameraButtonPressed:(UIBarButtonItem *)button
|
||||
{
|
||||
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Camera!"
|
||||
message:@"Sorry, this device does not have a camera."
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"OK"
|
||||
otherButtonTitles:nil];
|
||||
[alert show];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This allows the selection of the image taken to be better seen if the user is not already in that VC
|
||||
if (self.autoSelectCameraImages && [self.navigationController.topViewController isKindOfClass:[GMAlbumsViewController class]]) {
|
||||
[((GMAlbumsViewController *)self.navigationController.topViewController) selectAllAlbumsCell];
|
||||
}
|
||||
|
||||
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
||||
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
||||
picker.mediaTypes = @[(NSString *)kUTTypeImage];
|
||||
picker.allowsEditing = self.allowsEditingCameraImages;
|
||||
picker.delegate = self;
|
||||
picker.modalPresentationStyle = UIModalPresentationPopover;
|
||||
|
||||
UIPopoverPresentationController *popPC = picker.popoverPresentationController;
|
||||
popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
|
||||
popPC.barButtonItem = button;
|
||||
|
||||
[self showViewController:picker sender:button];
|
||||
}
|
||||
|
||||
- (NSDictionary *)toolbarTitleTextAttributes {
|
||||
return @{NSForegroundColorAttributeName : _toolbarTextColor,
|
||||
NSFontAttributeName : [UIFont fontWithName:_pickerFontName size:_pickerFontHeaderSize]};
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)titleButtonItem
|
||||
{
|
||||
UIBarButtonItem *title = [[UIBarButtonItem alloc] initWithTitle:self.toolbarTitle
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:nil
|
||||
action:nil];
|
||||
|
||||
NSDictionary *attributes = [self toolbarTitleTextAttributes];
|
||||
[title setTitleTextAttributes:attributes forState:UIControlStateNormal];
|
||||
[title setTitleTextAttributes:attributes forState:UIControlStateDisabled];
|
||||
[title setEnabled:NO];
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)spaceButtonItem
|
||||
{
|
||||
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)cameraButtonItem
|
||||
{
|
||||
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@selector(cameraButtonPressed:)];
|
||||
}
|
||||
|
||||
- (NSArray *)toolbarItems
|
||||
{
|
||||
UIBarButtonItem *camera = [self cameraButtonItem];
|
||||
UIBarButtonItem *title = [self titleButtonItem];
|
||||
UIBarButtonItem *space = [self spaceButtonItem];
|
||||
|
||||
NSMutableArray *items = [[NSMutableArray alloc] init];
|
||||
if (_showCameraButton) {
|
||||
[items addObject:camera];
|
||||
}
|
||||
[items addObject:space];
|
||||
[items addObject:title];
|
||||
[items addObject:space];
|
||||
|
||||
return [NSArray arrayWithArray:items];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Camera Delegate
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
|
||||
{
|
||||
[picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
NSString *mediaType = info[UIImagePickerControllerMediaType];
|
||||
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
|
||||
UIImage *image = info[UIImagePickerControllerEditedImage] ? : info[UIImagePickerControllerOriginalImage];
|
||||
UIImageWriteToSavedPhotosAlbum(image,
|
||||
self,
|
||||
@selector(image:finishedSavingWithError:contextInfo:),
|
||||
nil);
|
||||
}
|
||||
}
|
||||
|
||||
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
||||
{
|
||||
[picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
-(void)image:(UIImage *)image finishedSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
|
||||
{
|
||||
if (error) {
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Image Not Saved"
|
||||
message:@"Sorry, unable to save the new image!"
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"OK"
|
||||
otherButtonTitles:nil];
|
||||
[alert show];
|
||||
}
|
||||
|
||||
// Note: The image view will auto refresh as the photo's are being observed in the other VCs
|
||||
}
|
||||
|
||||
@end
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 158 B |
Binary file not shown.
After Width: | Height: | Size: 194 B |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -18,6 +18,10 @@
|
|||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
||||
D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; };
|
||||
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
|
||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
|
||||
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -67,6 +71,9 @@
|
|||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
|
||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; };
|
||||
D627FF81217FE8F400CC0648 /* BehaviorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */; };
|
||||
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B4E21EA695800FE4B39 /* StatusContentType.swift */; };
|
||||
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */; };
|
||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; };
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
||||
|
@ -110,8 +117,36 @@
|
|||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; };
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
||||
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; };
|
||||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
||||
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */; };
|
||||
D67E051521643C77000E0927 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E051421643C77000E0927 /* Tab.swift */; };
|
||||
D686329521ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686326E21ED8312008C716E /* GMImagePicker.strings */; };
|
||||
D686329621ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327121ED8312008C716E /* GMImagePicker.strings */; };
|
||||
D686329721ED8319008C716E /* GMGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327321ED8312008C716E /* GMGridViewCell.m */; };
|
||||
D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327421ED8312008C716E /* GMVideoIcon@2x.png */; };
|
||||
D686329921ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327621ED8313008C716E /* GMImagePicker.strings */; };
|
||||
D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327821ED8313008C716E /* GMAlbumsViewController.m */; };
|
||||
D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327921ED8313008C716E /* GMVideoIcon.png */; };
|
||||
D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327B21ED8313008C716E /* GMImagePicker.strings */; };
|
||||
D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */; };
|
||||
D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327E21ED8314008C716E /* GMAlbumsViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D686329F21ED8319008C716E /* GMGridViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327F21ED8315008C716E /* GMGridViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A021ED8319008C716E /* GMSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328021ED8315008C716E /* GMSelected.png */; };
|
||||
D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328121ED8315008C716E /* GMAlbumsViewCell.m */; };
|
||||
D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328221ED8316008C716E /* GMAlbumsViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328421ED8316008C716E /* GMImagePicker.strings */; };
|
||||
D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328721ED8317008C716E /* GMImagePicker.strings */; };
|
||||
D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328921ED8317008C716E /* GMSelected@2x.png */; };
|
||||
D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328A21ED8317008C716E /* GMImagePickerController.m */; };
|
||||
D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328B21ED8317008C716E /* GMGridViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328D21ED8317008C716E /* GMImagePicker.strings */; };
|
||||
D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */; };
|
||||
D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; };
|
||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
||||
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||
|
@ -150,6 +185,13 @@
|
|||
remoteGlobalIDString = 04496BC7216252E5001F1B23;
|
||||
remoteInfo = TTTAttributedLabel;
|
||||
};
|
||||
D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D60A548A21ED515800F1F87C;
|
||||
remoteInfo = GMImagePicker;
|
||||
};
|
||||
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
|
@ -194,6 +236,7 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */,
|
||||
04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
|
||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||
|
@ -216,6 +259,10 @@
|
|||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
||||
D60A548B21ED515800F1F87C /* GMImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GMImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D60A548D21ED515800F1F87C /* GMImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GMImagePicker.h; sourceTree = "<group>"; };
|
||||
D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
|
||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
|
||||
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -266,6 +313,9 @@
|
|||
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorTableViewController.swift; sourceTree = "<group>"; };
|
||||
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentType.swift; sourceTree = "<group>"; };
|
||||
D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedTableViewController.swift; sourceTree = "<group>"; };
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; };
|
||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
||||
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
||||
|
@ -308,8 +358,36 @@
|
|||
D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; };
|
||||
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
||||
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
|
||||
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; };
|
||||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
||||
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
|
||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceTableViewController.swift; sourceTree = "<group>"; };
|
||||
D67E051421643C77000E0927 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = "<group>"; };
|
||||
D686326F21ED8312008C716E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686327221ED8312008C716E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686327321ED8312008C716E /* GMGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewCell.m; sourceTree = "<group>"; };
|
||||
D686327421ED8312008C716E /* GMVideoIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMVideoIcon@2x.png"; sourceTree = "<group>"; };
|
||||
D686327721ED8313008C716E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686327821ED8313008C716E /* GMAlbumsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewController.m; sourceTree = "<group>"; };
|
||||
D686327921ED8313008C716E /* GMVideoIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMVideoIcon.png; sourceTree = "<group>"; };
|
||||
D686327C21ED8313008C716E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@2x.png"; sourceTree = "<group>"; };
|
||||
D686327E21ED8314008C716E /* GMAlbumsViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewCell.h; sourceTree = "<group>"; };
|
||||
D686327F21ED8315008C716E /* GMGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewController.h; sourceTree = "<group>"; };
|
||||
D686328021ED8315008C716E /* GMSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMSelected.png; sourceTree = "<group>"; };
|
||||
D686328121ED8315008C716E /* GMAlbumsViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewCell.m; sourceTree = "<group>"; };
|
||||
D686328221ED8316008C716E /* GMAlbumsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewController.h; sourceTree = "<group>"; };
|
||||
D686328521ED8316008C716E /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686328821ED8317008C716E /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686328921ED8317008C716E /* GMSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMSelected@2x.png"; sourceTree = "<group>"; };
|
||||
D686328A21ED8317008C716E /* GMImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMImagePickerController.m; sourceTree = "<group>"; };
|
||||
D686328B21ED8317008C716E /* GMGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewCell.h; sourceTree = "<group>"; };
|
||||
D686328E21ED8317008C716E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@1x.png"; sourceTree = "<group>"; };
|
||||
D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewController.m; sourceTree = "<group>"; };
|
||||
D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = "<group>"; };
|
||||
D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
||||
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||
|
@ -353,6 +431,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D60A548821ED515800F1F87C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099A82144B0CC00432DC2 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -372,6 +457,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */,
|
||||
04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
|
||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||
|
@ -407,6 +493,48 @@
|
|||
path = TTTAttributedLabel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D60A548C21ED515800F1F87C /* GMImagePicker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D60A548D21ED515800F1F87C /* GMImagePicker.h */,
|
||||
D60A548E21ED515800F1F87C /* Info.plist */,
|
||||
D686327A21ED8313008C716E /* Base.lproj */,
|
||||
D686327021ED8312008C716E /* ca.lproj */,
|
||||
D686329221ED8319008C716E /* de.lproj */,
|
||||
D686328C21ED8317008C716E /* en.lproj */,
|
||||
D686326D21ED8312008C716E /* es.lproj */,
|
||||
D686328321ED8316008C716E /* fr.lproj */,
|
||||
D686327E21ED8314008C716E /* GMAlbumsViewCell.h */,
|
||||
D686328121ED8315008C716E /* GMAlbumsViewCell.m */,
|
||||
D686328221ED8316008C716E /* GMAlbumsViewController.h */,
|
||||
D686327821ED8313008C716E /* GMAlbumsViewController.m */,
|
||||
D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */,
|
||||
D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */,
|
||||
D686328B21ED8317008C716E /* GMGridViewCell.h */,
|
||||
D686327321ED8312008C716E /* GMGridViewCell.m */,
|
||||
D686327F21ED8315008C716E /* GMGridViewController.h */,
|
||||
D686329021ED8319008C716E /* GMGridViewController.m */,
|
||||
D686329121ED8319008C716E /* GMImagePickerController.h */,
|
||||
D686328A21ED8317008C716E /* GMImagePickerController.m */,
|
||||
D686328021ED8315008C716E /* GMSelected.png */,
|
||||
D686328921ED8317008C716E /* GMSelected@2x.png */,
|
||||
D686327921ED8313008C716E /* GMVideoIcon.png */,
|
||||
D686327421ED8312008C716E /* GMVideoIcon@2x.png */,
|
||||
D686327521ED8313008C716E /* it.lproj */,
|
||||
D686328621ED8317008C716E /* pt.lproj */,
|
||||
);
|
||||
path = GMImagePicker;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D60C07E221E817560057FAA8 /* Compose Media */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */,
|
||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
||||
);
|
||||
path = "Compose Media";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -488,6 +616,7 @@
|
|||
D61099FA214569F600432DC2 /* Report.swift */,
|
||||
D61099FC21456A1D00432DC2 /* SearchResults.swift */,
|
||||
D61099FE21456A4C00432DC2 /* Status.swift */,
|
||||
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */,
|
||||
D6109A10214607D500432DC2 /* Timeline.swift */,
|
||||
);
|
||||
path = Model;
|
||||
|
@ -596,6 +725,7 @@
|
|||
D627FF77217E94F200CC0648 /* Drafts */,
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
|
@ -615,6 +745,7 @@
|
|||
children = (
|
||||
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
|
||||
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
|
||||
D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */,
|
||||
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */,
|
||||
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */,
|
||||
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */,
|
||||
|
@ -690,6 +821,7 @@
|
|||
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */,
|
||||
D6333B362137838300CE884A /* AttributedString+Trim.swift */,
|
||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */,
|
||||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -707,15 +839,98 @@
|
|||
path = XCallbackURL;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D67C57A721E2649B00C3118B /* Account Detail */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */,
|
||||
);
|
||||
path = "Account Detail";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D67C57B021E28F9400C3118B /* Compose Status Reply */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */,
|
||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */,
|
||||
);
|
||||
path = "Compose Status Reply";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686326D21ED8312008C716E /* es.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686326E21ED8312008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = es.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327021ED8312008C716E /* ca.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686327121ED8312008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = ca.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327521ED8313008C716E /* it.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686327621ED8313008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = it.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327A21ED8313008C716E /* Base.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686327B21ED8313008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = Base.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328321ED8316008C716E /* fr.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686328421ED8316008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = fr.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328621ED8317008C716E /* pt.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686328721ED8317008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = pt.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328C21ED8317008C716E /* en.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686328D21ED8317008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = en.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686329221ED8319008C716E /* de.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D686329321ED8319008C716E /* GMImagePicker.strings */,
|
||||
);
|
||||
path = de.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6BED1722126661300F02DA0 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04496BD621625361001F1B23 /* ContentLabel.swift */,
|
||||
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */,
|
||||
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
||||
D60C07E221E817560057FAA8 /* Compose Media */,
|
||||
D641C78A213DD926004B4513 /* Status */,
|
||||
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||
D641C78C213DD937004B4513 /* Notifications */,
|
||||
|
@ -759,6 +974,7 @@
|
|||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||
D60A548C21ED515800F1F87C /* GMImagePicker */,
|
||||
D6D4DDCD212518A000E1C4BB /* Products */,
|
||||
D65A37F221472F300087646E /* Frameworks */,
|
||||
);
|
||||
|
@ -773,6 +989,7 @@
|
|||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
||||
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
||||
04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */,
|
||||
D60A548B21ED515800F1F87C /* GMImagePicker.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -847,6 +1064,19 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D60A548621ED515800F1F87C /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329F21ED8319008C716E /* GMGridViewController.h in Headers */,
|
||||
D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */,
|
||||
D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */,
|
||||
D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */,
|
||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */,
|
||||
D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099A62144B0CC00432DC2 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -876,6 +1106,24 @@
|
|||
productReference = 04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D60A548A21ED515800F1F87C /* GMImagePicker */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D60A549421ED515800F1F87C /* Build configuration list for PBXNativeTarget "GMImagePicker" */;
|
||||
buildPhases = (
|
||||
D60A548621ED515800F1F87C /* Headers */,
|
||||
D60A548721ED515800F1F87C /* Sources */,
|
||||
D60A548821ED515800F1F87C /* Frameworks */,
|
||||
D60A548921ED515800F1F87C /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = GMImagePicker;
|
||||
productName = GMImagePicker;
|
||||
productReference = D60A548B21ED515800F1F87C /* GMImagePicker.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
|
||||
|
@ -927,6 +1175,7 @@
|
|||
dependencies = (
|
||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
04496BCE216252E5001F1B23 /* PBXTargetDependency */,
|
||||
D60A549121ED515800F1F87C /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tusker;
|
||||
productName = Tusker;
|
||||
|
@ -982,6 +1231,9 @@
|
|||
04496BC7216252E5001F1B23 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
};
|
||||
D60A548A21ED515800F1F87C = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
};
|
||||
D61099AA2144B0CC00432DC2 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1000;
|
||||
|
@ -1010,6 +1262,12 @@
|
|||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
de,
|
||||
es,
|
||||
it,
|
||||
fr,
|
||||
pt,
|
||||
ca,
|
||||
);
|
||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||
|
@ -1022,6 +1280,7 @@
|
|||
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
||||
04496BC7216252E5001F1B23 /* TTTAttributedLabel */,
|
||||
D60A548A21ED515800F1F87C /* GMImagePicker */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -1034,6 +1293,27 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D60A548921ED515800F1F87C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329621ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */,
|
||||
D686329521ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */,
|
||||
D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329921ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */,
|
||||
D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */,
|
||||
D68632A021ED8319008C716E /* GMSelected.png in Resources */,
|
||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099A92144B0CC00432DC2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1064,6 +1344,8 @@
|
|||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
||||
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||
D621544D21682AD90003D87D /* TabTableViewCell.xib in Resources */,
|
||||
|
@ -1095,6 +1377,18 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D60A548721ED515800F1F87C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */,
|
||||
D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */,
|
||||
D686329721ED8319008C716E /* GMGridViewCell.m in Sources */,
|
||||
D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */,
|
||||
D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099A72144B0CC00432DC2 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1122,6 +1416,7 @@
|
|||
D6109A0F21459B6900432DC2 /* Pagination.swift in Sources */,
|
||||
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */,
|
||||
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */,
|
||||
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */,
|
||||
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */,
|
||||
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */,
|
||||
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */,
|
||||
|
@ -1150,9 +1445,11 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
||||
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */,
|
||||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
|
@ -1166,6 +1463,7 @@
|
|||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D621544B21682AD30003D87D /* TabTableViewCell.swift in Sources */,
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
|
||||
|
@ -1189,6 +1487,7 @@
|
|||
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
|
||||
D627FF74217BBC9700CC0648 /* AppRouter.swift in Sources */,
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||
|
@ -1196,6 +1495,7 @@
|
|||
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
|
||||
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
|
||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */,
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||
|
@ -1242,6 +1542,11 @@
|
|||
target = 04496BC7216252E5001F1B23 /* TTTAttributedLabel */;
|
||||
targetProxy = 04496BCD216252E5001F1B23 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D60A549121ED515800F1F87C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D60A548A21ED515800F1F87C /* GMImagePicker */;
|
||||
targetProxy = D60A549021ED515800F1F87C /* PBXContainerItemProxy */;
|
||||
};
|
||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||
|
@ -1270,6 +1575,70 @@
|
|||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
D686326E21ED8312008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686326F21ED8312008C716E /* es */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327121ED8312008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686327221ED8312008C716E /* ca */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327621ED8313008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686327721ED8313008C716E /* it */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686327B21ED8313008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686327C21ED8313008C716E /* Base */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328421ED8316008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686328521ED8316008C716E /* fr */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328721ED8317008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686328821ED8317008C716E /* pt */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686328D21ED8317008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686328E21ED8317008C716E /* en */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D686329321ED8319008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686329421ED8319008C716E /* de */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -1337,6 +1706,62 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
D60A549521ED515800F1F87C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = HGYVAQA9FW;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = GMImagePicker/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GMImagePicker;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D60A549621ED515800F1F87C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = HGYVAQA9FW;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = GMImagePicker/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GMImagePicker;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D61099C32144B0CC00432DC2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -1690,6 +2115,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D60A549421ED515800F1F87C /* Build configuration list for PBXNativeTarget "GMImagePicker" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D60A549521ED515800F1F87C /* Debug */,
|
||||
D60A549621ED515800F1F87C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Array+Uniques.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/6/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Array where Element: Hashable {
|
||||
func uniques() -> [Element] {
|
||||
var buffer = [Element]()
|
||||
var added = Set<Element>()
|
||||
for elem in self {
|
||||
if !added.contains(elem) {
|
||||
buffer.append(elem)
|
||||
added.insert(elem)
|
||||
}
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ class Preferences: Codable {
|
|||
|
||||
// MARK: - Advanced
|
||||
var silentActions: [String: Permission] = [:]
|
||||
var statusContentType: StatusContentType = .plain
|
||||
|
||||
// MARK: - Utility Methods
|
||||
func tabIndex(_ tab: Tab) -> Int {
|
||||
|
|
|
@ -9,67 +9,86 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import Intents
|
||||
import Photos
|
||||
import GMImagePicker
|
||||
import MobileCoreServices
|
||||
|
||||
class ComposeViewController: UIViewController {
|
||||
|
||||
let router: AppRouter
|
||||
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var inReplyToContainerView: UIView!
|
||||
@IBOutlet weak var inReplyToAvatarImageView: UIImageView!
|
||||
@IBOutlet weak var inReplyToDisplayNameLabel: UILabel!
|
||||
@IBOutlet weak var inReplyToUsernameLabel: UILabel!
|
||||
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var inReplyToLabel: UILabel!
|
||||
@IBOutlet weak var statusTextView: UITextView!
|
||||
@IBOutlet weak var placeholderLabel: UILabel!
|
||||
@IBOutlet weak var charactersRemainingLabel: UILabel!
|
||||
@IBOutlet weak var visibilityButton: UIButton!
|
||||
@IBOutlet weak var postButton: UIButton!
|
||||
@IBOutlet weak var contentWarningTextField: UITextField!
|
||||
@IBOutlet weak var mediaStackView: UIStackView!
|
||||
@IBOutlet weak var paddingView: UIView!
|
||||
@IBOutlet weak var progressView: SteppedProgressView!
|
||||
|
||||
var scrolled = false
|
||||
|
||||
var inReplyToID: String?
|
||||
|
||||
// TODO: cleanup this
|
||||
var mentioningAcct: String?
|
||||
var text: String?
|
||||
var accountsToMention: [String]
|
||||
var initialText: String?
|
||||
var contentWarningEnabled = false {
|
||||
didSet {
|
||||
contentWarningStateChanged()
|
||||
}
|
||||
}
|
||||
var visibility: Status.Visibility! {
|
||||
didSet {
|
||||
visibilityChanged()
|
||||
}
|
||||
}
|
||||
var selectedAssets: [PHAsset] = [] {
|
||||
didSet {
|
||||
updateAttachmentViews()
|
||||
}
|
||||
}
|
||||
|
||||
var draft: DraftsManager.Draft?
|
||||
|
||||
var currentDraft: DraftsManager.Draft?
|
||||
// Weak so that if a new session is initiated (i.e. XCBManager.currentSession is changed) while the current one is in progress, this one will be released
|
||||
weak var xcbSession: XCBSession?
|
||||
var postedStatus: Status?
|
||||
|
||||
var contentWarning = false {
|
||||
didSet {
|
||||
contentWarningTextField.isHidden = !contentWarning
|
||||
}
|
||||
}
|
||||
var visibility = Preferences.shared.defaultPostVisibility {
|
||||
didSet {
|
||||
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
||||
}
|
||||
}
|
||||
weak var postBarButtonItem: UIBarButtonItem!
|
||||
var visibilityBarButtonItem: UIBarButtonItem!
|
||||
var contentWarningBarButtonItem: UIBarButtonItem!
|
||||
|
||||
var status: Status?
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var contentView: UIView!
|
||||
@IBOutlet weak var stackView: UIStackView!
|
||||
|
||||
var replyView: ComposeStatusReplyView?
|
||||
var replyAvatarImageViewTopConstraint: NSLayoutConstraint?
|
||||
|
||||
@IBOutlet weak var selfDetailView: LargeAccountDetailView!
|
||||
|
||||
@IBOutlet weak var charactersRemainingLabel: UILabel!
|
||||
@IBOutlet weak var statusTextView: UITextView!
|
||||
@IBOutlet weak var placeholderLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var contentWarningContainerView: UIView!
|
||||
@IBOutlet weak var contentWarningTextField: UITextField!
|
||||
|
||||
@IBOutlet weak var attachmentsStackView: UIStackView!
|
||||
@IBOutlet weak var addAttachmentButton: UIButton!
|
||||
|
||||
@IBOutlet weak var postProgressView: SteppedProgressView!
|
||||
|
||||
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, router: AppRouter) {
|
||||
self.inReplyToID = inReplyToID
|
||||
self.mentioningAcct = mentioningAcct
|
||||
self.text = text
|
||||
self.router = router
|
||||
|
||||
self.inReplyToID = inReplyToID
|
||||
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
||||
} else if let mentioningAcct = mentioningAcct {
|
||||
accountsToMention = [mentioningAcct]
|
||||
} else {
|
||||
accountsToMention = []
|
||||
}
|
||||
if let ownAccount = MastodonController.account {
|
||||
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
|
||||
}
|
||||
accountsToMention = accountsToMention.uniques()
|
||||
|
||||
super.init(nibName: "ComposeViewController", bundle: nil)
|
||||
|
||||
title = "Compose"
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed))
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsPressed))
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .done, target: self, action: #selector(postButtonPressed))
|
||||
postBarButtonItem = navigationItem.rightBarButtonItem
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -79,97 +98,141 @@ class ComposeViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
statusTextView.layer.cornerRadius = 5
|
||||
statusTextView.layer.masksToBounds = true
|
||||
scrollView.delegate = self
|
||||
|
||||
statusTextView.delegate = self
|
||||
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
||||
contentWarningTextField.delegate = self
|
||||
statusTextView.becomeFirstResponder()
|
||||
|
||||
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
|
||||
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(keyboardDoneButtonPressed))
|
||||
toolbar.setItems([flexSpace, done], animated: false)
|
||||
toolbar.sizeToFit()
|
||||
let toolbar = UIToolbar()
|
||||
contentWarningBarButtonItem = UIBarButtonItem(title: "CW", style: .plain, target: self, action: #selector(contentWarningButtonPressed))
|
||||
visibilityBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(visbilityButtonPressed))
|
||||
toolbar.items = [
|
||||
contentWarningBarButtonItem,
|
||||
visibilityBarButtonItem,
|
||||
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
] + createFormattingButtons() + [
|
||||
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
|
||||
UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsButtonPressed))
|
||||
]
|
||||
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
statusTextView.inputAccessoryView = toolbar
|
||||
contentWarningTextField.inputAccessoryView = toolbar
|
||||
|
||||
if let inReplyToID = inReplyToID,
|
||||
let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||
inReplyToDisplayNameLabel.text = inReplyTo.account.realDisplayName
|
||||
inReplyToUsernameLabel.text = "@\(inReplyTo.account.username)"
|
||||
inReplyToContentLabel.statusID = inReplyToID
|
||||
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
||||
inReplyToAvatarImageView.layer.masksToBounds = true
|
||||
inReplyToAvatarImageView.image = nil
|
||||
ImageCache.avatars.get(inReplyTo.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.inReplyToAvatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined()
|
||||
initialText = statusTextView.text
|
||||
|
||||
MastodonController.getOwnAccount { (account) in
|
||||
DispatchQueue.main.async {
|
||||
self.selfDetailView.update(account: account)
|
||||
}
|
||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||
if inReplyTo.account != MastodonController.account {
|
||||
statusTextView.text = "@\(inReplyTo.account.acct) "
|
||||
}
|
||||
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.account.id }).map({ "@\($0.acct) " }).joined()
|
||||
contentWarning = inReplyTo.sensitive
|
||||
contentWarningTextField.text = inReplyTo.spoilerText
|
||||
}
|
||||
|
||||
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||
visibility = inReplyTo.visibility
|
||||
} else {
|
||||
inReplyToLabel.isHidden = true
|
||||
inReplyToContainerView.isHidden = true
|
||||
|
||||
let replyView = ComposeStatusReplyView.create()
|
||||
replyView.updateUI(for: inReplyTo)
|
||||
stackView.insertArrangedSubview(replyView, at: 0)
|
||||
self.replyView = replyView
|
||||
|
||||
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
|
||||
replyAvatarImageViewTopConstraint!.isActive = true
|
||||
|
||||
let replyLabelContainer = UIView()
|
||||
replyLabelContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let replyLabel = UILabel()
|
||||
replyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
replyLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||
replyLabel.textColor = .darkGray
|
||||
replyLabelContainer.addSubview(replyLabel)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
replyLabel.leadingAnchor.constraint(equalTo: replyLabelContainer.leadingAnchor, constant: 8),
|
||||
replyLabel.trailingAnchor.constraint(equalTo: replyLabelContainer.trailingAnchor, constant: -8),
|
||||
replyLabel.topAnchor.constraint(equalTo: replyLabelContainer.topAnchor),
|
||||
replyLabel.bottomAnchor.constraint(equalTo: replyLabelContainer.bottomAnchor)
|
||||
])
|
||||
|
||||
stackView.insertArrangedSubview(replyLabelContainer, at: 1)
|
||||
}
|
||||
|
||||
if let mentioningAcct = mentioningAcct {
|
||||
statusTextView.text += "@\(mentioningAcct) "
|
||||
}
|
||||
if let text = text {
|
||||
statusTextView.text += text
|
||||
}
|
||||
|
||||
initialText = statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
updateCharactersRemaining()
|
||||
updatePlaceholder()
|
||||
|
||||
progressView.progress = 0
|
||||
contentWarningEnabled = false
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
|
||||
|
||||
if let mentioningAcct = mentioningAcct {
|
||||
let req = MastodonController.client.searchForAccount(query: mentioningAcct)
|
||||
MastodonController.client.run(req) { [weak self] (response) in
|
||||
if case let .success(accounts, _) = response {
|
||||
self?.userActivity = UserActivityManager.newPostActivity(mentioning: accounts.first)
|
||||
} else {
|
||||
self?.userActivity = UserActivityManager.newPostActivity()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.userActivity = UserActivityManager.newPostActivity()
|
||||
if inReplyToID == nil {
|
||||
visibility = Preferences.shared.defaultPostVisibility
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
if inReplyToID != nil && !scrolled {
|
||||
scrollView.contentOffset = CGPoint(x: 0, y: inReplyToContainerView.bounds.height - 44)
|
||||
scrolled = true
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
// if inReplyToID != nil {
|
||||
// scrollView.contentOffset = CGPoint(x: 0, y: stackView.arrangedSubviews.first!.frame.height)
|
||||
// }
|
||||
}
|
||||
|
||||
func createFormattingButtons() -> [UIBarButtonItem] {
|
||||
guard Preferences.shared.statusContentType != .plain else {
|
||||
return []
|
||||
}
|
||||
|
||||
return StatusFormat.allCases.map { (format) in
|
||||
let (str, attributes) = format.title
|
||||
let item = UIBarButtonItem(title: str, style: .plain, target: self, action: #selector(formatButtonPressed(_:)))
|
||||
item.setTitleTextAttributes(attributes, for: .normal)
|
||||
item.setTitleTextAttributes(attributes, for: .highlighted)
|
||||
item.tag = StatusFormat.allCases.firstIndex(of: format)!
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
@objc func adjustForKeyboard(notification: NSNotification) {
|
||||
let userInfo = notification.userInfo!
|
||||
|
||||
func addMedia(for image: UIImage) {
|
||||
let mediaView = ComposeMediaView(image: image)
|
||||
mediaView.delegate = self
|
||||
mediaStackView.addArrangedSubview(mediaView)
|
||||
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
|
||||
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
|
||||
|
||||
if notification.name == UIResponder.keyboardWillHideNotification {
|
||||
scrollView.contentInset = .zero
|
||||
} else {
|
||||
// let accessoryFrame = view.convert(statusTextView.inputAccessoryView!.frame, from: view.window)
|
||||
let offset = keyboardViewEndFrame.height// + accessoryFrame.height
|
||||
// TODO: radar for incorrect keyboard end frame height (either converted or screen)
|
||||
// the value returned is somewhere between the height of the keyboard and the height of the keyboard + accessory
|
||||
// actually maybe not??
|
||||
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: offset, right: 0)
|
||||
}
|
||||
scrollView.scrollIndicatorInsets = scrollView.contentInset
|
||||
}
|
||||
|
||||
func updateCharactersRemaining() {
|
||||
// TODO: include CW char count
|
||||
let count = CharacterCounter.count(text: statusTextView.text)
|
||||
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count
|
||||
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
|
||||
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount
|
||||
if remaining < 0 {
|
||||
charactersRemainingLabel.textColor = .red
|
||||
postButton.isEnabled = false
|
||||
postBarButtonItem.isEnabled = false
|
||||
} else {
|
||||
charactersRemainingLabel.textColor = .darkGray
|
||||
postButton.isEnabled = true
|
||||
postBarButtonItem.isEnabled = true
|
||||
}
|
||||
charactersRemainingLabel.text = remaining.description
|
||||
}
|
||||
|
@ -178,147 +241,65 @@ class ComposeViewController: UIViewController {
|
|||
placeholderLabel.isHidden = !statusTextView.text.isEmpty
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
statusTextView.resignFirstResponder()
|
||||
contentWarningTextField.resignFirstResponder()
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
func updateAddAttachmentButton() {
|
||||
addAttachmentButton.isEnabled = selectedAssets.count < 5 // 4 attachments + 1 button
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@IBAction func visibilityPressed(_ sender: Any) {
|
||||
let alertController = UIAlertController(currentVisibility: self.visibility) { (visibility) in
|
||||
guard let visibility = visibility else { return }
|
||||
UIView.performWithoutAnimation {
|
||||
self.visibility = visibility
|
||||
self.visibilityButton.layoutIfNeeded()
|
||||
func updateAttachmentViews() {
|
||||
for view in attachmentsStackView.arrangedSubviews {
|
||||
if view is ComposeMediaView {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func contentWarningPressed(_ sender: Any) {
|
||||
contentWarning = !contentWarning
|
||||
}
|
||||
|
||||
@IBAction func mediaPressed(_ sender: Any) {
|
||||
let imagePicker = UIImagePickerController()
|
||||
imagePicker.delegate = self
|
||||
|
||||
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
||||
alertController.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
|
||||
imagePicker.sourceType = .camera
|
||||
self.present(imagePicker, animated: true)
|
||||
}))
|
||||
for asset in selectedAssets {
|
||||
let mediaView = ComposeMediaView.create()
|
||||
mediaView.delegate = self
|
||||
mediaView.update(asset: asset)
|
||||
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
||||
updateAddAttachmentButton()
|
||||
}
|
||||
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
|
||||
alertController.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { __ in
|
||||
imagePicker.sourceType = .photoLibrary
|
||||
self.present(imagePicker, animated: true)
|
||||
}))
|
||||
}
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func postPressed(_ sender: Any) {
|
||||
guard let text = statusTextView.text,
|
||||
!text.isEmpty else { return }
|
||||
|
||||
postButton.isEnabled = false
|
||||
|
||||
let contentWarning: String?
|
||||
if self.contentWarning,
|
||||
let text = contentWarningTextField.text,
|
||||
!text.isEmpty {
|
||||
contentWarning = text
|
||||
func contentWarningStateChanged() {
|
||||
contentWarningContainerView.isHidden = !contentWarningEnabled
|
||||
contentWarningBarButtonItem.style = contentWarningEnabled ? .done : .plain
|
||||
if contentWarningEnabled {
|
||||
contentWarningTextField.becomeFirstResponder()
|
||||
} else {
|
||||
contentWarning = nil
|
||||
}
|
||||
let sensitive = contentWarning != nil
|
||||
|
||||
let visibility = self.visibility
|
||||
|
||||
var attachments: [Attachment?] = []
|
||||
let group = DispatchGroup()
|
||||
for view in mediaStackView.arrangedSubviews {
|
||||
guard let mediaView = view as? ComposeMediaView,
|
||||
let image = mediaView.image,
|
||||
let data = image.pngData() else { continue }
|
||||
let index = attachments.count
|
||||
attachments.append(nil)
|
||||
progressView.steps += 1
|
||||
group.enter()
|
||||
let request = MastodonController.client.upload(attachment: FormAttachment(pngData: data), description: mediaView.mediaDescription)
|
||||
MastodonController.client.run(request) { response in
|
||||
guard case let .success(attachment, _) = response else { fatalError() }
|
||||
attachments[index] = attachment
|
||||
self.progressView.step()
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
progressView.steps = 2 + attachments.count
|
||||
progressView.currentStep = 1
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let attachments = attachments.compactMap { $0 }
|
||||
|
||||
let request = MastodonController.client.createStatus(text: text,
|
||||
inReplyTo: self.inReplyToID,
|
||||
media: attachments,
|
||||
sensitive: sensitive,
|
||||
spoilerText: contentWarning,
|
||||
visibility: visibility)
|
||||
MastodonController.client.run(request) { response in
|
||||
guard case let .success(status, _) = response else { fatalError() }
|
||||
self.status = status
|
||||
MastodonCache.add(status: status)
|
||||
|
||||
if let draft = self.draft {
|
||||
DraftsManager.shared.remove(draft)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.progressView.step()
|
||||
self.dismiss(animated: true)
|
||||
// TODO: reorganize routing/navigation
|
||||
(((self.presentingViewController as! MainTabBarViewController).selectedViewController as! UINavigationController).topViewController as! TuskerNavigationDelegate).selected(status: status.id)
|
||||
|
||||
self.xcbSession?.complete(with: .success, additionalData: [
|
||||
"statusURL": status.url?.absoluteString,
|
||||
"statusURI": status.uri
|
||||
])
|
||||
}
|
||||
}
|
||||
statusTextView.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func imagePressed(_ gesture: UITapGestureRecognizer) {
|
||||
gesture.view!.superview!.removeFromSuperview()
|
||||
}
|
||||
|
||||
@objc func keyboardDoneButtonPressed() {
|
||||
statusTextView.endEditing(false)
|
||||
func visibilityChanged() {
|
||||
// TODO: update visiblity button image
|
||||
}
|
||||
|
||||
func saveDraft() {
|
||||
if let draft = draft {
|
||||
draft.update(text: statusTextView.text)
|
||||
// TODO: save attachmenst to drafts
|
||||
// TODO: save CW to draft
|
||||
if let currentDraft = currentDraft {
|
||||
currentDraft.update(text: statusTextView.text)
|
||||
} else {
|
||||
DraftsManager.shared.create(text: statusTextView.text)
|
||||
}
|
||||
}
|
||||
|
||||
func close() {
|
||||
@objc func close() {
|
||||
dismiss(animated: true)
|
||||
xcbSession?.complete(with: .cancel)
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
// MARK: - Navigation
|
||||
|
||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
statusTextView.resignFirstResponder()
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
guard statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) != initialText else {
|
||||
close()
|
||||
return
|
||||
|
@ -336,28 +317,177 @@ class ComposeViewController: UIViewController {
|
|||
self.close()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Delete draft", style: .destructive, handler: { (_) in
|
||||
if let draft = self.draft {
|
||||
DraftsManager.shared.remove(draft)
|
||||
if let currentDraft = self.currentDraft {
|
||||
DraftsManager.shared.remove(currentDraft)
|
||||
}
|
||||
self.close()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
router.present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc func draftsPressed() {
|
||||
let drafts = router.drafts()
|
||||
drafts.delegate = self
|
||||
router.present(UINavigationController(rootViewController: drafts), animated: true)
|
||||
@objc func contentWarningButtonPressed() {
|
||||
contentWarningEnabled = !contentWarningEnabled
|
||||
}
|
||||
|
||||
@objc func contentWarningTextFieldDidChange() {
|
||||
updateCharactersRemaining()
|
||||
}
|
||||
|
||||
@objc func visbilityButtonPressed() {
|
||||
let alertController = UIAlertController(currentVisibility: self.visibility) { (visibility) in
|
||||
guard let visibility = visibility else { return }
|
||||
self.visibility = visibility
|
||||
}
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@objc func formatButtonPressed(_ button: UIBarButtonItem) {
|
||||
guard statusTextView.isFirstResponder else {
|
||||
return
|
||||
}
|
||||
|
||||
let format = StatusFormat.allCases[button.tag]
|
||||
guard let insertionResult = format.insertionResult else {
|
||||
return
|
||||
}
|
||||
|
||||
let currentSelectedRange = statusTextView.selectedRange
|
||||
if currentSelectedRange.length == 0 {
|
||||
statusTextView.insertText(insertionResult.prefix + insertionResult.suffix)
|
||||
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.insertionPoint, length: 0)
|
||||
} else {
|
||||
let start = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.lowerBound)
|
||||
let end = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.upperBound)
|
||||
let selectedText = statusTextView.text[start..<end]
|
||||
statusTextView.insertText(String(insertionResult.prefix + selectedText + insertionResult.suffix))
|
||||
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.prefix.count, length: currentSelectedRange.length)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func draftsButtonPressed() {
|
||||
let draftsVC = router.drafts()
|
||||
draftsVC.delegate = self
|
||||
router.present(UINavigationController(rootViewController: draftsVC), animated: true)
|
||||
}
|
||||
|
||||
@IBAction func addAttachmentPressed(_ sender: Any) {
|
||||
let picker = GMImagePickerController()
|
||||
picker.delegate = self
|
||||
picker.toolbarTintColor = view.tintColor
|
||||
picker.navigationBarTintColor = view.tintColor
|
||||
picker.title = "Choose Attachment"
|
||||
present(picker, animated: true)
|
||||
}
|
||||
|
||||
@objc func postButtonPressed() {
|
||||
guard let text = statusTextView.text,
|
||||
!text.isEmpty else { return }
|
||||
|
||||
// disable post button while sending post request
|
||||
postBarButtonItem.isEnabled = false
|
||||
|
||||
let contentWarning: String?
|
||||
if contentWarningEnabled, let cwText = contentWarningTextField.text, !cwText.isEmpty {
|
||||
contentWarning = cwText
|
||||
} else {
|
||||
contentWarning = nil
|
||||
}
|
||||
let sensitive = contentWarning != nil
|
||||
let visibility = self.visibility!
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
var attachments: [Attachment?] = []
|
||||
for asset in selectedAssets {
|
||||
let index = attachments.count
|
||||
attachments.append(nil)
|
||||
|
||||
let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView
|
||||
let description = mediaView.descriptionTextView.text
|
||||
|
||||
group.enter()
|
||||
|
||||
let options = PHImageRequestOptions()
|
||||
options.version = .current
|
||||
options.deliveryMode = .highQualityFormat
|
||||
options.resizeMode = .none
|
||||
options.isNetworkAccessAllowed = true
|
||||
PHImageManager.default().requestImageData(for: asset, options: options) { (data, dataUTI, orientation, info) in
|
||||
guard let data = data, let dataUTI = dataUTI else { fatalError() }
|
||||
|
||||
let mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String
|
||||
|
||||
self.postProgressView.step()
|
||||
|
||||
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
guard case let .success(attachment, _) = response else { fatalError() }
|
||||
|
||||
attachments[index] = attachment
|
||||
|
||||
self.postProgressView.step()
|
||||
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postProgressView.steps = 2 + (attachments.count * 2) // 2 steps (request data, then upload) for each attachment
|
||||
postProgressView.currentStep = 1
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let attachments = attachments.compactMap { $0 }
|
||||
|
||||
let request = MastodonController.client.createStatus(text: text,
|
||||
contentType: Preferences.shared.statusContentType,
|
||||
inReplyTo: self.inReplyToID,
|
||||
media: attachments,
|
||||
sensitive: sensitive,
|
||||
spoilerText: contentWarning,
|
||||
visibility: visibility,
|
||||
language: nil)
|
||||
MastodonController.client.run(request) { (response) in
|
||||
guard case let .success(status, _) = response else { fatalError() }
|
||||
self.postedStatus = status
|
||||
MastodonCache.add(status: status)
|
||||
|
||||
if let draft = self.currentDraft {
|
||||
DraftsManager.shared.remove(draft)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.postProgressView.step()
|
||||
self.dismiss(animated: true)
|
||||
|
||||
let conversationVC = self.router.conversation(for: status.id)
|
||||
self.router.push(conversationVC, animated: true)
|
||||
|
||||
self.xcbSession?.complete(with: .success, additionalData: [
|
||||
"statusURL": status.url?.absoluteString,
|
||||
"statusURI": status.uri
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeViewController: UITextFieldDelegate {
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.endEditing(false)
|
||||
return true
|
||||
extension ComposeViewController: UIScrollViewDelegate {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard let replyView = replyView else { return }
|
||||
|
||||
var constant: CGFloat = 8
|
||||
|
||||
if scrollView.contentOffset.y < 0 {
|
||||
constant -= scrollView.contentOffset.y
|
||||
replyAvatarImageViewTopConstraint?.constant = 8 - scrollView.contentOffset.y
|
||||
} else if scrollView.contentOffset.y > replyView.frame.height - replyView.avatarImageView.frame.height - 16 {
|
||||
constant += replyView.frame.height - replyView.avatarImageView.frame.height - 16 - scrollView.contentOffset.y
|
||||
}
|
||||
|
||||
replyAvatarImageViewTopConstraint?.constant = constant
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,27 +498,23 @@ extension ComposeViewController: UITextViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
if let selectedImage = info[.originalImage] as? UIImage {
|
||||
addMedia(for: selectedImage)
|
||||
dismiss(animated: true)
|
||||
}
|
||||
extension ComposeViewController: GMImagePickerControllerDelegate {
|
||||
func assetsPickerController(_ picker: GMImagePickerController!, didFinishPickingAssets assets: [Any]!) {
|
||||
let assets = assets as! [PHAsset]
|
||||
selectedAssets.append(contentsOf: assets)
|
||||
picker.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func assetsPickerController(_ picker: GMImagePickerController!, shouldSelect asset: PHAsset!) -> Bool {
|
||||
return selectedAssets.count + picker.selectedAssets.count < 4
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: ComposeMediaViewDelegate {
|
||||
func editDescription(for media: ComposeMediaView) {
|
||||
let alertController = UIAlertController(title: "Media Description", message: nil, preferredStyle: .alert)
|
||||
alertController.addTextField { textField in
|
||||
textField.text = media.mediaDescription
|
||||
}
|
||||
alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { _ in
|
||||
let description = alertController.textFields![0].text
|
||||
media.mediaDescription = description
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
present(alertController, animated: true)
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
||||
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
||||
selectedAssets.remove(at: index)
|
||||
updateAddAttachmentButton()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,8 +523,9 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|||
}
|
||||
|
||||
func draftSelected(_ draft: DraftsManager.Draft) {
|
||||
self.draft = draft
|
||||
self.currentDraft = draft
|
||||
statusTextView.text = draft.text
|
||||
updatePlaceholder()
|
||||
updateCharactersRemaining()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.23.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.16.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ComposeViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="charactersRemainingLabel" destination="3Ti-cT-JnH" id="hhy-k7-jBN"/>
|
||||
<outlet property="contentWarningTextField" destination="FA4-UR-Pc9" id="UnZ-6T-Phn"/>
|
||||
<outlet property="inReplyToAvatarImageView" destination="cOi-cF-kIg" id="bCo-zq-NYI"/>
|
||||
<outlet property="inReplyToContainerView" destination="YTO-vO-WZ5" id="gsc-Qh-Roi"/>
|
||||
<outlet property="inReplyToContentLabel" destination="a2k-Ax-jJd" id="wSz-ga-J2W"/>
|
||||
<outlet property="inReplyToDisplayNameLabel" destination="77P-er-bd4" id="GrH-Cu-Sqw"/>
|
||||
<outlet property="inReplyToLabel" destination="lZc-Z2-0vV" id="KSM-Kh-lgR"/>
|
||||
<outlet property="inReplyToUsernameLabel" destination="ZNM-ey-3j8" id="hMp-DJ-7kB"/>
|
||||
<outlet property="mediaStackView" destination="wHz-h7-939" id="2gr-Vs-ymW"/>
|
||||
<outlet property="paddingView" destination="YyO-d2-4bx" id="wPp-TI-5Zc"/>
|
||||
<outlet property="placeholderLabel" destination="blO-2d-WiC" id="cG4-Gt-c4T"/>
|
||||
<outlet property="postButton" destination="i4l-LR-1kp" id="BmN-Zr-OcQ"/>
|
||||
<outlet property="progressView" destination="Ncd-80-ldI" id="ZNz-kk-Rjl"/>
|
||||
<outlet property="scrollView" destination="cp4-lI-aME" id="dqM-XD-7EF"/>
|
||||
<outlet property="statusTextView" destination="5Yb-vo-0yN" id="r6k-fK-U5F"/>
|
||||
<outlet property="addAttachmentButton" destination="eEV-Yt-Njk" id="o9g-pP-dtd"/>
|
||||
<outlet property="attachmentsStackView" destination="P0F-3w-gI1" id="Bi5-EK-N3a"/>
|
||||
<outlet property="charactersRemainingLabel" destination="PMB-Wa-Ht0" id="PN9-wr-Pzu"/>
|
||||
<outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/>
|
||||
<outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/>
|
||||
<outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/>
|
||||
<outlet property="placeholderLabel" destination="EW3-YK-vPC" id="Rsw-Nv-TNz"/>
|
||||
<outlet property="postProgressView" destination="Tq7-6P-hMT" id="amT-F1-JI0"/>
|
||||
<outlet property="scrollView" destination="6Z0-Vy-hMX" id="ya0-2T-QaV"/>
|
||||
<outlet property="selfDetailView" destination="zZ3-Gv-4P5" id="jou-Vl-TQE"/>
|
||||
<outlet property="stackView" destination="bOB-hF-O9w" id="lD7-b2-MWl"/>
|
||||
<outlet property="statusTextView" destination="9pn-0T-IHb" id="u7j-KW-zCo"/>
|
||||
<outlet property="view" destination="7XG-Dk-OGm" id="09I-sr-hnP"/>
|
||||
<outlet property="visibilityButton" destination="6CJ-b2-876" id="jkz-te-T3u"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
|
@ -35,166 +31,179 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="cp4-lI-aME">
|
||||
<rect key="frame" x="0.0" y="-44" width="375" height="711"/>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="6Z0-Vy-hMX">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ovj-HJ-0pD">
|
||||
<rect key="frame" x="16" y="16" width="343" height="779"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YTO-vO-WZ5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="128"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="cOi-cF-kIg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="IMp-xZ-1wk"/>
|
||||
<constraint firstAttribute="height" constant="50" id="p8i-9U-qdf"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="77P-er-bd4">
|
||||
<rect key="frame" x="58" y="0.0" width="105" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZNM-ey-3j8">
|
||||
<rect key="frame" x="171" y="0.0" width="172" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a2k-Ax-jJd" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="58" y="28.5" width="285" height="99.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="a2k-Ax-jJd" firstAttribute="top" secondItem="77P-er-bd4" secondAttribute="bottom" constant="8" id="6oq-wI-C5d"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ZNM-ey-3j8" secondAttribute="trailing" id="9v0-fI-wi5"/>
|
||||
<constraint firstItem="cOi-cF-kIg" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="Fnv-I3-PLN"/>
|
||||
<constraint firstItem="cOi-cF-kIg" firstAttribute="leading" secondItem="YTO-vO-WZ5" secondAttribute="leading" id="J0g-iO-Hsy"/>
|
||||
<constraint firstItem="77P-er-bd4" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="anu-L8-kpt"/>
|
||||
<constraint firstAttribute="trailing" secondItem="a2k-Ax-jJd" secondAttribute="trailing" id="cIX-qN-Bnr"/>
|
||||
<constraint firstAttribute="bottom" secondItem="a2k-Ax-jJd" secondAttribute="bottom" id="cYK-IY-C6V"/>
|
||||
<constraint firstItem="ZNM-ey-3j8" firstAttribute="leading" secondItem="77P-er-bd4" secondAttribute="trailing" constant="8" id="dV5-U5-sJL"/>
|
||||
<constraint firstItem="77P-er-bd4" firstAttribute="leading" secondItem="cOi-cF-kIg" secondAttribute="trailing" constant="8" id="hva-Nf-90R"/>
|
||||
<constraint firstItem="ZNM-ey-3j8" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="oET-4l-974"/>
|
||||
<constraint firstItem="a2k-Ax-jJd" firstAttribute="leading" secondItem="cOi-cF-kIg" secondAttribute="trailing" constant="8" id="rt2-Lx-cgH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dpq-un-p7Y">
|
||||
<rect key="frame" x="0.0" y="136" width="343" height="643"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="In reply to Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZc-Z2-0vV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="5Yb-vo-0yN">
|
||||
<rect key="frame" x="0.0" y="26.5" width="343" height="200"/>
|
||||
<color key="backgroundColor" red="0.8980392157" green="0.8980392157" blue="0.91764705879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="200" id="URa-6h-Hbt"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HTy-eW-OvQ">
|
||||
<rect key="frame" x="0.0" y="232.5" width="343" height="30"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pZv-DT-CdW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="42" height="30"/>
|
||||
<state key="normal" title="Media"/>
|
||||
<connections>
|
||||
<action selector="mediaPressed:" destination="-1" eventType="touchUpInside" id="7Uf-WL-RnQ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H1c-9o-wdb">
|
||||
<rect key="frame" x="50" y="0.0" width="30" height="30"/>
|
||||
<state key="normal" title="CW"/>
|
||||
<connections>
|
||||
<action selector="contentWarningPressed:" destination="-1" eventType="touchUpInside" id="MrZ-09-UDE"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6CJ-b2-876">
|
||||
<rect key="frame" x="88" y="0.0" width="42" height="30"/>
|
||||
<state key="normal" title="Public"/>
|
||||
<connections>
|
||||
<action selector="visibilityPressed:" destination="-1" eventType="touchUpInside" id="rg9-0L-AZ9"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" text="500" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Ti-cT-JnH">
|
||||
<rect key="frame" x="138" y="0.0" width="166" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<view contentMode="scaleToFill" placeholderIntrinsicWidth="infinite" placeholderIntrinsicHeight="66" translatesAutoresizingMaskIntoConstraints="NO" id="zZ3-Gv-4P5" customClass="LargeAccountDetailView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="336" height="66"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="500" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PMB-Wa-Ht0">
|
||||
<rect key="frame" x="336" y="8" width="31" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i4l-LR-1kp">
|
||||
<rect key="frame" x="312" y="0.0" width="31" height="30"/>
|
||||
<state key="normal" title="Post"/>
|
||||
<connections>
|
||||
<action selector="postPressed:" destination="-1" eventType="touchUpInside" id="KKB-fz-Xra"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="PMB-Wa-Ht0" secondAttribute="trailing" constant="8" id="5KJ-rz-Heh"/>
|
||||
<constraint firstItem="zZ3-Gv-4P5" firstAttribute="leading" secondItem="6V0-mH-Mhu" secondAttribute="leading" id="f6Q-fK-zq1"/>
|
||||
<constraint firstItem="zZ3-Gv-4P5" firstAttribute="top" secondItem="6V0-mH-Mhu" secondAttribute="top" id="fjf-mn-l9f"/>
|
||||
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="top" secondItem="6V0-mH-Mhu" secondAttribute="top" constant="8" id="q3V-aY-t9K"/>
|
||||
<constraint firstAttribute="bottom" secondItem="zZ3-Gv-4P5" secondAttribute="bottom" id="rOO-0n-odM"/>
|
||||
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="leading" secondItem="zZ3-Gv-4P5" secondAttribute="trailing" id="sVv-tH-7eB"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kU2-7l-MSy">
|
||||
<rect key="frame" x="0.0" y="66" width="375" height="46"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write your warning here" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="T05-p6-vTz">
|
||||
<rect key="frame" x="4" y="8" width="367" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="yzY-MF-Ukx"/>
|
||||
</constraints>
|
||||
<nil key="textColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
</textField>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="T05-p6-vTz" secondAttribute="trailing" constant="4" id="8tG-eW-TG4"/>
|
||||
<constraint firstAttribute="bottom" secondItem="T05-p6-vTz" secondAttribute="bottom" constant="8" id="SUL-Hk-uvM"/>
|
||||
<constraint firstItem="T05-p6-vTz" firstAttribute="leading" secondItem="kU2-7l-MSy" secondAttribute="leading" constant="4" id="WGG-B2-lPC"/>
|
||||
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="8" id="lvW-S0-4k4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9">
|
||||
<rect key="frame" x="0.0" y="112" width="375" height="150"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9pn-0T-IHb">
|
||||
<rect key="frame" x="4" y="0.0" width="367" height="150"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="150" id="ISI-jm-FxV"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="What's on your mind?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EW3-YK-vPC">
|
||||
<rect key="frame" x="8" y="8" width="188" height="24"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="9pn-0T-IHb" secondAttribute="bottom" id="UAs-fL-Riv"/>
|
||||
<constraint firstItem="9pn-0T-IHb" firstAttribute="leading" secondItem="lhQ-ae-pe9" secondAttribute="leading" constant="4" id="ezI-15-Yd4"/>
|
||||
<constraint firstItem="9pn-0T-IHb" firstAttribute="top" secondItem="lhQ-ae-pe9" secondAttribute="top" id="n8v-pK-I9E"/>
|
||||
<constraint firstItem="EW3-YK-vPC" firstAttribute="leading" secondItem="9pn-0T-IHb" secondAttribute="leading" constant="4" id="n9v-mJ-gz3"/>
|
||||
<constraint firstItem="EW3-YK-vPC" firstAttribute="top" secondItem="9pn-0T-IHb" secondAttribute="top" constant="8" id="q5e-yM-bS4"/>
|
||||
<constraint firstAttribute="trailing" secondItem="9pn-0T-IHb" secondAttribute="trailing" constant="4" id="x7Z-8w-xgm"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
|
||||
<rect key="frame" x="0.0" y="262" width="375" height="80"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Aqk-LY-jEj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="1"/>
|
||||
<color key="backgroundColor" red="0.90978598590000004" green="0.90964758400000001" blue="0.91777461770000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="0C7-KP-bIQ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cVn-xc-LH9">
|
||||
<rect key="frame" x="0.0" y="79" width="375" height="1"/>
|
||||
<color key="backgroundColor" red="0.90978598590000004" green="0.90964758400000001" blue="0.91777461770000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="1" id="SZ4-5b-Hcf"/>
|
||||
<constraint firstAttribute="height" constant="1" id="VIz-vl-Um4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eEV-Yt-Njk">
|
||||
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="sGZ-uD-CtS"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||
<state key="normal" title=" Add image or video" image="More">
|
||||
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="addAttachmentPressed:" destination="-1" eventType="touchUpInside" id="aUR-nx-O9u"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Aqk-LY-jEj" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="6i1-Jt-AEM"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eEV-Yt-Njk" secondAttribute="bottom" id="C6D-yq-PU3"/>
|
||||
<constraint firstItem="Aqk-LY-jEj" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="Y5a-qr-Dby"/>
|
||||
<constraint firstAttribute="bottom" secondItem="cVn-xc-LH9" secondAttribute="bottom" id="dCy-ov-086"/>
|
||||
<constraint firstItem="eEV-Yt-Njk" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" constant="8" id="enN-pq-hxK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Aqk-LY-jEj" secondAttribute="trailing" id="h1Q-QT-wB9"/>
|
||||
<constraint firstItem="cVn-xc-LH9" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="oNI-gt-O9v"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eEV-Yt-Njk" secondAttribute="trailing" constant="8" id="qe1-4r-oaa"/>
|
||||
<constraint firstItem="eEV-Yt-Njk" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="rpc-rE-Q57"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cVn-xc-LH9" secondAttribute="trailing" id="uSI-lv-mqY"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<textField hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Content Warning" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="FA4-UR-Pc9">
|
||||
<rect key="frame" x="0.0" y="265.5" width="343" height="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
||||
</textField>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="wHz-h7-939">
|
||||
<rect key="frame" x="0.0" y="268.5" width="343" height="100"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="100" id="0qA-Te-SOn"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="YyO-d2-4bx">
|
||||
<rect key="frame" x="0.0" y="374.5" width="343" height="268.5"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="What is on your mind?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="blO-2d-WiC">
|
||||
<rect key="frame" x="20.5" y="186.5" width="170" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="bOB-hF-O9w" secondAttribute="trailing" id="GAR-qc-jte"/>
|
||||
<constraint firstAttribute="height" secondItem="bOB-hF-O9w" secondAttribute="height" id="KO2-zF-s7P"/>
|
||||
<constraint firstItem="bOB-hF-O9w" firstAttribute="top" secondItem="pcX-rB-RxJ" secondAttribute="top" id="aBm-Ub-TpI"/>
|
||||
<constraint firstItem="bOB-hF-O9w" firstAttribute="leading" secondItem="pcX-rB-RxJ" secondAttribute="leading" id="yOt-hH-L57"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="ovj-HJ-0pD" secondAttribute="bottom" id="GqL-42-cue"/>
|
||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="leading" secondItem="cp4-lI-aME" secondAttribute="leading" constant="16" id="I7Y-uR-bf0"/>
|
||||
<constraint firstItem="blO-2d-WiC" firstAttribute="top" secondItem="5Yb-vo-0yN" secondAttribute="top" constant="8" id="Wfi-2X-213"/>
|
||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="top" secondItem="cp4-lI-aME" secondAttribute="top" constant="16" id="bq1-LT-laf"/>
|
||||
<constraint firstItem="blO-2d-WiC" firstAttribute="leading" secondItem="5Yb-vo-0yN" secondAttribute="leading" constant="4.5" id="dhw-kL-D2B"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ovj-HJ-0pD" secondAttribute="trailing" constant="16" id="hJy-JL-iae"/>
|
||||
<constraint firstItem="dpq-un-p7Y" firstAttribute="height" secondItem="cp4-lI-aME" secondAttribute="height" constant="-68" id="oE5-6c-QYI"/>
|
||||
<constraint firstItem="pcX-rB-RxJ" firstAttribute="leading" secondItem="6Z0-Vy-hMX" secondAttribute="leading" id="X0f-ja-XBF"/>
|
||||
<constraint firstAttribute="bottom" secondItem="pcX-rB-RxJ" secondAttribute="bottom" id="X8a-PA-r8F"/>
|
||||
<constraint firstAttribute="trailing" secondItem="pcX-rB-RxJ" secondAttribute="trailing" id="lh7-xn-MGp"/>
|
||||
<constraint firstItem="pcX-rB-RxJ" firstAttribute="top" secondItem="6Z0-Vy-hMX" secondAttribute="top" id="yMM-IS-8K1"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progressViewStyle="bar" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Ncd-80-ldI" customClass="SteppedProgressView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="2.5"/>
|
||||
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tq7-6P-hMT" customClass="SteppedProgressView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="2"/>
|
||||
<color key="trackTintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</progressView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Ncd-80-ldI" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="0eq-md-iJK"/>
|
||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="width" secondItem="7XG-Dk-OGm" secondAttribute="width" constant="-32" id="0sw-Bf-MLK"/>
|
||||
<constraint firstItem="cp4-lI-aME" firstAttribute="top" secondItem="7XG-Dk-OGm" secondAttribute="topMargin" constant="-64" id="7ef-so-AaW"/>
|
||||
<constraint firstItem="cp4-lI-aME" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="8fs-On-3th"/>
|
||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="bottom" secondItem="cp4-lI-aME" secondAttribute="bottom" id="S1A-db-SQZ"/>
|
||||
<constraint firstItem="Ncd-80-ldI" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="Wy2-V4-Jkp"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cp4-lI-aME" secondAttribute="trailing" id="afV-LD-XZS"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ncd-80-ldI" secondAttribute="trailing" id="k6I-L5-xZc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Tq7-6P-hMT" secondAttribute="trailing" id="GeN-8q-weq"/>
|
||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="bottom" secondItem="6Z0-Vy-hMX" secondAttribute="bottom" id="Hf3-Cc-mVX"/>
|
||||
<constraint firstItem="Tq7-6P-hMT" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="LgA-xu-VGE"/>
|
||||
<constraint firstItem="Tq7-6P-hMT" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="agM-ZO-c3E"/>
|
||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="trailing" secondItem="6Z0-Vy-hMX" secondAttribute="trailing" id="hjY-W6-wTQ"/>
|
||||
<constraint firstItem="bOB-hF-O9w" firstAttribute="width" secondItem="7XG-Dk-OGm" secondAttribute="width" id="i0p-NE-ca1"/>
|
||||
<constraint firstItem="6Z0-Vy-hMX" firstAttribute="leading" secondItem="Heg-g4-sYM" secondAttribute="leading" id="lFF-yC-ql9"/>
|
||||
<constraint firstItem="6Z0-Vy-hMX" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="osv-zq-seP"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Heg-g4-sYM"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="More" width="60" height="30"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// StatusFormat.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/12/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
enum StatusFormat: CaseIterable {
|
||||
case italics, bold, strikethrough, code
|
||||
|
||||
var insertionResult: FormatInsertionResult? {
|
||||
switch Preferences.shared.statusContentType {
|
||||
case .plain:
|
||||
return nil
|
||||
case .markdown:
|
||||
return Markdown.format(self)
|
||||
case .html:
|
||||
return HTML.format(self)
|
||||
}
|
||||
}
|
||||
|
||||
var title: (String, [NSAttributedString.Key: Any]) {
|
||||
switch self {
|
||||
case .italics:
|
||||
return ("I", [.font: UIFont.italicSystemFont(ofSize: 17)])
|
||||
case .bold:
|
||||
return ("B", [.font: UIFont.boldSystemFont(ofSize: 17)])
|
||||
case .strikethrough:
|
||||
return ("S", [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue)])
|
||||
case .code:
|
||||
return ("</>", [.font: UIFont(name: "Menlo", size: 17)!])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias FormatInsertionResult = (prefix: String, suffix: String, insertionPoint: Int)
|
||||
|
||||
protocol FormatType {
|
||||
static func format(_ format: StatusFormat) -> FormatInsertionResult
|
||||
}
|
||||
|
||||
extension StatusFormat {
|
||||
struct Markdown: FormatType {
|
||||
static var formats: [StatusFormat: String] = [
|
||||
.italics: "_",
|
||||
.bold: "**",
|
||||
.strikethrough: "~~",
|
||||
.code: "`"
|
||||
]
|
||||
|
||||
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
||||
let str = formats[format]!
|
||||
return (str, str, str.count)
|
||||
}
|
||||
}
|
||||
|
||||
struct HTML: FormatType {
|
||||
static var tags: [StatusFormat: String] = [
|
||||
.italics: "em",
|
||||
.bold: "strong",
|
||||
.strikethrough: "del",
|
||||
.code: "code"
|
||||
]
|
||||
|
||||
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
||||
let tag = tags[format]!
|
||||
return ("<\(tag)>", "</\(tag)>", tag.count + 2)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// AdvancedTableViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/12/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class AdvancedTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var postContentTypeLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
postContentTypeLabel.text = Preferences.shared.statusContentType.displayName
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard indexPath.section == 1 && indexPath.row == 0 else {
|
||||
return
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let alertController = UIAlertController(title: "Post Content Type", message: nil, preferredStyle: .actionSheet)
|
||||
for contentType in StatusContentType.allCases {
|
||||
let action = UIAlertAction(title: contentType.displayName, style: .default) { (_) in
|
||||
Preferences.shared.statusContentType = contentType
|
||||
self.postContentTypeLabel.text = contentType.displayName
|
||||
}
|
||||
if contentType == Preferences.shared.statusContentType {
|
||||
action.setValue(true, forKey: "checked")
|
||||
}
|
||||
alertController.addAction(action)
|
||||
}
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StatusContentType {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .plain:
|
||||
return "Plain"
|
||||
case .markdown:
|
||||
return "Markdown"
|
||||
case .html:
|
||||
return "HTML"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VJJ-jC-9g8">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VJJ-jC-9g8">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -311,7 +311,7 @@
|
|||
<!--Advanced-->
|
||||
<scene sceneID="xgj-Fx-53j">
|
||||
<objects>
|
||||
<tableViewController id="3mv-l7-6We" sceneMemberID="viewController">
|
||||
<tableViewController id="3mv-l7-6We" customClass="AdvancedTableViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="hcR-ju-th0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
@ -344,6 +344,39 @@
|
|||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="aNt-ow-hHp">
|
||||
<string key="footerTitle">This option is only supported for Pleroma instances with formatting enabled. On all other instances, formatting symbols will remain in the plain, unformatted text.</string>
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="L56-bm-5R5">
|
||||
<rect key="frame" x="0.0" y="135.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="L56-bm-5R5" id="mtr-Qa-CSI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Post Content Type" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ThO-WW-QsH">
|
||||
<rect key="frame" x="16" y="11.5" width="142" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Plain" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uq5-Fi-mpp">
|
||||
<rect key="frame" x="322" y="11.5" width="37" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="uq5-Fi-mpp" firstAttribute="centerY" secondItem="mtr-Qa-CSI" secondAttribute="centerY" id="BMn-9K-DXz"/>
|
||||
<constraint firstItem="ThO-WW-QsH" firstAttribute="centerY" secondItem="mtr-Qa-CSI" secondAttribute="centerY" id="p7P-du-z4q"/>
|
||||
<constraint firstItem="ThO-WW-QsH" firstAttribute="leading" secondItem="mtr-Qa-CSI" secondAttribute="leadingMargin" id="upT-aD-0JW"/>
|
||||
<constraint firstItem="uq5-Fi-mpp" firstAttribute="trailing" secondItem="mtr-Qa-CSI" secondAttribute="trailingMargin" id="vW4-hc-eeu"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="3mv-l7-6We" id="Ots-vG-253"/>
|
||||
|
@ -351,6 +384,9 @@
|
|||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Advanced" id="Rav-Eq-k22"/>
|
||||
<connections>
|
||||
<outlet property="postContentTypeLabel" destination="uq5-Fi-mpp" id="LN1-20-FMA"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="4wC-Hp-AZk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// LargeAccountDetailViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/6/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class LargeAccountDetailView: UIView, PreferencesAdaptive {
|
||||
|
||||
var avatarImageView = UIImageView()
|
||||
var displayNameLabel = UILabel()
|
||||
var usernameLabel = UILabel()
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
avatarImageView.layer.masksToBounds = true
|
||||
addSubview(avatarImageView)
|
||||
|
||||
displayNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
displayNameLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||
addSubview(displayNameLabel)
|
||||
|
||||
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
usernameLabel.font = .systemFont(ofSize: 17, weight: .light)
|
||||
usernameLabel.textColor = .darkGray
|
||||
addSubview(usernameLabel)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
avatarImageView.heightAnchor.constraint(equalToConstant: 50),
|
||||
avatarImageView.widthAnchor.constraint(equalToConstant: 50),
|
||||
avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
||||
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
|
||||
displayNameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||
displayNameLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||
usernameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||
usernameLabel.topAnchor.constraint(equalTo: displayNameLabel.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
updateUIForPreferences()
|
||||
}
|
||||
|
||||
func updateUIForPreferences() {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
}
|
||||
|
||||
func update(account: Account) {
|
||||
displayNameLabel.text = account.realDisplayName
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
ImageCache.avatars.get(account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// ComposeAttachmentView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/10/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
protocol ComposeMediaViewDelegate {
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||
}
|
||||
|
||||
class ComposeMediaView: UIView {
|
||||
|
||||
var delegate: ComposeMediaViewDelegate?
|
||||
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
@IBOutlet weak var placeholderLabel: UILabel!
|
||||
|
||||
static func create() -> ComposeMediaView {
|
||||
return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeMediaView
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = 10 // 0.1 * imageView.frame.width
|
||||
|
||||
descriptionTextView.delegate = self
|
||||
}
|
||||
|
||||
func update(asset: PHAsset) {
|
||||
// let size = imageView.frame.size // is this initialized yet?
|
||||
let size = CGSize(width: 80, height: 80)
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
self.imageView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@IBAction func removePressed(_ sender: Any) {
|
||||
delegate?.didRemoveMedia(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeMediaView: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ComposeMediaView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="u7I-sx-kUe">
|
||||
<rect key="frame" x="8" y="293.5" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="CgF-eC-We6"/>
|
||||
<constraint firstAttribute="width" constant="80" id="S3g-yM-TRb"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G1g-Lw-Ren">
|
||||
<rect key="frame" x="345" y="322.5" width="22" height="22"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="22" id="dyG-5Y-91s"/>
|
||||
<constraint firstAttribute="width" constant="22" id="sWQ-3z-Z5r"/>
|
||||
</constraints>
|
||||
<state key="normal" image="Remove"/>
|
||||
<connections>
|
||||
<action selector="removePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="aor-Cq-YjJ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="O6b-Zs-u8r">
|
||||
<rect key="frame" x="96" y="20" width="241" height="647"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="GsE-uM-fhe"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Describe for the visually impaired..." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rkD-NP-09H">
|
||||
<rect key="frame" x="100" y="28" width="233" height="41"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0h4-wv-2R8">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="1"/>
|
||||
<color key="backgroundColor" red="0.90978598594665527" green="0.90964758396148682" blue="0.91777461767196655" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="aQa-2T-uYY"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="O6b-Zs-u8r" secondAttribute="bottom" id="3sv-wo-gxe"/>
|
||||
<constraint firstItem="u7I-sx-kUe" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="5Qs-i7-glv"/>
|
||||
<constraint firstItem="u7I-sx-kUe" firstAttribute="top" relation="greaterThanOrEqual" secondItem="0h4-wv-2R8" secondAttribute="bottom" constant="8" id="86L-Cb-Lsk"/>
|
||||
<constraint firstItem="O6b-Zs-u8r" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="9QY-MR-Yc2"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="0h4-wv-2R8" secondAttribute="trailing" id="FCS-un-JT6"/>
|
||||
<constraint firstItem="u7I-sx-kUe" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="UWq-Lf-zGB"/>
|
||||
<constraint firstItem="rkD-NP-09H" firstAttribute="leading" secondItem="O6b-Zs-u8r" secondAttribute="leading" constant="4" id="aQd-T9-n1I"/>
|
||||
<constraint firstItem="0h4-wv-2R8" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="edp-4a-YGq"/>
|
||||
<constraint firstItem="G1g-Lw-Ren" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="hJA-JF-MHp"/>
|
||||
<constraint firstItem="O6b-Zs-u8r" firstAttribute="leading" secondItem="u7I-sx-kUe" secondAttribute="trailing" constant="8" id="hvZ-m4-TOV"/>
|
||||
<constraint firstItem="G1g-Lw-Ren" firstAttribute="leading" secondItem="O6b-Zs-u8r" secondAttribute="trailing" constant="8" id="ith-0X-3Yz"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="u7I-sx-kUe" secondAttribute="bottom" constant="8" id="lGN-Qg-mBO"/>
|
||||
<constraint firstItem="rkD-NP-09H" firstAttribute="top" secondItem="O6b-Zs-u8r" secondAttribute="top" constant="8" id="mRb-3M-7uz"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="G1g-Lw-Ren" secondAttribute="trailing" constant="8" id="o4F-MV-ahd"/>
|
||||
<constraint firstItem="0h4-wv-2R8" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="sFo-wp-MYP"/>
|
||||
<constraint firstItem="O6b-Zs-u8r" firstAttribute="trailing" secondItem="rkD-NP-09H" secondAttribute="trailing" constant="4" id="vwg-7l-8ca"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="descriptionTextView" destination="O6b-Zs-u8r" id="TNy-7h-0sY"/>
|
||||
<outlet property="imageView" destination="u7I-sx-kUe" id="o3a-O0-m85"/>
|
||||
<outlet property="placeholderLabel" destination="rkD-NP-09H" id="WtV-2h-L3n"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="136.80000000000001" y="142.57871064467767"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="Remove" width="81" height="81"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// ComposeStatusReplyView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/6/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class ComposeStatusReplyView: UIView, PreferencesAdaptive {
|
||||
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
|
||||
static func create() -> ComposeStatusReplyView {
|
||||
return UINib(nibName: "ComposeStatusReplyView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeStatusReplyView
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
updateUIForPreferences()
|
||||
}
|
||||
|
||||
func updateUIForPreferences() {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
}
|
||||
|
||||
func updateUI(for status: Status) {
|
||||
displayNameLabel.text = status.account.realDisplayName
|
||||
usernameLabel.text = "@\(status.account.acct)"
|
||||
contentLabel.statusID = status.id
|
||||
|
||||
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = UIImage(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ComposeStatusReplyView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ypn-Ed-MTq">
|
||||
<rect key="frame" x="8" y="28" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="8qi-gl-5ci"/>
|
||||
<constraint firstAttribute="width" constant="50" id="Dy2-jh-AJj"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2cE-sS-Uut">
|
||||
<rect key="frame" x="66" y="28" width="301" height="631"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sdv-dB-Plm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0yZ-71-eTj">
|
||||
<rect key="frame" x="115" y="0.0" width="178" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OEF-Hj-v3f" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="24.5" width="301" height="606.5"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Sdv-dB-Plm" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="6v5-7p-9gm"/>
|
||||
<constraint firstAttribute="bottom" secondItem="OEF-Hj-v3f" secondAttribute="bottom" id="IEQ-Ab-tsP"/>
|
||||
<constraint firstItem="OEF-Hj-v3f" firstAttribute="top" secondItem="Sdv-dB-Plm" secondAttribute="bottom" constant="4" id="J5s-TU-odB"/>
|
||||
<constraint firstItem="Sdv-dB-Plm" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="YmP-yU-sfe"/>
|
||||
<constraint firstItem="OEF-Hj-v3f" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="bbW-07-e2x"/>
|
||||
<constraint firstItem="0yZ-71-eTj" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="bdX-ge-bMT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0yZ-71-eTj" secondAttribute="trailing" constant="8" id="hU7-aZ-ibI"/>
|
||||
<constraint firstItem="0yZ-71-eTj" firstAttribute="leading" secondItem="Sdv-dB-Plm" secondAttribute="trailing" constant="8" id="m0X-YU-m3V"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OEF-Hj-v3f" secondAttribute="trailing" id="xqX-4X-lJl"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="2cE-sS-Uut" firstAttribute="height" relation="greaterThanOrEqual" secondItem="Ypn-Ed-MTq" secondAttribute="height" id="Fn3-o4-RGx"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="2cE-sS-Uut" secondAttribute="bottom" constant="8" id="G2d-Kz-c4e"/>
|
||||
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="MbW-9d-3gC"/>
|
||||
<constraint firstItem="2cE-sS-Uut" firstAttribute="leading" secondItem="Ypn-Ed-MTq" secondAttribute="trailing" constant="8" id="TS2-Sr-PB3"/>
|
||||
<constraint firstItem="2cE-sS-Uut" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="cat-Cr-PSV"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="2cE-sS-Uut" secondAttribute="trailing" constant="8" id="eH4-lG-5UR"/>
|
||||
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" placeholder="YES" id="xCn-8G-jUZ"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="Ypn-Ed-MTq" id="eea-bc-klc"/>
|
||||
<outlet property="contentLabel" destination="OEF-Hj-v3f" id="GBI-ib-5T0"/>
|
||||
<outlet property="displayNameLabel" destination="Sdv-dB-Plm" id="RxW-Ra-Ups"/>
|
||||
<outlet property="usernameLabel" destination="0yZ-71-eTj" id="VQm-Dq-3zP"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="138.40000000000001" y="-72.863568215892059"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,57 +0,0 @@
|
|||
//
|
||||
// ComposeAttachmentView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/30/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ComposeMediaViewDelegate {
|
||||
func editDescription(for media: ComposeMediaView)
|
||||
}
|
||||
|
||||
class ComposeMediaView: UIImageView {
|
||||
|
||||
var delegate: ComposeMediaViewDelegate?
|
||||
|
||||
var remove: UIImageView
|
||||
|
||||
var mediaDescription: String?
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
return nil
|
||||
}
|
||||
|
||||
init(image: UIImage) {
|
||||
remove = UIImageView(image: UIImage(named: "Remove"))
|
||||
|
||||
super.init(image: image)
|
||||
|
||||
contentMode = .scaleAspectFill
|
||||
layer.cornerRadius = 5
|
||||
layer.masksToBounds = true
|
||||
isUserInteractionEnabled = true
|
||||
addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressed)))
|
||||
|
||||
remove.isUserInteractionEnabled = true
|
||||
remove.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(removePressed)))
|
||||
remove.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(remove)
|
||||
|
||||
remove.widthAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
remove.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
remove.topAnchor.constraint(equalTo: topAnchor, constant: 5).isActive = true
|
||||
remove.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
|
||||
}
|
||||
|
||||
@objc func removePressed() {
|
||||
removeFromSuperview()
|
||||
}
|
||||
|
||||
@objc func longPressed() {
|
||||
delegate?.editDescription(for: self)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue