completion

This commit is contained in:
2025-08-06 13:49:11 +08:00
commit c2d7317897
684 changed files with 92987 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="bt4-r9-Trl">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Nix Share Extension-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="NixShareExtension" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-270" y="-1356"/>
</scene>
<!--Enter Captions-->
<scene sceneID="heX-HD-CZY">
<objects>
<viewController storyboardIdentifier="ShareViewController" id="Dq8-gr-n5Y" customClass="ImageCaptionViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="q8x-aR-pqn"/>
<viewControllerLayoutGuide type="bottom" id="XDI-cm-ZCd"/>
</layoutGuides>
<view key="view" opaque="NO" contentMode="scaleToFill" id="Qzl-Cn-JM7">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" indicatorStyle="black" keyboardDismissMode="onDrag" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="512" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="joK-Be-CpC" customClass="TPKeyboardAvoidingTableView">
<rect key="frame" x="0.0" y="64" width="375" height="559"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="ImageCaptionCell" id="vja-JL-Ztl" customClass="ImageCaptionTableViewCell">
<rect key="frame" x="0.0" y="22" width="375" height="512"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vja-JL-Ztl" id="3tn-Gy-EEd">
<rect key="frame" x="0.0" y="0.0" width="375" height="512"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="R4h-8T-Gi8">
<rect key="frame" x="15" y="11" width="345" height="421"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</imageView>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="bma-Ax-dpe">
<rect key="frame" x="177.5" y="211.5" width="20" height="20"/>
</activityIndicatorView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rpd-pm-k7F">
<rect key="frame" x="15" y="442" width="345" height="60"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="fcL-7s-kAz"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" autocorrectionType="no" spellCheckingType="no" returnKeyType="done" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
</textView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rdb-lo-ZfB">
<rect key="frame" x="15" y="442" width="345" height="60"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<color key="textColor" red="1" green="0.40000000600000002" blue="0.40000000600000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottomMargin" secondItem="rpd-pm-k7F" secondAttribute="bottom" constant="-1" id="2Ql-GR-lk8"/>
<constraint firstAttribute="bottom" secondItem="rdb-lo-ZfB" secondAttribute="bottom" constant="10" id="4QO-xa-POV"/>
<constraint firstItem="rdb-lo-ZfB" firstAttribute="top" secondItem="R4h-8T-Gi8" secondAttribute="bottom" constant="10" id="BPx-Gt-byP"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="top" secondItem="rdb-lo-ZfB" secondAttribute="top" id="Dey-Wg-cwm"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="centerX" secondItem="bma-Ax-dpe" secondAttribute="centerX" id="Nnu-Ys-5hH"/>
<constraint firstItem="bma-Ax-dpe" firstAttribute="centerY" secondItem="R4h-8T-Gi8" secondAttribute="centerY" id="P1p-Hq-9eF"/>
<constraint firstItem="bma-Ax-dpe" firstAttribute="centerX" secondItem="R4h-8T-Gi8" secondAttribute="centerX" id="Rwo-xa-Qgc"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="trailing" secondItem="rdb-lo-ZfB" secondAttribute="trailing" id="VyT-ce-sWV"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="bottom" secondItem="rdb-lo-ZfB" secondAttribute="bottom" id="X7G-rr-gQB"/>
<constraint firstAttribute="trailingMargin" secondItem="R4h-8T-Gi8" secondAttribute="trailing" id="XX7-sP-gxv"/>
<constraint firstItem="bma-Ax-dpe" firstAttribute="width" secondItem="R4h-8T-Gi8" secondAttribute="width" id="gqM-47-c0u"/>
<constraint firstItem="bma-Ax-dpe" firstAttribute="height" secondItem="R4h-8T-Gi8" secondAttribute="height" id="lEf-ql-t08"/>
<constraint firstItem="R4h-8T-Gi8" firstAttribute="leading" secondItem="3tn-Gy-EEd" secondAttribute="leadingMargin" id="ngz-fi-GTY"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="leading" secondItem="3tn-Gy-EEd" secondAttribute="leadingMargin" id="rbG-oo-QSQ"/>
<constraint firstItem="R4h-8T-Gi8" firstAttribute="top" secondItem="3tn-Gy-EEd" secondAttribute="topMargin" id="swP-Vn-wYj"/>
<constraint firstItem="rpd-pm-k7F" firstAttribute="leading" secondItem="rdb-lo-ZfB" secondAttribute="leading" id="tQ0-11-BGs"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="gqM-47-c0u"/>
<exclude reference="lEf-ql-t08"/>
</mask>
</variation>
</tableViewCellContentView>
<connections>
<outlet property="activityIndicatorView" destination="bma-Ax-dpe" id="IbH-ED-kPy"/>
<outlet property="errorLabel" destination="rdb-lo-ZfB" id="gHD-OE-0du"/>
<outlet property="textView" destination="rpd-pm-k7F" id="SC8-3d-0Pc"/>
<outlet property="thumbnailView" destination="R4h-8T-Gi8" id="C90-1w-dC7"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="Dq8-gr-n5Y" id="MFh-6y-aKs"/>
<outlet property="delegate" destination="Dq8-gr-n5Y" id="5oi-dp-Ihx"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="XDI-cm-ZCd" firstAttribute="top" secondItem="joK-Be-CpC" secondAttribute="bottom" id="UyZ-x6-OAm"/>
<constraint firstItem="joK-Be-CpC" firstAttribute="leading" secondItem="Qzl-Cn-JM7" secondAttribute="leadingMargin" constant="-16" id="X6g-P8-qen"/>
<constraint firstItem="joK-Be-CpC" firstAttribute="top" secondItem="Qzl-Cn-JM7" secondAttribute="topMargin" id="jeH-P8-QGE"/>
<constraint firstAttribute="trailingMargin" secondItem="joK-Be-CpC" secondAttribute="trailing" constant="-16" id="oSw-iK-jD1"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZfE-2l-wnm"/>
</view>
<toolbarItems/>
<navigationItem key="navigationItem" title="Enter Captions" id="9gy-vt-gU0">
<barButtonItem key="backBarButtonItem" title="Back" id="ARf-0c-syw"/>
<barButtonItem key="leftBarButtonItem" title="Cancel" id="Oz8-8U-jBt">
<connections>
<action selector="cancel:" destination="Dq8-gr-n5Y" id="GbE-Fc-j7T"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" title="Next" id="liG-JY-nUM">
<connections>
<segue destination="YSB-oQ-RzE" kind="show" identifier="pushToPlaylistsTableView" id="v3F-fd-hUM"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="tableView" destination="joK-Be-CpC" id="Bj7-i7-U70"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="UTD-fj-Qxk" userLabel="First Responder" sceneMemberID="firstResponder"/>
<navigationBar contentMode="scaleToFill" id="vkB-1h-kSh">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</navigationBar>
</objects>
<point key="canvasLocation" x="405.60000000000002" y="107.49625187406298"/>
</scene>
<!--Select Receivers-->
<scene sceneID="m8D-cG-8bC">
<objects>
<tableViewController id="YSB-oQ-RzE" customClass="ReceiverTableViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelection="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="10" sectionFooterHeight="10" id="Z7J-nI-d1p">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" indentationWidth="10" reuseIdentifier="ReceiverCell" rowHeight="512" id="8rm-dO-heR">
<rect key="frame" x="0.0" y="55.5" width="375" height="512"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="8rm-dO-heR" id="EXg-Qx-0br">
<rect key="frame" x="0.0" y="0.0" width="375" height="511.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="YSB-oQ-RzE" id="8vR-ju-e9W"/>
<outlet property="delegate" destination="YSB-oQ-RzE" id="GA2-Ky-yOS"/>
</connections>
</tableView>
<toolbarItems/>
<navigationItem key="navigationItem" title="Select Receivers" id="zBq-p7-QR9">
<barButtonItem key="rightBarButtonItem" title="Send" style="done" id="jty-bC-dNd">
<connections>
<action selector="send:" destination="YSB-oQ-RzE" id="QEa-aL-xJH"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="DSg-wk-dJR" userLabel="First Responder" sceneMemberID="firstResponder"/>
<navigationBar contentMode="scaleToFill" id="zuZ-xG-faf">
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</navigationBar>
</objects>
<point key="canvasLocation" x="1146" y="108"/>
</scene>
<!--Container View Controller-->
<scene sceneID="lgx-fI-BVY">
<objects>
<viewController storyboardIdentifier="ContainerViewId" id="bt4-r9-Trl" customClass="ContainerViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="wB4-ov-Dgz"/>
<viewControllerLayoutGuide type="bottom" id="N2E-uY-WfY"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="cTm-za-nKK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="LXC-Xv-bwp"/>
</view>
<connections>
<segue destination="fP3-PA-YK2" kind="presentation" identifier="InitialSegue" modalPresentationStyle="overFullScreen" id="Gkw-cW-zlX"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="qCF-9c-6mQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-269" y="-601"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="fnC-KA-I6D">
<objects>
<navigationController storyboardIdentifier="NavigationController" id="fP3-PA-YK2" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" id="7bZ-Ai-tt6">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="Dq8-gr-n5Y" kind="relationship" relationship="rootViewController" id="wAw-ws-KMS"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="O5y-Cm-6pa" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-269" y="108"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,31 @@
//
// ContainerViewController.h
// PhotoShareExtension
//
// Created by Russell on 27/5/15.
//
//
#import <UIKit/UIKit.h>
#import <NixNetwork/NixUpload.h>
#import <SDWebImage/SDImageCache.h>
@interface ContainerViewController : UIViewController <NixUploadDelegate>
@property (nonatomic, strong) NixAPI *api;
@property (nonatomic, strong) NixUpload *uploadHandler;
@property (nonatomic, strong) NSArray *imageErrors;
@property (nonatomic, strong) NSArray *imageUrls;
@property (nonatomic, strong) NSArray *captions;
@property (nonatomic, strong) NSArray *playlistIds;
@property (nonatomic, strong) NSArray *friendUsernames;
@property (nonatomic, strong) NSArray *playlistData;
@property (nonatomic, strong) NSArray *friendData;
@property (nonatomic, strong) NSArray *selectedReceiverIndexPaths;
- (void)verifyServiceAvailable;
- (void)verifyServiceAvailableWithCompletionHandler:(void (^)(BOOL))completionHandler;
- (void)pingWithCompletionHandler:(void (^)(NSError *))completionHandler;
- (void)send;
- (void)cancel;
@end

