feat: You can now share files & text from other apps into SN on iOS (#2358)
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
|
||||
@interface RCT_EXTERN_MODULE(ReceiveSharingIntent, NSObject)
|
||||
|
||||
RCT_EXTERN_METHOD(getFileNames:(NSString)url
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter: (RCTPromiseRejectBlock)reject);
|
||||
|
||||
RCT_EXTERN_METHOD(clearFileNames)
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import Photos
|
||||
import MobileCoreServices
|
||||
|
||||
@objc(ReceiveSharingIntent)
|
||||
class ReceiveSharingIntent: NSObject {
|
||||
|
||||
struct Share: Codable {
|
||||
var media: [SharedMediaFile] = []
|
||||
var text: [String] = []
|
||||
var urls: [String] = []
|
||||
}
|
||||
|
||||
private var share = Share()
|
||||
|
||||
@objc
|
||||
func getFileNames(_ url: String,
|
||||
resolver resolve: RCTPromiseResolveBlock,
|
||||
rejecter reject: RCTPromiseRejectBlock
|
||||
) -> Void {
|
||||
let fileUrl = URL(string: url)
|
||||
let json = handleUrl(url: fileUrl);
|
||||
if(json == "error"){
|
||||
let error = NSError(domain: "", code: 400, userInfo: nil)
|
||||
reject("message", "file type is Invalid", error);
|
||||
}else if(json == "invalid group name"){
|
||||
let error = NSError(domain: "", code: 400, userInfo: nil)
|
||||
reject("message", "invalid group name. Please check your share extention bundle name is same as `group.mainbundle name` ", error);
|
||||
}else{
|
||||
resolve(json);
|
||||
}
|
||||
}
|
||||
|
||||
private func handleUrl(url: URL?) -> String? {
|
||||
if let url = url {
|
||||
let appDomain = Bundle.main.bundleIdentifier!
|
||||
let userDefaults = UserDefaults(suiteName: "group.\(appDomain)")
|
||||
if let key = url.host?.components(separatedBy: "=").last {
|
||||
if let mediaJson = userDefaults?.object(forKey: "\(key).media") as? Data {
|
||||
let mediaSharedArray = decode(data: mediaJson)
|
||||
let sharedMediaFiles: [SharedMediaFile] = mediaSharedArray.compactMap {
|
||||
guard let path = getAbsolutePath(for: $0.path) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return SharedMediaFile.init(path: path, fileName: fileNameForPath(path: path), mimeType: mimeTypeForPath(path: path))
|
||||
}
|
||||
self.share.media = sharedMediaFiles
|
||||
}
|
||||
if let textSharedArray = userDefaults?.object(forKey: "\(key).text") as? [String] {
|
||||
self.share.text = textSharedArray
|
||||
}
|
||||
if let textSharedArray = userDefaults?.object(forKey: "\(key).url") as? [String] {
|
||||
self.share.urls = textSharedArray
|
||||
}
|
||||
let encodedData = try? JSONEncoder().encode(self.share)
|
||||
let json = String(data: encodedData!, encoding: .utf8)!
|
||||
return json
|
||||
}
|
||||
return "error"
|
||||
}
|
||||
return "invalid group name"
|
||||
}
|
||||
|
||||
|
||||
private func getAbsolutePath(for identifier: String) -> String? {
|
||||
if (identifier.starts(with: "file://") || identifier.starts(with: "/var/mobile/Media") || identifier.starts(with: "/private/var/mobile")) {
|
||||
return identifier;
|
||||
}
|
||||
let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: .none).firstObject
|
||||
if(phAsset == nil) {
|
||||
return nil
|
||||
}
|
||||
let (url, _) = getFullSizeImageURLAndOrientation(for: phAsset!)
|
||||
return url
|
||||
}
|
||||
|
||||
private func getFullSizeImageURLAndOrientation(for asset: PHAsset)-> (String?, Int) {
|
||||
var url: String? = nil
|
||||
var orientation: Int = 0
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let options2 = PHContentEditingInputRequestOptions()
|
||||
options2.isNetworkAccessAllowed = true
|
||||
asset.requestContentEditingInput(with: options2){(input, info) in
|
||||
orientation = Int(input?.fullSizeImageOrientation ?? 0)
|
||||
url = input?.fullSizeImageURL?.path
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
return (url, orientation)
|
||||
}
|
||||
|
||||
private func decode(data: Data) -> [SharedMediaFile] {
|
||||
let encodedData = try? JSONDecoder().decode([SharedMediaFile].self, from: data)
|
||||
return encodedData!
|
||||
}
|
||||
|
||||
private func toJson(data: [SharedMediaFile]?) -> String? {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
let encodedData = try? JSONEncoder().encode(data)
|
||||
let json = String(data: encodedData!, encoding: .utf8)!
|
||||
return json
|
||||
}
|
||||
|
||||
class SharedMediaFile: Codable {
|
||||
var path: String;
|
||||
var fileName: String?;
|
||||
var mimeType: String?;
|
||||
|
||||
init(path: String, fileName: String?, mimeType: String?) {
|
||||
self.path = path
|
||||
self.fileName = fileName
|
||||
self.mimeType = mimeType
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func clearFileNames(){
|
||||
print("clearFileNames");
|
||||
}
|
||||
|
||||
|
||||
@objc
|
||||
static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func fileNameForPath(path: String) -> String? {
|
||||
let url = NSURL(fileURLWithPath: path)
|
||||
return url.lastPathComponent
|
||||
}
|
||||
|
||||
func mimeTypeForPath(path: String) -> String {
|
||||
let url = NSURL(fileURLWithPath: path)
|
||||
let pathExtension = url.pathExtension
|
||||
|
||||
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension! as NSString, nil)?.takeRetainedValue() {
|
||||
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
|
||||
return mimetype as String
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" 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>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
34
packages/mobile/ios/Share To SN/Info.plist
Normal file
34
packages/mobile/ios/Share To SN/Info.plist
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>PHSupportedMediaTypes</key>
|
||||
<array>
|
||||
<string>Video</string>
|
||||
<string>Image</string>
|
||||
</array>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>100</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>100</integer>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>100</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.standardnotes.standardnotes</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
336
packages/mobile/ios/Share To SN/ShareViewController.swift
Normal file
336
packages/mobile/ios/Share To SN/ShareViewController.swift
Normal file
@@ -0,0 +1,336 @@
|
||||
import UIKit
|
||||
import Social
|
||||
import MobileCoreServices
|
||||
import Photos
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
let hostAppBundleIdentifier = "com.standardnotes.standardnotes"
|
||||
let shareProtocol = hostAppBundleIdentifier
|
||||
|
||||
class ShareViewController: SLComposeServiceViewController {
|
||||
let sharedKey = "ShareKey"
|
||||
var sharedMedia: [SharedMediaFile] = []
|
||||
var sharedText: [String] = []
|
||||
var sharedURL: [String] = []
|
||||
let imageContentType = UTType.image.identifier
|
||||
let videoContentType = UTType.movie.identifier
|
||||
let textContentType = UTType.text.identifier
|
||||
let urlContentType = UTType.url.identifier
|
||||
let fileURLType = UTType.fileURL.identifier;
|
||||
|
||||
override func isContentValid() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad();
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
|
||||
if let contents = content.attachments {
|
||||
for (index, attachment) in (contents).enumerated() {
|
||||
if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
|
||||
handleFiles(content: content, attachment: attachment, index: index)
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
|
||||
handleImages(content: content, attachment: attachment, index: index)
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
|
||||
handleText(content: content, attachment: attachment, index: index)
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
|
||||
handleUrl(content: content, attachment: attachment, index: index)
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
|
||||
handleVideos(content: content, attachment: attachment, index: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didSelectPost() {
|
||||
print("didSelectPost");
|
||||
}
|
||||
|
||||
override func configurationItems() -> [Any]! {
|
||||
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
|
||||
return []
|
||||
}
|
||||
|
||||
private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
|
||||
attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let item = data as? String, let this = self {
|
||||
|
||||
this.sharedText.append(item)
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
this.redirectToHostApp()
|
||||
}
|
||||
|
||||
} else {
|
||||
self?.dismissWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
|
||||
attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let item = data as? URL, let this = self {
|
||||
|
||||
this.sharedURL.append(item.absoluteString)
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
this.redirectToHostApp()
|
||||
}
|
||||
|
||||
} else {
|
||||
self?.dismissWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
|
||||
attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let url = data as? URL, let this = self {
|
||||
// this.redirectToHostApp(type: .media)
|
||||
// Always copy
|
||||
let fileExtension = this.getExtension(from: url, type: .video)
|
||||
let newName = UUID().uuidString
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName).\(fileExtension)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if(copied) {
|
||||
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
this.redirectToHostApp()
|
||||
}
|
||||
|
||||
} else {
|
||||
self?.dismissWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
|
||||
attachment.loadItem(forTypeIdentifier: videoContentType, options:nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let url = data as? URL, let this = self {
|
||||
|
||||
// Always copy
|
||||
let fileExtension = this.getExtension(from: url, type: .video)
|
||||
let newName = UUID().uuidString
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName).\(fileExtension)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if(copied) {
|
||||
guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
|
||||
return
|
||||
}
|
||||
this.sharedMedia.append(sharedFile)
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
this.redirectToHostApp()
|
||||
}
|
||||
|
||||
} else {
|
||||
self?.dismissWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
|
||||
attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let url = data as? URL, let this = self {
|
||||
|
||||
// Always copy
|
||||
let newName = this.getFileName(from :url)
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if (copied) {
|
||||
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
this.redirectToHostApp()
|
||||
}
|
||||
|
||||
} else {
|
||||
self?.dismissWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func redirectToHostApp() {
|
||||
let userDefaults = UserDefaults(suiteName: "group.\(hostAppBundleIdentifier)")
|
||||
userDefaults?.set(self.toData(data: self.sharedMedia), forKey: "\(self.sharedKey).media")
|
||||
userDefaults?.set(self.sharedText, forKey: "\(self.sharedKey).text")
|
||||
userDefaults?.set(self.sharedURL, forKey: "\(self.sharedKey).url")
|
||||
userDefaults?.synchronize()
|
||||
|
||||
let url = URL(string: "\(shareProtocol)://dataUrl=\(sharedKey)")
|
||||
var responder = self as UIResponder?
|
||||
let selectorOpenURL = sel_registerName("openURL:")
|
||||
|
||||
while (responder != nil) {
|
||||
if (responder?.responds(to: selectorOpenURL))! {
|
||||
let _ = responder?.perform(selectorOpenURL, with: url)
|
||||
}
|
||||
responder = responder!.next
|
||||
}
|
||||
|
||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
|
||||
private func dismissWithError() {
|
||||
print("[ERROR] Error loading data!")
|
||||
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
|
||||
|
||||
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
alert.addAction(action)
|
||||
present(alert, animated: true, completion: nil)
|
||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
|
||||
private func alertLog(message: String) {
|
||||
let alert = UIAlertController(title: "Log", message: message, preferredStyle: .alert)
|
||||
|
||||
let action = UIAlertAction(title: "OK", style: .default)
|
||||
|
||||
alert.addAction(action)
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
enum RedirectType {
|
||||
case media
|
||||
case text
|
||||
case file
|
||||
}
|
||||
|
||||
func getExtension(from url: URL, type: SharedMediaType) -> String {
|
||||
let parts = url.lastPathComponent.components(separatedBy: ".")
|
||||
var ex: String? = nil
|
||||
if (parts.count > 1) {
|
||||
ex = parts.last
|
||||
}
|
||||
|
||||
if (ex == nil) {
|
||||
switch type {
|
||||
case .image:
|
||||
ex = "PNG"
|
||||
case .video:
|
||||
ex = "MP4"
|
||||
case .file:
|
||||
ex = "TXT"
|
||||
}
|
||||
}
|
||||
return ex ?? "Unknown"
|
||||
}
|
||||
|
||||
func getFileName(from url: URL) -> String {
|
||||
var name = url.lastPathComponent
|
||||
|
||||
if (name == "") {
|
||||
name = UUID().uuidString + "." + getExtension(from: url, type: .file)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: dstURL.path) {
|
||||
try FileManager.default.removeItem(at: dstURL)
|
||||
}
|
||||
try FileManager.default.copyItem(at: srcURL, to: dstURL)
|
||||
} catch (let error) {
|
||||
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
|
||||
let asset = AVAsset(url: forVideo)
|
||||
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
|
||||
let thumbnailPath = getThumbnailPath(for: forVideo)
|
||||
|
||||
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
|
||||
return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
|
||||
}
|
||||
|
||||
var saved = false
|
||||
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
|
||||
assetImgGenerate.appliesPreferredTrackTransform = true
|
||||
// let scale = UIScreen.main.scale
|
||||
assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
|
||||
do {
|
||||
let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
|
||||
try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
|
||||
saved = true
|
||||
} catch {
|
||||
saved = false
|
||||
}
|
||||
|
||||
return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
|
||||
|
||||
}
|
||||
|
||||
private func getThumbnailPath(for url: URL) -> URL {
|
||||
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
|
||||
let path = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(fileName).jpg")
|
||||
return path
|
||||
}
|
||||
|
||||
class SharedMediaFile: Codable {
|
||||
var path: String; // can be image, video or url path. It can also be text content
|
||||
var thumbnail: String?; // video thumbnail
|
||||
var duration: Double?; // video duration in milliseconds
|
||||
var type: SharedMediaType;
|
||||
|
||||
|
||||
init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
|
||||
self.path = path
|
||||
self.thumbnail = thumbnail
|
||||
self.duration = duration
|
||||
self.type = type
|
||||
}
|
||||
|
||||
// Debug method to print out SharedMediaFile details in the console
|
||||
func toString() {
|
||||
print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
|
||||
}
|
||||
}
|
||||
|
||||
enum SharedMediaType: Int, Codable {
|
||||
case image
|
||||
case video
|
||||
case file
|
||||
}
|
||||
|
||||
func toData(data: [SharedMediaFile]) -> Data {
|
||||
let encodedData = try? JSONEncoder().encode(data)
|
||||
return encodedData!
|
||||
}
|
||||
}
|
||||
|
||||
extension Array {
|
||||
subscript (safe index: UInt) -> Element? {
|
||||
return Int(index) < count ? self[Int(index)] : nil
|
||||
}
|
||||
}
|
||||
2
packages/mobile/ios/StandardNotes-Bridging-Header.h
Normal file
2
packages/mobile/ios/StandardNotes-Bridging-Header.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTViewManager.h>
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00E356F31AD99517003FC87E /* StandardNotesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* StandardNotesTests.m */; };
|
||||
07CAB75E2A618128008FE1EF /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB75D2A618128008FE1EF /* ShareViewController.swift */; };
|
||||
07CAB7612A618128008FE1EF /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07CAB75F2A618128008FE1EF /* MainInterface.storyboard */; };
|
||||
07CAB7652A618128008FE1EF /* Share To SN.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 07CAB75B2A618128008FE1EF /* Share To SN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
07CAB76E2A618353008FE1EF /* ReceiveSharingIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */; };
|
||||
07CAB7702A618385008FE1EF /* ReceiveSharingIntent.m in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
1C2EEB3B45F4EB07AC795C77 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
|
||||
@@ -34,13 +39,43 @@
|
||||
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
|
||||
remoteInfo = StandardNotes;
|
||||
};
|
||||
07CAB7632A618128008FE1EF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 07CAB75A2A618128008FE1EF;
|
||||
remoteInfo = "Share To SN";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
07CAB7662A618128008FE1EF /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
07CAB7652A618128008FE1EF /* Share To SN.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StandardNotesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* StandardNotesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandardNotesTests.m; sourceTree = "<group>"; };
|
||||
04FCB5A3A3387CA3CFC82AA3 /* libPods-StandardNotes-StandardNotesTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-StandardNotes-StandardNotesTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
07CAB75B2A618128008FE1EF /* Share To SN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share To SN.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
07CAB75D2A618128008FE1EF /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
07CAB7602A618128008FE1EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
07CAB7622A618128008FE1EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
07CAB76A2A6182C7008FE1EF /* StandardNotesDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = StandardNotesDebug.entitlements; path = StandardNotes/StandardNotesDebug.entitlements; sourceTree = "<group>"; };
|
||||
07CAB76B2A6182D6008FE1EF /* Share To SNDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share To SNDebug.entitlements"; sourceTree = "<group>"; };
|
||||
07CAB76C2A618353008FE1EF /* StandardNotes-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StandardNotes-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReceiveSharingIntent.swift; path = ReceiveSharingIntent/ReceiveSharingIntent.swift; sourceTree = "<group>"; };
|
||||
07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ReceiveSharingIntent.m; path = ReceiveSharingIntent/ReceiveSharingIntent.m; sourceTree = "<group>"; };
|
||||
0BB10C8D896AFACECE748F6D /* Pods-StandardNotesDev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StandardNotesDev.release.xcconfig"; path = "Target Support Files/Pods-StandardNotesDev/Pods-StandardNotesDev.release.xcconfig"; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* StandardNotes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StandardNotes.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = StandardNotes/AppDelegate.h; sourceTree = "<group>"; };
|
||||
@@ -75,6 +110,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
07CAB7582A618128008FE1EF /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -113,6 +155,17 @@
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
07CAB75C2A618128008FE1EF /* Share To SN */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07CAB76B2A6182D6008FE1EF /* Share To SNDebug.entitlements */,
|
||||
07CAB75D2A618128008FE1EF /* ShareViewController.swift */,
|
||||
07CAB75F2A618128008FE1EF /* MainInterface.storyboard */,
|
||||
07CAB7622A618128008FE1EF /* Info.plist */,
|
||||
);
|
||||
path = "Share To SN";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0D0B9F98B8FBE9ED9C11F260 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -129,6 +182,7 @@
|
||||
13B07FAE1A68108700A75B9A /* StandardNotes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07CAB76A2A6182C7008FE1EF /* StandardNotesDebug.entitlements */,
|
||||
D261494428699DCE00B17102 /* Web.bundle */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
CDC75795292552080019F4AF /* AppDelegate.mm */,
|
||||
@@ -137,6 +191,9 @@
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
CD7D5EC8278005B6005FE1BF /* Info.plist */,
|
||||
CD7D5EC927800608005FE1BF /* LaunchScreen.storyboard */,
|
||||
07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */,
|
||||
07CAB76C2A618353008FE1EF /* StandardNotes-Bridging-Header.h */,
|
||||
07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */,
|
||||
);
|
||||
name = StandardNotes;
|
||||
sourceTree = "<group>";
|
||||
@@ -166,6 +223,7 @@
|
||||
13B07FAE1A68108700A75B9A /* StandardNotes */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
00E356EF1AD99517003FC87E /* StandardNotesTests */,
|
||||
07CAB75C2A618128008FE1EF /* Share To SN */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
CD7D5EE127801645005FE1BF /* StandardNotesDev-Info.plist */,
|
||||
@@ -184,6 +242,7 @@
|
||||
13B07F961A680F5B00A75B9A /* StandardNotes.app */,
|
||||
00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */,
|
||||
CD7D5EDF278015D2005FE1BF /* StandardNotesDev.app */,
|
||||
07CAB75B2A618128008FE1EF /* Share To SN.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -212,6 +271,23 @@
|
||||
productReference = 00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
07CAB75A2A618128008FE1EF /* Share To SN */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 07CAB7692A618128008FE1EF /* Build configuration list for PBXNativeTarget "Share To SN" */;
|
||||
buildPhases = (
|
||||
07CAB7572A618128008FE1EF /* Sources */,
|
||||
07CAB7582A618128008FE1EF /* Frameworks */,
|
||||
07CAB7592A618128008FE1EF /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Share To SN";
|
||||
productName = "Share To SN";
|
||||
productReference = 07CAB75B2A618128008FE1EF /* Share To SN.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
13B07F861A680F5B00A75B9A /* StandardNotes */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "StandardNotes" */;
|
||||
@@ -224,10 +300,12 @@
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
ADF6EC52208176452ADF4217 /* [CP] Embed Pods Frameworks */,
|
||||
BD9D24718C148A593389F498 /* [CP] Copy Pods Resources */,
|
||||
07CAB7662A618128008FE1EF /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
07CAB7642A618128008FE1EF /* PBXTargetDependency */,
|
||||
);
|
||||
name = StandardNotes;
|
||||
productName = StandardNotes;
|
||||
@@ -262,14 +340,18 @@
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1420;
|
||||
LastUpgradeCheck = 1210;
|
||||
TargetAttributes = {
|
||||
00E356ED1AD99517003FC87E = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||
};
|
||||
07CAB75A2A618128008FE1EF = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1120;
|
||||
LastSwiftMigration = 1420;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -289,6 +371,7 @@
|
||||
13B07F861A680F5B00A75B9A /* StandardNotes */,
|
||||
00E356ED1AD99517003FC87E /* StandardNotesTests */,
|
||||
CD7D5ECB278015D2005FE1BF /* StandardNotesDev */,
|
||||
07CAB75A2A618128008FE1EF /* Share To SN */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -301,6 +384,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
07CAB7592A618128008FE1EF /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
07CAB7612A618128008FE1EF /* MainInterface.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -570,11 +661,21 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
07CAB7572A618128008FE1EF /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
07CAB75E2A618128008FE1EF /* ShareViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CDC75796292552080019F4AF /* AppDelegate.mm in Sources */,
|
||||
07CAB76E2A618353008FE1EF /* ReceiveSharingIntent.swift in Sources */,
|
||||
07CAB7702A618385008FE1EF /* ReceiveSharingIntent.m in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -596,13 +697,30 @@
|
||||
target = 13B07F861A680F5B00A75B9A /* StandardNotes */;
|
||||
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
|
||||
};
|
||||
07CAB7642A618128008FE1EF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 07CAB75A2A618128008FE1EF /* Share To SN */;
|
||||
targetProxy = 07CAB7632A618128008FE1EF /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
07CAB75F2A618128008FE1EF /* MainInterface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
07CAB7602A618128008FE1EF /* Base */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
00E356F61AD99517003FC87E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5915B3231CE7C52662278306 /* Pods-StandardNotes-StandardNotesTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -630,6 +748,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 321F15E603CF0AF8B1769447 /* Pods-StandardNotes-StandardNotesTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
INFOPLIST_FILE = StandardNotesTests/Info.plist;
|
||||
@@ -650,13 +769,93 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
07CAB7672A618128008FE1EF /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "Share To SN/Share To SNDebug.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = HKF9BXSN95;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Share To SN/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Share To SN";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.standardnotes.standardnotes.Share-To-SN";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
07CAB7682A618128008FE1EF /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = HKF9BXSN95;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Share To SN/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Share To SN";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.standardnotes.standardnotes.Share-To-SN";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 66417CEB7622E77D89928FCA /* Pods-StandardNotes.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Blue;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotes.entitlements;
|
||||
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotesDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -676,6 +875,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.standardnotes.standardnotes;
|
||||
PRODUCT_NAME = StandardNotes;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "StandardNotes-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -687,6 +887,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 948EE90E15EA48C27577820B /* Pods-StandardNotes.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Blue;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotes.entitlements;
|
||||
@@ -710,6 +911,7 @@
|
||||
PRODUCT_NAME = StandardNotes;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.standardnotes.standardnotes";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.standardnotes.standardnotes";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "StandardNotes-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -916,6 +1118,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
07CAB7692A618128008FE1EF /* Build configuration list for PBXNativeTarget "Share To SN" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
07CAB7672A618128008FE1EF /* Debug */,
|
||||
07CAB7682A618128008FE1EF /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "StandardNotes" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTLinkingManager.h>
|
||||
#import <WebKit/WKWebsiteDataStore.h>
|
||||
#import <TrustKit/TrustKit.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
|
||||
{
|
||||
return [RCTLinkingManager application:application openURL:url options:options];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
|
||||
[self configurePinning];
|
||||
|
||||
|
||||
[self disableUrlCache];
|
||||
|
||||
|
||||
[self clearWebEditorCache];
|
||||
|
||||
|
||||
NSString *CFBundleIdentifier = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
|
||||
|
||||
|
||||
NSDictionary * initialProperties = @{@"env" : [CFBundleIdentifier isEqualToString:@"com.standardnotes.standardnotes.dev"] ? @"dev" : @"prod"};
|
||||
|
||||
|
||||
self.moduleName = @"StandardNotes";
|
||||
self.initialProps = @{};
|
||||
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
@@ -50,12 +58,12 @@
|
||||
if(![currentVersion isEqualToString:lastVersionClear]) {
|
||||
// UIWebView
|
||||
[[NSURLCache sharedURLCache] removeAllCachedResponses];
|
||||
|
||||
|
||||
// WebKit
|
||||
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
|
||||
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
|
||||
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{}];
|
||||
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:lastVersionClearKey];
|
||||
}
|
||||
}
|
||||
@@ -65,17 +73,17 @@
|
||||
NSDictionary *trustKitConfig =
|
||||
@{
|
||||
kTSKSwizzleNetworkDelegates: @YES,
|
||||
|
||||
|
||||
// The list of domains we want to pin and their configuration
|
||||
kTSKPinnedDomains: @{
|
||||
@"standardnotes.org" : @{
|
||||
kTSKIncludeSubdomains:@YES,
|
||||
|
||||
|
||||
kTSKEnforcePinning:@YES,
|
||||
|
||||
|
||||
// Send reports for pin validation failures so we can track them
|
||||
kTSKReportUris: @[@"https://standard.report-uri.com/r/d/hpkp/reportOnly"],
|
||||
|
||||
|
||||
// The pinned public keys' Subject Public Key Info hashes
|
||||
kTSKPublicKeyHashes : @[
|
||||
@"Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=",
|
||||
@@ -90,12 +98,12 @@
|
||||
},
|
||||
@"standardnotes.com" : @{
|
||||
kTSKIncludeSubdomains:@YES,
|
||||
|
||||
|
||||
kTSKEnforcePinning:@YES,
|
||||
|
||||
|
||||
// Send reports for pin validation failures so we can track them
|
||||
kTSKReportUris: @[@"https://standard.report-uri.com/r/d/hpkp/reportOnly"],
|
||||
|
||||
|
||||
// The pinned public keys' Subject Public Key Info hashes
|
||||
kTSKPublicKeyHashes : @[
|
||||
@"Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=",
|
||||
@@ -110,7 +118,7 @@
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict/>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>http</string>
|
||||
|
||||
@@ -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>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>webcredentials:app.standardnotes.com</string>
|
||||
</array>
|
||||
<key>com.apple.developer.default-data-protection</key>
|
||||
<string>NSFileProtectionComplete</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.standardnotes.standardnotes</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
|
||||
import { RefObject } from 'react'
|
||||
import { AppState, NativeEventSubscription, NativeModules, Platform } from 'react-native'
|
||||
import { AppState, Linking, NativeEventSubscription, NativeModules, Platform } from 'react-native'
|
||||
import { readFile } from 'react-native-fs'
|
||||
import WebView from 'react-native-webview'
|
||||
const { ReceiveSharingIntent } = NativeModules
|
||||
@@ -13,13 +13,19 @@ type ReceivedItem = {
|
||||
text?: string | null
|
||||
weblink?: string | null
|
||||
subject?: string | null
|
||||
path?: string | null
|
||||
}
|
||||
|
||||
type ReceivedFile = ReceivedItem & {
|
||||
type ReceivedAndroidFile = ReceivedItem & {
|
||||
contentUri: string
|
||||
mimeType: string
|
||||
}
|
||||
|
||||
type ReceivedIosFile = ReceivedItem & {
|
||||
path: string
|
||||
mimeType: string
|
||||
}
|
||||
|
||||
type ReceivedWeblink = ReceivedItem & {
|
||||
weblink: string
|
||||
}
|
||||
@@ -28,10 +34,14 @@ type ReceivedText = ReceivedItem & {
|
||||
text: string
|
||||
}
|
||||
|
||||
const isReceivedFile = (item: ReceivedItem): item is ReceivedFile => {
|
||||
const isReceivedAndroidFile = (item: ReceivedItem): item is ReceivedAndroidFile => {
|
||||
return !!item.contentUri && !!item.mimeType
|
||||
}
|
||||
|
||||
const isReceivedIosFile = (item: ReceivedItem): item is ReceivedIosFile => {
|
||||
return !!item.path
|
||||
}
|
||||
|
||||
const isReceivedWeblink = (item: ReceivedItem): item is ReceivedWeblink => {
|
||||
return !!item.weblink
|
||||
}
|
||||
@@ -40,15 +50,16 @@ const isReceivedText = (item: ReceivedItem): item is ReceivedText => {
|
||||
return !!item.text
|
||||
}
|
||||
|
||||
const BundleIdentifier = 'com.standardnotes.standardnotes'
|
||||
const IosUrlToCheckFor = `${BundleIdentifier}://dataUrl`
|
||||
|
||||
export class ReceivedSharedItemsHandler {
|
||||
private appStateEventSub: NativeEventSubscription | null = null
|
||||
private eventSub: NativeEventSubscription | null = null
|
||||
private receivedItemsQueue: ReceivedItem[] = []
|
||||
private isApplicationLaunched = false
|
||||
|
||||
constructor(private webViewRef: RefObject<WebView>) {
|
||||
if (Platform.OS === 'android') {
|
||||
this.registerNativeEventSub()
|
||||
}
|
||||
this.registerNativeEventSub()
|
||||
}
|
||||
|
||||
setIsApplicationLaunched = (isApplicationLaunched: boolean) => {
|
||||
@@ -61,23 +72,29 @@ export class ReceivedSharedItemsHandler {
|
||||
|
||||
deinit() {
|
||||
this.receivedItemsQueue = []
|
||||
this.appStateEventSub?.remove()
|
||||
this.eventSub?.remove()
|
||||
}
|
||||
|
||||
private registerNativeEventSub = () => {
|
||||
this.appStateEventSub = AppState.addEventListener('change', (state) => {
|
||||
if (state === 'active') {
|
||||
ReceiveSharingIntent.getFileNames()
|
||||
.then(async (filesObject: Record<string, ReceivedItem>) => {
|
||||
const items = Object.values(filesObject)
|
||||
this.receivedItemsQueue.push(...items)
|
||||
if (Platform.OS === 'ios') {
|
||||
Linking.getInitialURL()
|
||||
.then((url) => {
|
||||
if (url && url.startsWith(IosUrlToCheckFor)) {
|
||||
this.addSharedItemsToQueue(url)
|
||||
}
|
||||
})
|
||||
.catch(console.error)
|
||||
this.eventSub = Linking.addEventListener('url', ({ url }) => {
|
||||
if (url && url.startsWith(IosUrlToCheckFor)) {
|
||||
this.addSharedItemsToQueue(url)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isApplicationLaunched) {
|
||||
this.handleItemsQueue().catch(console.error)
|
||||
}
|
||||
})
|
||||
.then(() => ReceiveSharingIntent.clearFileNames())
|
||||
.catch(console.error)
|
||||
this.eventSub = AppState.addEventListener('change', (state) => {
|
||||
if (state === 'active') {
|
||||
this.addSharedItemsToQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -92,7 +109,7 @@ export class ReceivedSharedItemsHandler {
|
||||
return
|
||||
}
|
||||
|
||||
if (isReceivedFile(item)) {
|
||||
if (isReceivedAndroidFile(item)) {
|
||||
const data = await readFile(item.contentUri, 'base64')
|
||||
const file = {
|
||||
name: item.fileName || item.contentUri,
|
||||
@@ -106,6 +123,20 @@ export class ReceivedSharedItemsHandler {
|
||||
messageData: file,
|
||||
}),
|
||||
)
|
||||
} else if (isReceivedIosFile(item)) {
|
||||
const data = await readFile(item.path, 'base64')
|
||||
const file = {
|
||||
name: item.fileName || item.path,
|
||||
data,
|
||||
mimeType: item.mimeType,
|
||||
}
|
||||
this.webViewRef.current?.postMessage(
|
||||
JSON.stringify({
|
||||
reactNativeEvent: ReactNativeToWebEvent.ReceivedFile,
|
||||
messageType: 'event',
|
||||
messageData: file,
|
||||
}),
|
||||
)
|
||||
} else if (isReceivedWeblink(item)) {
|
||||
this.webViewRef.current?.postMessage(
|
||||
JSON.stringify({
|
||||
@@ -131,4 +162,49 @@ export class ReceivedSharedItemsHandler {
|
||||
|
||||
this.handleItemsQueue().catch(console.error)
|
||||
}
|
||||
|
||||
private addSharedItemsToQueue = (url?: string) => {
|
||||
ReceiveSharingIntent.getFileNames(url)
|
||||
.then(async (received: unknown) => {
|
||||
if (!received) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
const items = Object.values(received as Record<string, ReceivedItem>)
|
||||
this.receivedItemsQueue.push(...items)
|
||||
} else if (typeof received === 'string') {
|
||||
const parsed: unknown = JSON.parse(received)
|
||||
if (typeof parsed !== 'object') {
|
||||
return
|
||||
}
|
||||
if (!parsed) {
|
||||
return
|
||||
}
|
||||
if ('media' in parsed && Array.isArray(parsed.media)) {
|
||||
this.receivedItemsQueue.push(...parsed.media)
|
||||
}
|
||||
if ('text' in parsed && Array.isArray(parsed.text)) {
|
||||
this.receivedItemsQueue.push(
|
||||
...parsed.text.map((text: string) => ({
|
||||
text: text,
|
||||
})),
|
||||
)
|
||||
}
|
||||
if ('urls' in parsed && Array.isArray(parsed.urls)) {
|
||||
this.receivedItemsQueue.push(
|
||||
...parsed.urls.map((url: string) => ({
|
||||
weblink: url,
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isApplicationLaunched) {
|
||||
this.handleItemsQueue().catch(console.error)
|
||||
}
|
||||
})
|
||||
.then(() => ReceiveSharingIntent.clearFileNames())
|
||||
.catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user