View File

@@ -0,0 +1,368 @@
//
// ContainerViewController.m
// PhotoShareExtension
//
// Created by Russell on 27/5/15.
//
//
#import "ContainerViewController.h"
#import "ImageCaptionViewController.h"
#import <ImageIO/ImageIO.h>
@interface ContainerViewController ()
@property (assign) ImageCaptionViewController *svc;
- (BOOL)tokenExists;
- (void)handleUploadError:(NSError *)error;
@end
@implementation ContainerViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self performSegueWithIdentifier:@"InitialSegue" sender:nil];
self.api = [[NixAPI alloc] init];
self.uploadHandler = [[NixUpload alloc] initWithAPI:self.api];
self.uploadHandler.delegate = self;
self.uploadHandler.uploadSessionIdentifier = @"com.creedon.Nixplay.uploadsession.appext";
[self.uploadHandler createSession]; // rejoin the session
[self.uploadHandler loadUploadState];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
self.svc = ((UINavigationController *)segue.destinationViewController)
.childViewControllers[0];
self.svc.containerViewController = self;
}
#pragma mark - Actions
- (void)verifyServiceAvailable {
[self verifyServiceAvailableWithCompletionHandler:nil];
}
- (void)verifyServiceAvailableWithCompletionHandler:(void (^)(BOOL))completionHandler {
[self.uploadHandler getCurrentTasks:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
NSLog(@"uploadTasks: %@", uploadTasks);
}];
if (!self.api.apiURL.length) {
[self showAlertViewWithTitle:NSLocalizedString(@"app_never_launched_title", nil)
message:NSLocalizedString(@"app_never_launched_message", nil)
actionWithTitle:NSLocalizedString(@"close", nil)
handler:^(UIAlertAction *action) {
[self cancel];
}];
completionHandler(NO);
return;
}
if (![self tokenExists]) {
[self showAlertViewWithTitle:NSLocalizedString(@"no_account_title", nil)
message:NSLocalizedString(@"no_account_message", nil)
actionWithTitle:NSLocalizedString(@"close", nil)
handler:^(UIAlertAction *action) {
[self cancel];
}];
completionHandler(NO);
} else {
[self pingWithCompletionHandler:^(NSError *error) {
if (error) {
if (error.code == 401) {
[self showAlertViewWithTitle:NSLocalizedString(@"signin_again_title", nil)
message:NSLocalizedString(@"signin_again_message", nil)
actionWithTitle:NSLocalizedString(@"close", nil)
handler:^(UIAlertAction *action) {
[self cancel];
}];
} else {
[self showAlertViewWithTitle:NSLocalizedString(@"server_connection_error_title", nil)
message:NSLocalizedString(@"server_connection_error_message", nil)
actionWithTitle:NSLocalizedString(@"close", nil)
handler:^(UIAlertAction *action) {
[self cancel];
}];
}
completionHandler(NO);
} else {
completionHandler(YES);
}
}];
}
}
- (void)pingWithCompletionHandler:(void (^)(NSError *))completionHandler {
[self.api ping:^(id data, NSURLResponse *response, NSError *error) {
NSLog(@"pingError: %@", error);
completionHandler(error);
}];
}
- (void)send {
NSMutableIndexSet *imageErrorIndexes = [NSMutableIndexSet indexSet];
for (NSUInteger i = 0; i < self.imageErrors.count; i++) {
NSError *error = self.imageErrors[i];
if ([error isKindOfClass:[NSError class]]) {
[imageErrorIndexes addIndex:i];
}
}
NSMutableArray *mutableImageUrls = [NSMutableArray arrayWithArray:self.imageUrls];
NSMutableArray *mutableCaptions = [NSMutableArray arrayWithArray:self.captions];
[mutableImageUrls removeObjectsAtIndexes:imageErrorIndexes];
[mutableCaptions removeObjectsAtIndexes:imageErrorIndexes];
if (mutableImageUrls.count == 0) {
NSError *error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier]
code:0
userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"upload_error_no_files_selected", nil)
}];
[self handleUploadError:error];
return;
}
//process image before upload
//if user choose optimize photo size
if( [self isOptimizeResolution ]){
//optimize photo size
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
SDImageCache *imageCache = [SDImageCache sharedImageCache];
//store images to /tmp
NSString *defaultCachePath = NSTemporaryDirectory();
//nix opt. size
float optimizedDimension = 1820.0f;
NSNumber *maxWidth = [NSNumber numberWithFloat:optimizedDimension];
for (NSUInteger i = 0; i < mutableImageUrls.count; i++) {
@autoreleasepool {
NSURL *imageUrl = mutableImageUrls[i];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, nil);
// load the image at the desired size
NSDictionary* options = @{
(id)kCGImageSourceShouldAllowFloat: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageAlways: (id)kCFBooleanTrue,
(id)kCGImageSourceThumbnailMaxPixelSize: maxWidth
};
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
//80% jpg quality
NSData * data = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.8f);
//retrieve cache path from Table view SDImageCache
NSURL* cachePathURL = [[NSURL alloc] initWithString:[imageCache cachePathForKey:[imageUrl absoluteString]]] ;
NSURL* newPathURL = [[NSURL alloc] initWithString:
[NSString stringWithFormat:@"%@/%@.JPG",defaultCachePath, [cachePathURL lastPathComponent]]];
NSDictionary *metaoptions = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)metaoptions);
if (imageProperties) {
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionaryWithDictionary:metadata];
[mutableMetadata setValue:[NSNumber numberWithInt:1] forKey:@"Orientation"];
[self writeData: [self writeMetadataIntoImageData:data metadata:mutableMetadata ] toPath: [newPathURL absoluteString]];
}
else{
//wirte to file
[self writeData: data toPath: [newPathURL absoluteString]];
}
//replace imageURLs object
[mutableImageUrls replaceObjectAtIndex:i withObject:[newPathURL absoluteString] ];
}
}
[self.uploadHandler uploadWithImageUrls:[mutableImageUrls copy]
captions:[mutableCaptions copy]
playlists:self.playlistIds
friends:self.friendUsernames
camera:NO];
});
}
else{
[mutableImageUrls enumerateObjectsUsingBlock:^(NSURL* url, NSUInteger idx, BOOL * _Nonnull stop) {
[mutableImageUrls replaceObjectAtIndex:idx withObject:[url absoluteString]];
}];
[self.uploadHandler uploadWithImageUrls:[mutableImageUrls copy]
captions:[mutableCaptions copy]
playlists:self.playlistIds
friends:self.friendUsernames
camera:NO];
}
}
- (void)cancel {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:^{
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was cancelled.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"Operation was cancelled.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Operation was cancelled.", nil)
};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:-57
userInfo:userInfo];
[self.extensionContext cancelRequestWithError:error];
}];
});
}
#pragma mark - Private
- (BOOL)tokenExists {
return [[self.api getAccessToken] length];
}
- (void)handleUploadError:(NSError *)error {
if (error) {
UINavigationController *navigationController = (UINavigationController *)self.presentedViewController;
ReceiverTableViewController *receiverTableVC = (ReceiverTableViewController *)navigationController.topViewController;
if ([receiverTableVC isKindOfClass:[ReceiverTableViewController class]]) {
[receiverTableVC hideActivityIndicator];
receiverTableVC.navigationItem.rightBarButtonItem.enabled = YES;
}
NSString *errorMessage = [error localizedDescription];
switch (error.code) {
case kNixIoFileTooLargeError:
errorMessage = NSLocalizedString(@"upload_error_file_too_large", nil);
break;
}
[self showAlertViewWithTitle:NSLocalizedString(@"upload_error_title", nil)
message:errorMessage
actionWithTitle:NSLocalizedString(@"close", nil)
handler:^(UIAlertAction *action) {
[self cancel];
}];
}
//delete cache image
[self deleteCache];
}
-(BOOL) isOptimizeResolution {
NSUserDefaults *preferences = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.creedon.Nixplay"] ;
NSString *optimizeResolutionKey = @"nixSettings.settings.resolution";
if ([preferences objectForKey:optimizeResolutionKey] == nil)
{
NSLog(@"%@ not exist",optimizeResolutionKey);
return false;
}
else
{
// Get current level
const Boolean optimizeResolution = [preferences boolForKey:optimizeResolutionKey];
NSLog(@"currentLevel %i",optimizeResolution);
return optimizeResolution;
}
return false;
}
- (BOOL)writeData:(NSData*) data toPath:(NSString *)path
{
NSError* errorWrite = nil;
BOOL write = [data writeToFile:path atomically:YES];
if(errorWrite != NULL){
NSLog(@"Failed to write to '%@': %@", path , [errorWrite localizedDescription]);
}
return write;
}
-(void)deleteCache{
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in tmpDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
}
}
//http://stackoverflow.com/questions/9006759/how-to-write-exif-metadata-to-an-image-not-the-camera-roll-just-a-uiimage-or-j
-(NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSMutableDictionary *)metadata {
// create an imagesourceref
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
// this is the type of image (e.g., public.jpeg)
CFStringRef UTI = CGImageSourceGetType(source);
// create a new data object and write the new image into it
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
if (!destination) {
NSLog(@"Error: Could not create image destination");
}
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadata);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(@"Error: Could not create data from image destination");
}
CFRelease(destination);
CFRelease(source);
return dest_data;
}
#pragma mark - NixUploadDelegate
- (void)didStartUploadWithTasks:(NSArray *)tasks {
[self dismissViewControllerAnimated:YES completion:^{
[self.extensionContext completeRequestReturningItems:@[]
completionHandler:nil];
}];
}
- (void)didUploadImageAtIndex:(NSUInteger)index error:(NSError *)error {
[self handleUploadError:error];
}
- (void)didFinishUploadingWithError:(NSError *)error {
[self handleUploadError:error];
}
-(void) showAlertViewWithTitle:(NSString*)title message:(NSString*)message actionWithTitle:actiontitle handler:(void (^ __nullable)(UIAlertAction *action))handler{
UIAlertController* alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self cancel];
}];
[alert addAction:defaultAction];
[self.svc presentViewController:alert animated:YES completion:nil];
}
@end

View File

@@ -0,0 +1,32 @@
//
// ShareViewController.h
// PhotoShareExtension
//
// Created by Russell on 28/4/15.
//
//
#import <UIKit/UIKit.h>
#import <Social/Social.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "ReceiverTableViewController.h"
#import "ContainerViewController.h"
FOUNDATION_EXPORT NSString *const kNixAppUrlScheme;
FOUNDATION_EXPORT NSString *const kNixAppUrlActionPhotoShare;
typedef NSString *kNixAppUrlAction;
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyImageUrl;
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyCaption;
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyFrameId;
typedef NSString *kNixAppUrlQueryKey;
FOUNDATION_EXPORT NSUInteger const kNixCaptionMaxLength;
FOUNDATION_EXPORT NSUInteger const kNixInputFieldCells;
@interface ImageCaptionViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextViewDelegate>
@property (nonatomic, strong) ContainerViewController *containerViewController;
- (IBAction)cancel:(id)sender;
@end

View File

@@ -0,0 +1,399 @@
//
// ShareViewController.m
// PhotoShareExtension
//
// Created by Russell on 27/4/15.
//
//
#import "ImageCaptionViewController.h"
#import <ImageIO/ImageIO.h>
#import <SDWebImage/UIImageView+WebCache.h>
NSString *const kNixAppUrlScheme = @"nixplay";
NSString *const kNixAppUrlActionPhotoShare = @"photo_share";
NSString *const kNixAppUrlQueryKeyImageUrl = @"image_url";
NSString *const kNixAppUrlQueryKeyCaption = @"caption";
NSString *const kNixAppUrlQueryKeyFrameId = @"frame_id";
NSUInteger const kNixCaptionMaxLength = 160;
NSUInteger const kNixInputFieldCells = 0;
@interface ImageCaptionViewController ()
@property (nonatomic, strong) NSMutableArray *imageErrors;
@property (nonatomic, strong) NSArray *imageUrls;
@property (nonatomic, strong) NSMutableArray *captions;
@property (nonatomic, strong) NSNumber *selectedFrameId;
@property (nonatomic, strong) NSString *selectedFrameName;
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@property (nonatomic, strong) NSIndexPath *activeCellIndexPath;
@property (nonatomic, assign) BOOL keyboardShown;
@property (nonatomic, assign) CGFloat keyboardOverlap;
- (void)loadExtensionItems;
- (void)validateImages;
- (void)loadImagesIntoCache;
@end
@interface FrameTableViewCell : UITableViewCell
@end
@interface ImageCaptionTableViewCell : UITableViewCell
@property (nonatomic, strong) NSError *error;
@property (nonatomic, strong) IBOutlet UIImageView *thumbnailView;
//@property (nonatomic, strong) IBOutlet UITextView *textView;
@property (nonatomic, strong) IBOutlet UITextView *textView;
@property (nonatomic, strong) IBOutlet UILabel *errorLabel;
@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *activityIndicatorView;
@end
@implementation ImageCaptionViewController
- (ContainerViewController*)containerViewController {
if (_containerViewController == nil) {
_containerViewController = [[ContainerViewController alloc] init];
}
return _containerViewController;
}
#pragma mark - IB
- (IBAction)cancel:(id)sender {
[self.containerViewController cancel];
}
#pragma mark - UIViewController
- (void)loadView {
[super loadView];
[self loadExtensionItems];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
[self verifyUser];
}
-(void)appWillBecomeActive:(NSNotification*)note {
NSLog(@"appWillBecomeActive: %@", note);
// re-validate again if the user is active logged-in
[self verifyUser];
}
-(void)appWillResignActive:(NSNotification*)note {
NSLog(@"appWillResignActive: %@", note);
}
-(void)appWillTerminate:(NSNotification*)note {
NSLog(@"appWillTerminate: %@", note);
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
/*
* This works around a quirk on iOS version < 8.3 where the contents of the table do not load (after the VC has been
* loaded once) on subsequent loads until the user scrolls the tableview.
*/
[self.tableView reloadData];
[self loadImagesIntoCache];
// [self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
// dispatch_async(dispatch_get_main_queue(), ^{
// self.navigationItem.rightBarButtonItem.enabled = success;
// });
// }]; // must be in viewWillAppear (doesn't work in viewDidAppear)
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"pushToPlaylistsTableView"]) {
[self.view endEditing:YES];
ReceiverTableViewController *playlistTableVC = (ReceiverTableViewController *)segue.destinationViewController;
playlistTableVC.containerViewController = self.containerViewController;
self.containerViewController.imageErrors = [self.imageErrors copy];
self.containerViewController.imageUrls = [self.imageUrls copy];
self.containerViewController.captions = [self.captions copy];
}
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"])
{
[textView resignFirstResponder];
return NO;
}
if ([text isEqualToString:@""])
return YES;
return textView.text.length < kNixCaptionMaxLength;
}
- (void)textViewDidEndEditing:(UITextView *)textView {
NSUInteger index = textView.tag;
self.captions[index] = textView.text;
self.activeCellIndexPath = nil;
}
- (void)textViewDidBeginEditing:(UITextView *)textView {
self.activeCellIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.imageUrls count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
NSUInteger imageIndex = indexPath.row;
cell = [tableView dequeueReusableCellWithIdentifier:@"ImageCaptionCell"
forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSURL *imageUrl = self.imageUrls[indexPath.row];
ImageCaptionTableViewCell *imageCaptionCell = (ImageCaptionTableViewCell *)cell;
imageCaptionCell.thumbnailView.contentMode = UIViewContentModeScaleAspectFit;
imageCaptionCell.thumbnailView.image = [[SDImageCache sharedImageCache]
imageFromMemoryCacheForKey:[imageUrl absoluteString]];
if (imageCaptionCell.thumbnailView.image) {
[imageCaptionCell.activityIndicatorView stopAnimating];
}
imageCaptionCell.textView.tag = imageIndex;
imageCaptionCell.textView.delegate = self;
NSError *imageError;
if (imageIndex < self.imageErrors.count) {
imageError = self.imageErrors[imageIndex];
}
imageCaptionCell.errorLabel.hidden = YES;
imageCaptionCell.textView.hidden = YES;
if (![imageError isKindOfClass:[NSNull class]]) {
imageCaptionCell.errorLabel.hidden = NO;
imageCaptionCell.errorLabel.text = [imageError localizedDescription];
} else {
imageCaptionCell.textView.hidden = NO;
imageCaptionCell.textView.layer.borderWidth = 0.5f;
imageCaptionCell.textView.layer.borderColor = [[UIColor lightGrayColor] CGColor];
imageCaptionCell.textView.layer.cornerRadius = 2.0f;
imageCaptionCell.textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping;
}
NSString *caption = [self.captions objectAtIndex:imageIndex];
if ([caption isKindOfClass:[NSString class]])
imageCaptionCell.textView.text = caption;
else
imageCaptionCell.textView.text = @"";
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath{
// @try{
// UIImage *image = [[SDImageCache sharedImageCache]
// imageFromMemoryCacheForKey:[self.imageUrls[indexPath.row] absoluteString]];
// CGFloat height = image.size.height > image.size.width ? image.size.height : 256;
// return height > 0 ? height : 256;
// }
// @catch(NSException * exception){
// NSLog(@"estimatedHeightForRowAtIndexPath Exception %@",exception);
// }
return 300;// UITableViewAutomaticDimension;
}
#pragma mark - Private
#pragma mark App flow
- (void)loadExtensionItems {
NSExtensionItem *extensionItem = self.containerViewController.extensionContext.inputItems.firstObject;
NSArray *inputItems = extensionItem.attachments;
NSMutableArray *mutableImageUrls = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
// Load images from selection
[inputItems enumerateObjectsUsingBlock:^(NSItemProvider *itemProvider, NSUInteger idx, BOOL *stop) {
dispatch_group_enter(group);
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage
options:nil
completionHandler: ^(NSURL *imageUrl, NSError *error) {
[mutableImageUrls addObject:imageUrl];
dispatch_group_leave(group);
}];
} else {
dispatch_group_leave(group);
}
}];
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.imageUrls = [NSArray arrayWithArray:mutableImageUrls];
NSUInteger imagesCount = [self.imageUrls count];
self.imageErrors = [NSMutableArray arrayWithCapacity:imagesCount];
self.captions = [[NSMutableArray alloc] initWithCapacity:imagesCount];
for (NSUInteger i = 0; i < imagesCount; i++) {
[self.captions addObject:[NSNull null]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self validateImages];
[self.tableView reloadData];
});
});
}
-(BOOL) isOptimizeResolution {
NSUserDefaults *preferences = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.creedon.Nixplay"] ;
NSString *optimizeResolutionKey = @"nixSettings.settings.resolution";
if ([preferences objectForKey:optimizeResolutionKey] == nil)
{
NSLog(@"%@ not exist",optimizeResolutionKey);
return false;
}
else
{
// Get current level
const Boolean optimizeResolution = [preferences boolForKey:optimizeResolutionKey];
NSLog(@"currentLevel %i",optimizeResolution);
return optimizeResolution;
}
return false;
}
- (void)validateImages {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
for (NSUInteger i = 0; i < self.imageUrls.count; i++) {
@autoreleasepool {
NSURL *imageUrl = self.imageUrls[i];
NSDictionary *fileAttrs = [fm attributesOfItemAtPath:[imageUrl path] error:nil];
NSNumber *fileSizeLimit = self.containerViewController.uploadHandler.fileSizeLimit;
NSNumber *fileSize = [fileAttrs objectForKey:NSFileSize];
NSError *fileError;
//https://www.wrike.com/open.htm?id=190326881
//allow proess photo when isOptimizeResolution
if(![self isOptimizeResolution]){
if ([fileSize longLongValue] > [fileSizeLimit longLongValue]) {
fileError = [NSError errorWithDomain:bundleIdentifier
code:kNixIoFileTooLargeError
userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"selection_error_file_too_large", nil)
}];
}
}
if (fileError) {
[self.imageErrors addObject:fileError];
} else {
[self.imageErrors addObject:[NSNull null]];
}
}
}
}
- (void)loadImagesIntoCache {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
SDImageCache *imageCache = [SDImageCache sharedImageCache];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat screenScale = [[UIScreen mainScreen] scale];
NSNumber *maxWidth = [NSNumber numberWithFloat:screenBounds.size.width * screenScale * 0.75];
// Create thumbnail and save to cache
for (NSUInteger i = 0; i < self.imageUrls.count; i++) {
@autoreleasepool {
NSURL *imageUrl = self.imageUrls[i];
if (![imageCache imageFromMemoryCacheForKey:[imageUrl absoluteString]]) {
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, nil);
NSDictionary *options = @{
(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageAlways: (id)kCFBooleanTrue,
(id)kCGImageSourceThumbnailMaxPixelSize: (id)maxWidth
};
CGImageRef thumbnailRef = CGImageSourceCreateThumbnailAtIndex(imageSource,
0,
(__bridge CFDictionaryRef)options);
UIImage *scaledImage = [UIImage imageWithCGImage:thumbnailRef];
[imageCache storeImage:scaledImage forKey:[imageUrl absoluteString] completion:^{
}];
}
}
}
// Update visible cells with images
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *visibleIndexPaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *visibleIndexPath in visibleIndexPaths) {
@autoreleasepool {
NSUInteger i = visibleIndexPath.row;
NSURL *imageUrl = self.imageUrls[i];
// Set up NSError to insert into cell
dispatch_async(dispatch_get_main_queue(), ^{
ImageCaptionTableViewCell *cell = (ImageCaptionTableViewCell *)[self.tableView
cellForRowAtIndexPath:visibleIndexPath];
if (!cell.thumbnailView.image) {
cell.thumbnailView.alpha = 0.f;
cell.thumbnailView.image = [imageCache imageFromMemoryCacheForKey:[imageUrl absoluteString]];
[UIView animateWithDuration:0.35f animations:^{
cell.thumbnailView.alpha = 1.f;
}];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[visibleIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
[cell.activityIndicatorView stopAnimating];
});
}
}
});
});
}
#pragma mark - User
- (void) verifyUser {
[self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem.enabled = success;
});
}]; // must be in viewWillAppear (doesn't work in viewDidAppear)
}
@end
#pragma mark -
@implementation FrameTableViewCell
@end
@implementation ImageCaptionTableViewCell
@end

View File

@@ -0,0 +1,16 @@
//
// SLComposeTableViewController.h
// PhotoShareExtension
//
// Created by Russell on 8/5/15.
//
//
#import <UIKit/UIKit.h>
#import "ContainerViewController.h"
@interface ReceiverTableViewController : UITableViewController
@property (nonatomic, strong) ContainerViewController *containerViewController;
- (void)showActivityIndicator;
- (void)hideActivityIndicator;
@end

View File

@@ -0,0 +1,556 @@
//
// SLComposeTableViewController.m
// PhotoShareExtension
//
// Created by Russell on 8/5/15.
//
//
#import "ReceiverTableViewController.h"
typedef NS_ENUM(NSUInteger, NixTableViewSection) {
kPlaylistHeader,
kPlaylist,
kFriendHeader,
kFriend
};
@interface ReceiverTableViewController ()
@property (nonatomic, strong) NSArray *playlistValues;
@property (nonatomic, strong) NSArray *friendValues;
@property (nonatomic, strong) NSMutableArray *selectedPlaylists;
@property (nonatomic, strong) NSMutableArray *selectedFriends;
- (IBAction)send:(id)sender;
- (void)preparePlaylistValues;
- (void)prepareFriendValues;
- (void)reloadData;
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends;
- (void)updateHeaderTitleWithCounts;
- (void)loadAllData:(void (^)(NSError *))completionHandler;
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler;
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler;
@end
@implementation ReceiverTableViewController
#pragma mark - IB
- (IBAction)send:(id)sender {
self.selectedPlaylists = [[NSMutableArray alloc]
initWithCapacity:self.containerViewController.playlistData.count];
self.selectedFriends = [[NSMutableArray alloc]
initWithCapacity:self.containerViewController.friendData.count];
NSArray *selectedIndexPaths = [self.tableView indexPathsForSelectedRows];
[selectedIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
@autoreleasepool {
switch (indexPath.section) {
case kPlaylist:
{
NSString *playlistId = self.containerViewController.playlistData[indexPath.row][@"id"];
[self.selectedPlaylists addObject:playlistId];
break;
}
case kFriend:
{
NSString *friendUsername = self.containerViewController.friendData[indexPath.row][@"username"];
[self.selectedFriends addObject:friendUsername];
break;
}
default:
break;
}
}
}];
self.containerViewController.playlistIds = [self.selectedPlaylists copy];
self.containerViewController.friendUsernames = [self.selectedFriends copy];
[self showActivityIndicator];
self.navigationItem.rightBarButtonItem.enabled = NO;
self.navigationItem.hidesBackButton = YES;
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
[self.containerViewController send];
}
#pragma mark - Navigation
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.tableView.editing = YES;
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(reloadData) forControlEvents:UIControlEventValueChanged];
if (!self.containerViewController.playlistData || !self.containerViewController.friendData) {
[self showActivityIndicator];
[self loadAllData:^(NSError *error) {
[self hideActivityIndicator];
if (error) {
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"signin_again_title", nil)
message:NSLocalizedString(@"signin_again_message", nil)
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self.containerViewController.extensionContext cancelRequestWithError:error];
}]];
[self presentViewController:alertController animated:YES completion:nil];
} else {
if (self.playlistValues.count == 0 && self.friendValues.count == 0) {
NSError *err;
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"no_receiver_title", nil)
message:NSLocalizedString(@"no_receiver_message", nil)
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self.containerViewController.extensionContext cancelRequestWithError:err];
}]];
[self presentViewController:alertController animated:YES completion:nil];
} else {
[self.tableView reloadData];
}
}
}];
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = NO;
NSArray *selectedReceiverIndexPaths = self.containerViewController.selectedReceiverIndexPaths;
[self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
if ([selectedReceiverIndexPaths count]) {
self.navigationItem.rightBarButtonItem.enabled = YES;
}
}];
if (self.containerViewController.playlistData && self.containerViewController.friendData) {
[self preparePlaylistValues];
[self prepareFriendValues];
[self.tableView reloadData];
if ([selectedReceiverIndexPaths count]) {
[selectedReceiverIndexPaths
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[self.tableView selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}];
[self updateHeaderTitleWithCounts];
}
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.friendValues = nil;
self.playlistValues = nil;
self.containerViewController.friendData = nil;
self.containerViewController.playlistData = nil;
self.containerViewController.selectedReceiverIndexPaths = nil; //[self.tableView indexPathsForSelectedRows];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Private
- (void)showActivityIndicator {
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.navigationItem.titleView = activityIndicator;
[activityIndicator startAnimating];
}
- (void)hideActivityIndicator {
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *)self.navigationItem.titleView;
if ([activityIndicator isKindOfClass:[UIActivityIndicatorView class]]) {
[activityIndicator stopAnimating];
}
self.navigationItem.titleView = nil;
}
- (void)preparePlaylistValues {
NSMutableArray *playlistValues = [NSMutableArray arrayWithCapacity:self.containerViewController.playlistData.count];
for (NSDictionary *record in self.containerViewController.playlistData) {
@autoreleasepool {
NSString *name = record[@"name"];
[playlistValues addObject:name];
}
}
self.playlistValues = playlistValues;
}
- (void)prepareFriendValues {
NSMutableArray *friendValues = [NSMutableArray arrayWithCapacity:self.containerViewController.friendData.count];
for (NSDictionary *record in self.containerViewController.friendData) {
@autoreleasepool {
NSString *name = record[@"fullName"];
[friendValues addObject:name];
}
}
self.friendValues = friendValues;
}
- (void)reloadData {
NSArray *playlistData = [self.containerViewController.playlistData copy];
NSArray *friendData = [self.containerViewController.friendData copy];
NSMutableArray *selectedPlaylistIds = [[NSMutableArray alloc] initWithCapacity:playlistData.count];
NSMutableArray *selectedFriendUsernames = [[NSMutableArray alloc] initWithCapacity:friendData.count];
[[self.tableView indexPathsForSelectedRows]
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
switch (indexPath.section) {
case kPlaylist:
[selectedPlaylistIds addObject:playlistData[indexPath.row][@"id"]];
break;
case kFriend:
[selectedFriendUsernames addObject:friendData[indexPath.row][@"username"]];
break;
default:
break;
}
}];
[self loadAllData:^(NSError *error) {
[self.refreshControl endRefreshing];
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *freshPlaylistData = [self.containerViewController.playlistData copy];
NSArray *freshFriendData = [self.containerViewController.friendData copy];
[freshPlaylistData enumerateObjectsUsingBlock:^(NSDictionary *playlist, NSUInteger idx, BOOL *stop) {
@autoreleasepool {
if ([selectedPlaylistIds containsObject:playlist[@"id"]]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kPlaylist];
[self.tableView selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
}
}];
[freshFriendData enumerateObjectsUsingBlock:^(NSDictionary *friend, NSUInteger idx, BOOL *stop) {
@autoreleasepool {
if ([selectedFriendUsernames containsObject:friend[@"username"]]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kFriend];
[self.tableView selectRowAtIndexPath:indexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
}
}];
});
}];
}
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends {
NSString *rawString;
if (playlists == 0) {
if (friends == 1) {
rawString = NSLocalizedString(@"navigation_header_n_selected_friend", nil);
} else {
rawString = NSLocalizedString(@"navigation_header_n_selected_friends", nil);
}
}
if (friends == 0) {
if (playlists == 1) {
rawString = NSLocalizedString(@"navigation_header_n_selected_playlist", nil);
} else {
rawString = NSLocalizedString(@"navigation_header_n_selected_playlists", nil);
}
}
if (0 < playlists && 0 < friends) {
rawString = NSLocalizedString(@"navigation_header_n_selected_receivers", nil);
}
if (playlists + friends == 0) {
return NSLocalizedString(@"navigation_header_select_receivers", nil);
}
return [NSString stringWithFormat:rawString, playlists + friends];
}
- (void)updateHeaderTitleWithCounts {
__block NSUInteger playlistCount = 0;
__block NSUInteger friendCount = 0;
[[self.tableView indexPathsForSelectedRows]
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
switch (indexPath.section) {
case kPlaylist:
playlistCount++;
break;
case kFriend:
friendCount++;
break;
}
}];
NSString *title = [self headerTitleWithPlaylistCounts:playlistCount andFriendCounts:friendCount];
self.navigationItem.title = title;
}
#pragma mark Network
- (void)loadAllData:(void (^)(NSError *))completionHandler {
__block NSError *error = nil;
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_enter(dispatchGroup);
[self loadPlaylistsWithCompletionHandler:^(NSError *playlistsError) {
if (playlistsError)
error = playlistsError;
dispatch_group_leave(dispatchGroup);
}];
dispatch_group_enter(dispatchGroup);
[self loadFriendsWithCompletionHandler:^(NSError *friendsError) {
if (friendsError)
error = friendsError;
dispatch_group_leave(dispatchGroup);
}];
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
completionHandler(error);
});
}
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler {
[self.containerViewController.api playlists:^(id data, NSURLResponse *response, NSError *error) {
if (error || !data || [data isKindOfClass:[NSDictionary class]]) {
completionHandler(error);
return;
}
if ([data isKindOfClass:[NSArray class]]) {
NSPredicate *predicateByTitle = [NSPredicate predicateWithFormat:@"SELF.type == %@", @"normal"];
NSArray *playlists = [(NSArray*)data filteredArrayUsingPredicate:predicateByTitle];
self.containerViewController.playlistData = playlists;
[self preparePlaylistValues];
[self.tableView reloadData];
}
completionHandler(nil);
}];
}
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler {
[self.containerViewController.api friends:^(NSDictionary *data, NSURLResponse *response, NSError *error) {
if (error || data[@"error"]) {
completionHandler(error);
return;
}
NSString *predString = [NSString stringWithFormat:@"(status='%@')", @"success"];
NSPredicate *pred = [NSPredicate predicateWithFormat:predString];
NSArray *friendsArr = [data[@"friends"] filteredArrayUsingPredicate:pred];
self.containerViewController.friendData = friendsArr;
[self prepareFriendValues];
[self.tableView reloadData];
completionHandler(nil);
}];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 4;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case kPlaylistHeader:
return self.playlistValues.count ? NSLocalizedString(@"table_header_playlist", nil) : nil;
case kFriendHeader:
return self.friendValues.count ? NSLocalizedString(@"table_header_friend", nil) : nil;
default:
return nil;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
switch (section) {
case kPlaylist:
return self.playlistValues.count;
case kFriend:
return self.friendValues.count;
case kPlaylistHeader:
return self.playlistValues.count ? 1 : 0;
case kFriendHeader:
return self.friendValues.count ? 1 : 0;
default:
return 0;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @"SLComposeTableViewControllerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
if ([[tableView indexPathsForSelectedRows] containsObject:indexPath]) {
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
switch (indexPath.section) {
case kPlaylist:
cell.textLabel.text = self.playlistValues[indexPath.row];
break;
case kFriend:
cell.textLabel.text = self.friendValues[indexPath.row];
break;
case kPlaylistHeader:
cell.textLabel.text = NSLocalizedString(@"table_select_all_playlists", nil);
break;
case kFriendHeader:
cell.textLabel.text = NSLocalizedString(@"table_select_all_friends", nil);
break;
default:
break;
}
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
}
*/
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case kPlaylistHeader:
{
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
[tableView selectRowAtIndexPath:playlistIndexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
break;
}
case kFriendHeader:
{
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
[tableView selectRowAtIndexPath:friendIndexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
break;
}
default:
break;
}
self.navigationItem.rightBarButtonItem.enabled = YES;
[self updateHeaderTitleWithCounts];
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case kPlaylistHeader:
{
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
[tableView deselectRowAtIndexPath:playlistIndexPath
animated:NO];
}
break;
}
case kFriendHeader:
{
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
[tableView deselectRowAtIndexPath:friendIndexPath
animated:NO];
}
break;
}
case kPlaylist:
{
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kPlaylistHeader];
[tableView deselectRowAtIndexPath:headerIndexPath
animated:NO];
break;
}
case kFriend:
{
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kFriendHeader];
[tableView deselectRowAtIndexPath:headerIndexPath
animated:NO];
break;
}
default:
break;
}
if (![tableView indexPathsForSelectedRows]) {
self.navigationItem.rightBarButtonItem.enabled = NO;
}
[self updateHeaderTitleWithCounts];
}
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end

View File

@@ -0,0 +1,108 @@
<?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>BundleEntryFilename</key>
<string>index.js</string>
<key>BundleForced</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</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>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSHasLocalizedDisplayName</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>cdn.shopify.com</key>
<dict>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>nixplay-prod-original.s3-us-west-2.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-prod-thumbnail.s3-us-west-2.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-prod-upload.s3.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-qa-original.s3-ap-northeast-1.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-qa-thumbnail.s3-ap-northeast-1.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-qa-upload.s3.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-rnd-original.s3-ap-southeast-1.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-rnd-thumbnail.s3-ap-southeast-1.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>nixplay-rnd-upload.s3.amazonaws.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<key>shop.nixplay.com</key>
<dict>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?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>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.creedon.Nixplay.alpha</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.creedon.Nixplay.alpha</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?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>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.creedon.Nixplay.beta</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.creedon.Nixplay.beta</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?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>aps-environment</key>
<string>production</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.creedon.Nixplay</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.creedon.Nixplay</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,48 @@
//
// NixShareExtension.m
// NixShareExtension
//
// Created by Haroon Shahid on 30/4/2018.
// Copyright © 2018 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ReactNativeShareExtension.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLog.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTBundleURLProvider.h>
#import <Foundation/Foundation.h>
//#import "ReactNativeShareExtension.h"
@interface NixShareExtension : ReactNativeShareExtension
@end
@implementation NixShareExtension
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (UIView*) shareView {
NSURL *jsCodeLocation;
#ifdef DEBUG
// jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"NixShareExtension"
initialProperties:nil
launchOptions:nil];
rootView.backgroundColor = nil;
return rootView;
}
@end

View File

@@ -0,0 +1,16 @@
<?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>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.creedon.Nixplay.qa</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.creedon.Nixplay.qa</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?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>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.creedon.Nixplay.rnd</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.com.creedon.Nixplay.rnd</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,50 @@
/*
Localizable.strings
PhotoShareExtension
Created by Russell on 19/5/15.
*/
"send_to" = "Send To";
"open_app" = "Open App";
"close" = "Schließen";
"none" = "None";
"frame" = "Rahmen";
"navigation_header_n_selected_playlist" = "%tu Playlist ausgewählt";
"navigation_header_n_selected_playlists" = "%tu Playlists ausgewählt";
"navigation_header_n_selected_friend" = "%tu Freund ausgewählt";
"navigation_header_n_selected_friends" = "%tu Freund ausgewählt";
"navigation_header_n_selected_receivers" = "%tu Empfänger ausgewählt";
"navigation_header_select_receivers" = "Empfänger ausgewählt";
"table_header_playlist" = "MEINE PLAYLISTS";
"table_header_friend" = "MEINE FREUNDE";
"table_select_all_playlists" = "Alle Playlists auswählen";
"table_select_all_friends" = "Alle Freunde auswählen";
"app_never_launched_title" = "Kein Nixplay-Konto";
"app_never_launched_message" = "Please launch the Nixplay app for the first time to log in to your Nixplay account.";
"no_account_title" = "Kein Nixplay-Konto";
"no_account_message" = "Es wurde kein Nixplay-Konto konfiguriert. Sie können sich in der Nixplay-App in Ihrem Nixplay-Konto einloggen.";
"signin_again_title" = "Error";
"signin_again_message" = "Sie haben sich eine Weile nicht in Ihrem Nixplay-Konto eingeloggt. Bitte nutzen Sie die Nixplay-App, um sich wieder einzuloggen.";
"no_receiver_title" = "Error";
"no_receiver_message" = "Sie haben keine Freunde und keine Playlists in Ihrem Nixplay-Konto.";
"upload_in_progress_title" = "Hochladen wird durchgeführt";
"upload_in_progress_message" = "Ihre Fotos werden hochgeladen. Versuchen Sie es später noch einmal.";
"server_connection_error_title" = "Keine Internetverbindung";
"server_connection_error_message" = "Prüfen Sie Ihre Geräteverbindung und versuchen Sie es später noch einmal.";
"upload_error_title" = "Error";
"upload_error_file_too_large" = "A file you selected is too large.";
"upload_error_no_files_selected" = "There are no files to upload. Please try again.";
"selection_error_file_too_large" = "This file is too large and will not be sent.";

View File

@@ -0,0 +1,21 @@
/* Class = "UINavigationItem"; title = "Enter Captions"; ObjectID = "9gy-vt-gU0"; */
"9gy-vt-gU0.title" = "Bildtitel eingeben";
/* Class = "UIBarButtonItem"; title = "Back"; ObjectID = "ARf-0c-syw"; */
"ARf-0c-syw.title" = "Zurück";
/* Class = "UIBarButtonItem"; title = "Cancel"; ObjectID = "Oz8-8U-jBt"; */
"Oz8-8U-jBt.title" = "Abbrechen";
/* Class = "UIBarButtonItem"; title = "Send"; ObjectID = "jty-bC-dNd"; */
"jty-bC-dNd.title" = "Senden";
/* Class = "UIBarButtonItem"; title = "Next"; ObjectID = "liG-JY-nUM"; */
"liG-JY-nUM.title" = "Weiter";
/* Class = "UILabel"; text = "Label"; ObjectID = "rdb-lo-ZfB"; */
"rdb-lo-ZfB.text" = "Label";
/* Class = "UINavigationItem"; title = "Select Receivers"; ObjectID = "zBq-p7-QR9"; */
"zBq-p7-QR9.title" = "Empfänger auswählen";

View File

@@ -0,0 +1,53 @@
/*
Localizable.strings
PhotoShareExtension
Created by Russell on 19/5/15.
*/
"send_to" = "Send To";
"open_app" = "Open App";
"close" = "Close";
"none" = "None";
"frame" = "Frame";
"navigation_header_n_selected_playlist" = "%tu Playlist Selected";
"navigation_header_n_selected_playlists" = "%tu Playlists Selected";
"navigation_header_n_selected_friend" = "%tu Friend Selected";
"navigation_header_n_selected_friends" = "%tu Friends Selected";
"navigation_header_n_selected_receivers" = "%tu Receivers Selected";
"navigation_header_select_receivers" = "Select Receivers";
"table_header_playlist" = "My Playlists";
"table_header_friend" = "My Friends";
"table_select_all_playlists" = "Select all playlists";
"table_select_all_friends" = "Select all friends";
"app_never_launched_title" = "No Nixplay Account";
"app_never_launched_message" = "Please launch the Nixplay app for the first time to log in to your Nixplay account.";
"no_account_title" = "No Nixplay Account";
"no_account_message" = "There is no Nixplay account configured. You can log in to your Nixplay account in the Nixplay app.";
"signin_again_title" = "Error";
"signin_again_message" = "You have not logged in to your Nixplay account in awhile. Please log in again using the Nixplay app.";
"no_receiver_title" = "Error";
"no_receiver_message" = "You have no friend and playlist in to your Nixplay account.";
"upload_in_progress_title" = "Upload in Progress";
"upload_in_progress_message" = "Your photos are being uploaded. Please try again later.";
"server_connection_error_title" = "No Internet Connection";
"server_connection_error_message" = "Please check your device connection and try again later.";
"upload_error_title" = "Error";
"upload_error_file_too_large" = "A file you selected is too large.";
"upload_error_no_files_selected" = "There are no files to upload. Please try again.";
"selection_error_file_too_large" = "This file is too large and will not be sent.";
"Oz8-8U-jBt.normalTitle" = "Cancel";
"Oz8-8U-jBt" = "Cancel";

View File

@@ -0,0 +1,21 @@
/* Class = "UINavigationItem"; title = "Enter Captions"; ObjectID = "9gy-vt-gU0"; */
"9gy-vt-gU0.title" = "Enter Captions";
/* Class = "UIBarButtonItem"; title = "Back"; ObjectID = "ARf-0c-syw"; */
"ARf-0c-syw.title" = "Back";
/* Class = "UIBarButtonItem"; title = "Cancel"; ObjectID = "Oz8-8U-jBt"; */
"Oz8-8U-jBt.title" = "Cancel";
/* Class = "UIBarButtonItem"; title = "Send"; ObjectID = "jty-bC-dNd"; */
"jty-bC-dNd.title" = "Send";
/* Class = "UIBarButtonItem"; title = "Next"; ObjectID = "liG-JY-nUM"; */
"liG-JY-nUM.title" = "Next";
/* Class = "UILabel"; text = "Label"; ObjectID = "rdb-lo-ZfB"; */
"rdb-lo-ZfB.text" = "Label";
/* Class = "UINavigationItem"; title = "Select Receivers"; ObjectID = "zBq-p7-QR9"; */
"zBq-p7-QR9.title" = "Select Receivers";

View File

@@ -0,0 +1,53 @@
/*
Localizable.strings
PhotoShareExtension
Created by Russell on 19/5/15.
*/
"send_to" = "送信先";
"open_app" = "アプリを開く";
"close" = "閉じる";
"none" = "なし";
"frame" = "フレーム";
"navigation_header_n_selected_playlist" = "%tu個の再生リストを選択しました";
"navigation_header_n_selected_playlists" = "%tu個の再生リストを選択しました";
"navigation_header_n_selected_friend" = "%tu名の友達を選択しました";
"navigation_header_n_selected_friends" = "%tu名の友達を選択しました";
"navigation_header_n_selected_receivers" = "%tu名の友達を選択しました";
"navigation_header_select_receivers" = "レシーバーを選択";
"table_header_playlist" = "マイ再生リスト";
"table_header_friend" = "マイフレンド";
"table_select_all_playlists" = "すべての再生リスト選択";
"table_select_all_friends" = "すべての友達を選択";
"app_never_launched_title" = "MAAGAppアカウントなし";
"app_never_launched_message" = "MAAGAppアカウントにログインするために、最初にMAAGAppアプリを起動させてください。";
"no_account_title" = "MAAGAppアカウントなし";
"no_account_message" = "MAAGAppアカウントは設定されていません。MAAGAppアプリのMAAGAppアカウントにログインできます。";
"signin_again_title" = "エラー";
"signin_again_message" = "しばらくの間、MAAGAppアカウントにログインしていませんでした。MAAGAppアプリを使ってもう一度ログインしてください。";
"no_receiver_title" = "エラー";
"no_receiver_message" = "MAAGAppアカウントには友達も再生リストもありません。";
"upload_in_progress_title" = "アップロード進行中";
"upload_in_progress_message" = "写真がアップロードされています。後でもう一度やり直してください。";
"server_connection_error_title" = "インターネット接続なし";
"server_connection_error_message" = "デバイス接続を確認して後でもう一度やり直してください。";
"upload_error_title" = "エラー";
"upload_error_file_too_large" = "選択したファイルは大きすぎます。";
"upload_error_no_files_selected" = "アップロードするファイルがありません。もう一度やり直してください。";
"selection_error_file_too_large" = "このファイルは大きすぎるので送信できません。";
"Oz8-8U-jBt.normalTitle" = "キャンセル";
"Oz8-8U-jBt" = "キャンセル";

View File

@@ -0,0 +1,21 @@
/* Class = "UINavigationItem"; title = "Enter Captions"; ObjectID = "9gy-vt-gU0"; */
"9gy-vt-gU0.title" = "Enter Captions";
/* Class = "UIBarButtonItem"; title = "Back"; ObjectID = "ARf-0c-syw"; */
"ARf-0c-syw.title" = "Back";
/* Class = "UIBarButtonItem"; title = "Cancel"; ObjectID = "Oz8-8U-jBt"; */
"Oz8-8U-jBt.title" = "Cancel";
/* Class = "UIBarButtonItem"; title = "Send"; ObjectID = "jty-bC-dNd"; */
"jty-bC-dNd.title" = "Send";
/* Class = "UIBarButtonItem"; title = "Next"; ObjectID = "liG-JY-nUM"; */
"liG-JY-nUM.title" = "Next";
/* Class = "UILabel"; text = "Label"; ObjectID = "rdb-lo-ZfB"; */
"rdb-lo-ZfB.text" = "Label";
/* Class = "UINavigationItem"; title = "Select Receivers"; ObjectID = "zBq-p7-QR9"; */
"zBq-p7-QR9.title" = "Select Receivers";