diff --git a/.travis.yml b/.travis.yml index 16c0b4e5d1..2eb2cddb7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,3 @@ -addons: - artifacts: - debug: true - s3_region: "us-west-1" - target_paths: - - /${TRAVIS_REPO_SLUG}/${TRAVIS_COMMIT}/ - -env: - global: - - secure: EUSicS1mBOImgoYHBZJ2ZE0xwNMAkGRYUDetvxcKk0EQrOii1Z/EuYC1IN7mwiI6Ybnm3b2xq51bq/Ro3iQNi+iylA0r20mEjRsSqb8cqy8hXRnl4f7ZI3qUorUvEmsJueve3U2MLu5eYJdSNPbqgdavZDZZxiZIdbHAfA80KMp1RmgeqxkCY9wTfrYEqCtTGEJMJtCHXoDJ0cdYEVDnHQYZrF9SPkkMqmwgfFvHBGcyTPMsRZ9JpggxJltXJFTlHBNn/wLfGPiMsJlNx0oS7SmoZ08W72h2QKBZX5bVZxoNmMeQCZ0FlSHeq8yTGJ5xM8ig6YRzkuK5JiHkTaY9rVQMoU2ozMll9Bb5/A07XDXoutH1/QC1jvvT9eMBhon3uZ7e3qyUiRgCpFWrOW0UXKQqF2hhGWT965mBB5pxWOgGZjxtVIuKpFwMbsPIss44OuzlaFis5YRsKyceBAM+g8gkwxB8r8AT0A6g4jY4fmvrUr0qyaldlcr7LafGvAa73uJHoTUruR2AZQlJmLpO9p5xbuwId8L0alApqoaaR3vPx6LPtl/hTBr+KScs/I8PjdiEaobNi1PT3sNH5KIZEB+WaNZaTkK8gAzwvx7V+V0GaDD/P7L5Qa4mRIsXa1xDxdVM7ne4B5zEK1KCePzbZJuv769IbNgzIqAfELJWmXE= - - secure: Axr+U8LHOxmGi8GyeL0pP0+84ucxeET59BUpHgANgro+2jLwoLzpvssguc0RHxftVsQBiz2CFfFBVIR6ol+Hf6yjI310Yqw9znBvJuxO0EbAGclGkWyx8DdoWlCSwz+RgiIfIoa7E6QZWt+CZwFbi3vTFutl6NX5Reu0wdpHZlfLT8xkWP3PP7awgL2g75WsNKNv4A1CvzkF3j/hi6JU/b3QsBMTOCHA1vcdpKhjF8MiS9d7gzdwsTr6MoDGVab/oGVCljiFmIPYTa4+xp/q1MYkSizxBmq1mqjXkn1RrZP25QGqgubfgtiywk8H9kDE8/POPJuoteO2gnz2u9I1AkVrrsxCfU9XJzXXwVfbFxVlyOIfC9gZj5ZI9niOqTdytPUMPOzJw79xUvYMeoS7ydcrm3O8gCs0/fBnSH0KfMq37um8zE8zah90M/8CcJjS5KC8hFO/tZomkUASoTwnJEZh4OdSG8Ys9YnKMKBDCuI8HUR2zlVPrv/8ACO4eSZ4LCaNeXQVWil5NNFgLPzvkElqpywVmQaGY3UD2W2w76AEs5sF4n/naQsi6L6oy61mDP8j+wSooQczx/1enue/hzjxZUles7p3ek0yha6N6fxkt/MyvEYWmw98wQZmsBzEahTGXy9iWfobFu0jUzUPXhSdoMul3Q9fceUSP9f+XV8= - before_script: - yarn install @@ -30,16 +18,10 @@ matrix: LANE='node' CHECK_TESTS='true' TEST_RN_PLATFORM='android' - ARTIFACTS_BUCKET='gutenberg-mobile-js-bundle' - ARTIFACTS_PATHS="./bundle" cache: yarn: true script: - - set -e # make sure Travis stops if any of the script lines error - ./.travis/travis-checks-js.sh - - yarn bundle:android # rebuild the Android JS bundle - - rm -Rf bundle/ios # delete the ios bundle since it's not up to date and we don't need to publish to S3 - - set +e - language: node_js node_js: 8 env: diff --git a/ios/gutenberg.xcodeproj/project.pbxproj b/ios/gutenberg.xcodeproj/project.pbxproj index b977515f0c..efc4d524da 100644 --- a/ios/gutenberg.xcodeproj/project.pbxproj +++ b/ios/gutenberg.xcodeproj/project.pbxproj @@ -49,7 +49,8 @@ F1EE6F7921E7F0A500241744 /* NotoSerif-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1EE6F7521E7F0A500241744 /* NotoSerif-Regular.ttf */; }; F1EE6F7A21E7F0A500241744 /* NotoSerif-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1EE6F7621E7F0A500241744 /* NotoSerif-Italic.ttf */; }; F1EE6F7B21E7F0A500241744 /* NotoSerif-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1EE6F7721E7F0A500241744 /* NotoSerif-Bold.ttf */; }; - FF9A6F4121FA8E2500D36D14 /* MediaPickAndUploadCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9A6F1621FA8E2500D36D14 /* MediaPickAndUploadCoordinator.swift */; }; + FF6836C822035EAB00A0C562 /* MediaUploadCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6836C722035EAB00A0C562 /* MediaUploadCoordinator.swift */; }; + FF9A6F4121FA8E2500D36D14 /* MediaPickCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9A6F1621FA8E2500D36D14 /* MediaPickCoordinator.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -433,7 +434,8 @@ F1EE6F7621E7F0A500241744 /* NotoSerif-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerif-Italic.ttf"; sourceTree = ""; }; F1EE6F7721E7F0A500241744 /* NotoSerif-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerif-Bold.ttf"; sourceTree = ""; }; F619623252704B46A619C33C /* RNTAztecView.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNTAztecView.xcodeproj; path = "../react-native-aztec/ios/RNTAztecView.xcodeproj"; sourceTree = ""; }; - FF9A6F1621FA8E2500D36D14 /* MediaPickAndUploadCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaPickAndUploadCoordinator.swift; path = gutenberg/MediaPickAndUploadCoordinator.swift; sourceTree = ""; }; + FF6836C722035EAB00A0C562 /* MediaUploadCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaUploadCoordinator.swift; path = gutenberg/MediaUploadCoordinator.swift; sourceTree = ""; }; + FF9A6F1621FA8E2500D36D14 /* MediaPickCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaPickCoordinator.swift; path = gutenberg/MediaPickCoordinator.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -584,7 +586,8 @@ F15198372100DC3C000F6E97 /* gutenberg-Bridging-Header.h */, F15198392100DC3D000F6E97 /* AppDelegate.swift */, 7EC7328E21907E3F00FED2E6 /* GutenbergViewController.swift */, - FF9A6F1621FA8E2500D36D14 /* MediaPickAndUploadCoordinator.swift */, + FF9A6F1621FA8E2500D36D14 /* MediaPickCoordinator.swift */, + FF6836C722035EAB00A0C562 /* MediaUploadCoordinator.swift */, F151983A2100DC3D000F6E97 /* MediaProvider.swift */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, @@ -1368,8 +1371,9 @@ buildActionMask = 2147483647; files = ( F151983C2100DC3D000F6E97 /* AppDelegate.swift in Sources */, - FF9A6F4121FA8E2500D36D14 /* MediaPickAndUploadCoordinator.swift in Sources */, + FF9A6F4121FA8E2500D36D14 /* MediaPickCoordinator.swift in Sources */, F151983D2100DC3D000F6E97 /* MediaProvider.swift in Sources */, + FF6836C822035EAB00A0C562 /* MediaUploadCoordinator.swift in Sources */, 7EC7328F21907E3F00FED2E6 /* GutenbergViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/gutenberg/GutenbergViewController.swift b/ios/gutenberg/GutenbergViewController.swift index 417fcbba4b..9e6c3742e6 100644 --- a/ios/gutenberg/GutenbergViewController.swift +++ b/ios/gutenberg/GutenbergViewController.swift @@ -7,7 +7,11 @@ class GutenbergViewController: UIViewController { fileprivate lazy var gutenberg = Gutenberg(dataSource: self) fileprivate var htmlMode = false - fileprivate var mediaPickAndUploadCoordinator: MediaPickAndUploadCoordinator? + fileprivate var mediaPickCoordinator: MediaPickCoordinator? + fileprivate lazy var mediaUploadCoordinator: MediaUploadCoordinator = { + let mediaUploadCoordinator = MediaUploadCoordinator(gutenberg: self.gutenberg) + return mediaUploadCoordinator + }() override func loadView() { view = gutenberg.rootView @@ -49,22 +53,62 @@ extension GutenbergViewController: GutenbergBridgeDelegate { callback(1, "https://cldup.com/cXyG__fTLN.jpg") case .deviceLibrary: print("Gutenberg did request a device media picker, opening the device picker") - mediaPickAndUploadCoordinator = MediaPickAndUploadCoordinator(presenter: self, gutenberg: gutenberg, mediaCallback: callback, finishCallback: { - self.mediaPickAndUploadCoordinator = nil - } ) - mediaPickAndUploadCoordinator?.pickAndUpload(from: .savedPhotosAlbum) + pickAndUpload(from: .savedPhotosAlbum, callback: callback) case .deviceCamera: print("Gutenberg did request a device media picker, opening the camera picker") - mediaPickAndUploadCoordinator = MediaPickAndUploadCoordinator(presenter: self, gutenberg: gutenberg, mediaCallback: callback, finishCallback: { - self.mediaPickAndUploadCoordinator = nil - } ) - mediaPickAndUploadCoordinator?.pickAndUpload(from: .camera) + pickAndUpload(from: .camera, callback: callback) } } + func pickAndUpload(from source: UIImagePickerController.SourceType, callback: @escaping MediaPickerDidPickMediaCallback) { + mediaPickCoordinator = MediaPickCoordinator(presenter: self, callback: { (url) in + guard let url = url, let mediaID = self.mediaUploadCoordinator.upload(url: url) else { + callback(nil, nil) + return + } + callback(mediaID, url.absoluteString) + self.mediaPickCoordinator = nil + } ) + mediaPickCoordinator?.pick(from: source) + } + func gutenbergDidRequestMediaUploadSync() { print("Gutenberg request for media uploads to be resync") } + + func gutenbergDidRequestMediaUploadActionDialog(for mediaID: Int32) { + guard let progress = mediaUploadCoordinator.progressForUpload(mediaID: mediaID) else { + return + } + + let title: String = "Media Options" + var message: String? = "" + let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet) + let dismissAction = UIAlertAction(title: "Dismiss", style: .cancel) { (action) in + + } + alertController.addAction(dismissAction) + + if progress.fractionCompleted < 1 { + let cancelUploadAction = UIAlertAction(title: "Cancel upload", style: .destructive) { (action) in + self.mediaUploadCoordinator.cancelUpload(with: mediaID) + } + alertController.addAction(cancelUploadAction) + } else if let error = progress.userInfo[.mediaError] as? String { + message = error + let retryUploadAction = UIAlertAction(title: "Retry upload", style: .default) { (action) in + self.mediaUploadCoordinator.retryUpload(with: mediaID) + } + alertController.addAction(retryUploadAction) + } + + alertController.title = title + alertController.message = message + alertController.popoverPresentationController?.sourceView = view + alertController.popoverPresentationController?.sourceRect = view.frame + alertController.popoverPresentationController?.permittedArrowDirections = .any + present(alertController, animated: true, completion: nil) + } } extension GutenbergViewController: GutenbergBridgeDataSource { diff --git a/ios/gutenberg/MediaPickAndUploadCoordinator.swift b/ios/gutenberg/MediaPickAndUploadCoordinator.swift deleted file mode 100644 index 2b3376da28..0000000000 --- a/ios/gutenberg/MediaPickAndUploadCoordinator.swift +++ /dev/null @@ -1,83 +0,0 @@ - -import Foundation -import UIKit -import RNReactNativeGutenbergBridge - -class MediaPickAndUploadCoordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { - - private let presenter: UIViewController - private let mediaCallback: MediaPickerDidPickMediaCallback - private let gutenberg: Gutenberg - - init(presenter: UIViewController, - gutenberg: Gutenberg, - mediaCallback: @escaping MediaPickerDidPickMediaCallback, - finishCallback: @escaping () -> Void) { - self.presenter = presenter - self.gutenberg = gutenberg - self.mediaCallback = mediaCallback - } - - func pickAndUpload(from source: UIImagePickerController.SourceType) { - guard UIImagePickerController.isSourceTypeAvailable(source) else { - // Camera not available, bound to happen in the simulator - mediaCallback(nil, nil) - return - } - let pickerController = UIImagePickerController() - pickerController.sourceType = source - pickerController.delegate = self - presenter.show(pickerController, sender: nil) - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - presenter.dismiss(animated: true, completion: nil) - mediaCallback(nil, nil) - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { - presenter.dismiss(animated: true, completion: nil) - let mediaID = UUID().uuidString - let url = URL(fileURLWithPath: NSTemporaryDirectory() + mediaID + ".jpg") - guard - let image = info[UIImagePickerControllerOriginalImage] as? UIImage, - let data = UIImageJPEGRepresentation(image, 1.0) - else { - return - } - do { - try data.write(to: url) - mediaCallback(mediaID.hashValue, url.absoluteString) - let progress = Progress(parent: nil, userInfo: [ProgressUserInfoKey.mediaID: mediaID, ProgressUserInfoKey.mediaURL: url]) - progress.totalUnitCount = 100 - - Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFireMethod(_:)), userInfo: progress, repeats: true) - } catch { - mediaCallback(nil, nil) - } - } - - @objc func timerFireMethod(_ timer: Timer) { - guard let progress = timer.userInfo as? Progress, - let mediaID = progress.userInfo[.mediaID] as? String, - let mediaURL = progress.userInfo[.mediaURL] as? URL - //let otherURL = URL(string: "https://cldup.com/cXyG__fTLN.jpg") - else { - timer.invalidate() - return - } - progress.completedUnitCount += 1 - - if progress.fractionCompleted < 1 { - gutenberg.mediaUploadUpdate(id: mediaID.hashValue, state: .uploading, progress: Float(progress.fractionCompleted), url: nil, serverID: nil) - } else if progress.fractionCompleted >= 1 { - timer.invalidate() - gutenberg.mediaUploadUpdate(id: mediaID.hashValue, state: .succeeded, progress: 1, url: mediaURL, serverID: 124) - } - } -} - -extension ProgressUserInfoKey { - static let mediaID = ProgressUserInfoKey("mediaID") - static let mediaURL = ProgressUserInfoKey("mediaURL") -} diff --git a/ios/gutenberg/MediaPickCoordinator.swift b/ios/gutenberg/MediaPickCoordinator.swift new file mode 100644 index 0000000000..34d7dfa0f1 --- /dev/null +++ b/ios/gutenberg/MediaPickCoordinator.swift @@ -0,0 +1,60 @@ + +import Foundation +import UIKit +import RNReactNativeGutenbergBridge + +class MediaPickCoordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + + private let presenter: UIViewController + private let callback: (URL?) -> Void + + init(presenter: UIViewController, + callback: @escaping (URL?) -> Void) { + self.presenter = presenter + self.callback = callback + } + + func pick(from source: UIImagePickerController.SourceType) { + guard UIImagePickerController.isSourceTypeAvailable(source) else { + // Camera not available, bound to happen in the simulator + callback(nil) + return + } + let pickerController = UIImagePickerController() + pickerController.sourceType = source + pickerController.delegate = self + presenter.show(pickerController, sender: nil) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + presenter.dismiss(animated: true, completion: nil) + callback(nil) + } + + func save(image: UIImage, toTemporaryDirectoryUsingName name: String) -> URL? { + let url = URL(fileURLWithPath: NSTemporaryDirectory() + name + ".jpg") + guard let data = UIImageJPEGRepresentation(image, 1.0) else { + return nil + } + do { + try data.write(to: url) + return url + } catch { + return nil + } + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + presenter.dismiss(animated: true, completion: nil) + let mediaID = UUID().uuidString + guard + let image = info[UIImagePickerControllerOriginalImage] as? UIImage, + let url = save(image: image, toTemporaryDirectoryUsingName: mediaID) + else { + callback(nil) + return + } + callback(url) + } + +} diff --git a/ios/gutenberg/MediaUploadCoordinator.swift b/ios/gutenberg/MediaUploadCoordinator.swift new file mode 100644 index 0000000000..fb2c3f08bc --- /dev/null +++ b/ios/gutenberg/MediaUploadCoordinator.swift @@ -0,0 +1,79 @@ + +import Foundation +import UIKit +import RNReactNativeGutenbergBridge + +class MediaUploadCoordinator: NSObject { + + private let gutenberg: Gutenberg + + private var activeUploads: [Int32: Progress] = [:] + + init(gutenberg: Gutenberg) { + self.gutenberg = gutenberg + } + + func upload(url: URL) -> Int32? { + //Make sure the media is not larger than a 32 bits to number to avoid problems when bridging to JS + let mediaID = Int32(truncatingIfNeeded:UUID().uuidString.hash) + let progress = Progress(parent: nil, userInfo: [ProgressUserInfoKey.mediaID: mediaID, ProgressUserInfoKey.mediaURL: url]) + progress.totalUnitCount = 100 + activeUploads[mediaID] = progress + let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFireMethod(_:)), userInfo: progress, repeats: true) + progress.cancellationHandler = { () in + timer.invalidate() + self.gutenberg.mediaUploadUpdate(id: mediaID, state: .reset, progress: 0, url: nil, serverID: nil) + } + return mediaID + } + + func progressForUpload(mediaID: Int32) -> Progress? { + return activeUploads[mediaID] + } + + func retryUpload(with mediaID: Int32) { + guard let progress = activeUploads[mediaID] else { + return + } + progress.completedUnitCount = 0 + Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFireMethod(_:)), userInfo: progress, repeats: true) + } + + func cancelUpload(with mediaID: Int32) { + guard let progress = activeUploads[mediaID] else { + return + } + progress.cancel() + } + + @objc func timerFireMethod(_ timer: Timer) { + guard let progress = timer.userInfo as? Progress, + let mediaID = progress.userInfo[.mediaID] as? Int32, + let mediaURL = progress.userInfo[.mediaURL] as? URL + else { + timer.invalidate() + return + } + progress.completedUnitCount += 1 + //Variable to switch upload final state from success to failure. + let successfull = true + if progress.fractionCompleted < 1 { + gutenberg.mediaUploadUpdate(id: mediaID, state: .uploading, progress: Float(progress.fractionCompleted), url: nil, serverID: nil) + } else if progress.fractionCompleted >= 1 { + timer.invalidate() + if successfull { + gutenberg.mediaUploadUpdate(id: mediaID, state: .failed, progress: 1, url: mediaURL, serverID: 123) + activeUploads[mediaID] = nil + } else { + progress.setUserInfoObject("Network upload failed", forKey: .mediaError) + gutenberg.mediaUploadUpdate(id: mediaID, state: .failed, progress: 1, url: nil, serverID: nil) + } + } + } +} + +extension ProgressUserInfoKey { + static let mediaID = ProgressUserInfoKey("mediaID") + static let mediaURL = ProgressUserInfoKey("mediaURL") + static let mediaError = ProgressUserInfoKey("mediaError") +} diff --git a/jitpack.yml b/jitpack.yml index b422439ad9..b6728ce614 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,6 +2,7 @@ before_install: - yes | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-27" - yes | $ANDROID_HOME/tools/bin/sdkmanager "build-tools;27.0.3" install: + - export TMPDIR=`dirname $(mktemp)` - echo "Changing into the android folder of the Bridge module" - pushd react-native-gutenberg-bridge/android && ./gradlew --stacktrace clean -Pgroup=com.github.wordpress-mobile.gutenberg-mobile -Pversion=$VERSION install && popd - pushd react-native-aztec/android && ./gradlew --stacktrace clean -Pgroup=com.github.wordpress-mobile.gutenberg-mobile -Pversion=$VERSION install && popd diff --git a/react-native-gutenberg-bridge/android/build.gradle b/react-native-gutenberg-bridge/android/build.gradle index 2b9e7080d7..d6495d7e34 100644 --- a/react-native-gutenberg-bridge/android/build.gradle +++ b/react-native-gutenberg-bridge/android/build.gradle @@ -1,19 +1,70 @@ - buildscript { + def isJitPack = System.getenv('JITPACK').asBoolean() + repositories { jcenter() google() + + if (isJitPack) { + maven { + url "https://plugins.gradle.org/m2/" + } + } } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' + + if (isJitPack) { + classpath "com.moowork.gradle:gradle-node-plugin:1.2.0" + } } } apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' +def isJitPack = System.getenv('JITPACK').asBoolean() + +if (isJitPack) { + println 'Building in JitPack' + + apply plugin: 'com.moowork.node' + + node { + // Version of node to use. + version = '8.11.3' + + // Version of npm to use. + npmVersion = '6.3.0' + + // Version of Yarn to use. + yarnVersion = '1.10.1' + + // Base URL for fetching node distributions (change if you have a mirror). + distBaseUrl = 'https://nodejs.org/dist' + + // If true, it will download node using above parameters. + // If false, it will try to use globally installed node. + download = true + + def tmpdir = "${System.getenv('TMPDIR')}/jsbundle/${System.getenv('VERSION')}" + + // Set the work directory for unpacking node + workDir = file("${tmpdir}/nodejs") + + // Set the work directory for NPM + npmWorkDir = file("${tmpdir}/npm") + + // Set the work directory for Yarn + yarnWorkDir = file("${tmpdir}/yarn") + + // Set the work directory where node_modules should be located + nodeModulesDir = file("${project.projectDir}/../../") + } +} + // import the `readReactNativeVersion()` function apply from: 'https://gist.githubusercontent.com/hypest/742448b9588b3a0aa580a5e80ae95bdf/raw/8eb62d40ee7a5104d2fcaeff21ce6f29bd93b054/readReactNativeVersion.gradle' @@ -79,3 +130,37 @@ dependencies { implementation "com.facebook.react:react-native:${rnVersion}" } } + +if (isJitPack) { + def assetsFolder = 'src/main/assets' + + task buildJSBundle(type: YarnTask) { + args = ['bundle:android'] + } + + task ensureAssetsDirectory << { + mkdir assetsFolder + } + + task copyJSBundle(type: Copy) { + def origFileName = 'App.js' + def origWithPath = "../../bundle/android/${origFileName}" + def target = 'index.android.bundle' + from origWithPath + into assetsFolder + rename origFileName, target + } << { + println "Done copying the Android JS bundle to assets folder" + } + + task cleanupNodeModulesFolder(type: Delete) { + delete '../../node_modules' + } + + if (isJitPack) { + preBuild.dependsOn(cleanupNodeModulesFolder) + cleanupNodeModulesFolder.dependsOn(copyJSBundle) + copyJSBundle.dependsOn(buildJSBundle) + buildJSBundle.dependsOn(yarn_install, ensureAssetsDirectory) + } +} diff --git a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java index 81f8273d7e..ecb6a336b2 100644 --- a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java +++ b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergBridgeJS2Parent.java @@ -9,6 +9,7 @@ interface MediaSelectedCallback { interface MediaUploadCallback { void onUploadMediaFileSelected(int mediaId, String mediaUri); + void onUploadMediaFileClear(int mediaId); void onMediaFileUploadProgress(int mediaId, float progress); void onMediaFileUploadSucceeded(int mediaId, String mediaUrl, int serverId); void onMediaFileUploadFailed(int mediaId); @@ -21,4 +22,8 @@ interface MediaUploadCallback { void requestMediaPickerFromDeviceCamera(MediaUploadCallback mediaUploadCallback); void mediaUploadSync(MediaUploadCallback mediaUploadCallback); + + void requestImageFailedRetryDialog(int mediaId); + + void requestImageUploadCancelDialog(int mediaId); } diff --git a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java index 5e87a2fdec..7ccf32a198 100644 --- a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java +++ b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/RNReactNativeGutenbergBridgeModule.java @@ -32,6 +32,7 @@ public class RNReactNativeGutenbergBridgeModule extends ReactContextBaseJavaModu private static final int MEDIA_UPLOAD_STATE_UPLOADING = 1; private static final int MEDIA_UPLOAD_STATE_SUCCEEDED = 2; private static final int MEDIA_UPLOAD_STATE_FAILED = 3; + private static final int MEDIA_UPLOAD_STATE_RESET = 4; private static final int MEDIA_SERVER_ID_UNKNOWN = 0; @@ -94,6 +95,16 @@ public void mediaUploadSync() { mGutenbergBridgeJS2Parent.mediaUploadSync(getNewUploadMediaCallback(null)); } + @ReactMethod + public void requestImageFailedRetryDialog(final int mediaId) { + mGutenbergBridgeJS2Parent.requestImageFailedRetryDialog(mediaId); + } + + @ReactMethod + public void requestImageUploadCancelDialog(final int mediaId) { + mGutenbergBridgeJS2Parent.requestImageUploadCancelDialog(mediaId); + } + private MediaSelectedCallback getNewMediaSelectedCallback(final Callback jsCallback) { return new MediaSelectedCallback() { @Override public void onMediaSelected(int mediaId, String mediaUrl) { @@ -111,6 +122,10 @@ public void onUploadMediaFileSelected(int mediaId, String mediaUri) { } } + @Override public void onUploadMediaFileClear(int mediaId) { + setMediaFileUploadDataInJS(MEDIA_UPLOAD_STATE_RESET, mediaId, null, 0); + } + @Override public void onMediaFileUploadProgress(int mediaId, float progress) { setMediaFileUploadDataInJS(MEDIA_UPLOAD_STATE_UPLOADING, mediaId, null, progress); diff --git a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index ca3fd9821b..09ec2ce608 100644 --- a/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/react-native-gutenberg-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -70,6 +70,8 @@ public interface OnMediaLibraryButtonListener { void onMediaLibraryButtonClicked(); void onUploadMediaButtonClicked(); void onCapturePhotoButtonClicked(); + void onRetryUploadForMediaClicked(int mediaId); + void onCancelUploadForMediaClicked(int mediaId); } public interface OnReattachQueryListener { @@ -113,6 +115,13 @@ public void mediaUploadSync(MediaUploadCallback mediaUploadCallback) { onReattachQueryListener.onQueryCurrentProgressForUploadingMedia(); } + @Override public void requestImageFailedRetryDialog(int mediaId) { + onMediaLibraryButtonListener.onRetryUploadForMediaClicked(mediaId); + } + + @Override public void requestImageUploadCancelDialog(int mediaId) { + onMediaLibraryButtonListener.onCancelUploadForMediaClicked(mediaId); + } }); return Arrays.asList( new MainReactPackage(), @@ -342,14 +351,18 @@ public void mediaFileUploadProgress(final int mediaId, final float progress) { public void mediaFileUploadFailed(final int mediaId) { if (isMediaUploadCallbackRegistered()) { mPendingMediaUploadCallback.onMediaFileUploadFailed(mediaId); - mPendingMediaUploadCallback = null; } } public void mediaFileUploadSucceeded(final int mediaId, final String mediaUrl, final int serverMediaId) { if (isMediaUploadCallbackRegistered()) { mPendingMediaUploadCallback.onMediaFileUploadSucceeded(mediaId, mediaUrl, serverMediaId); - mPendingMediaUploadCallback = null; + } + } + + public void clearMediaFileURL(final int mediaId) { + if (isMediaUploadCallbackRegistered()) { + mPendingMediaUploadCallback.onUploadMediaFileClear(mediaId); } } diff --git a/react-native-gutenberg-bridge/index.js b/react-native-gutenberg-bridge/index.js index da3b255667..9c052697c4 100644 --- a/react-native-gutenberg-bridge/index.js +++ b/react-native-gutenberg-bridge/index.js @@ -56,4 +56,12 @@ export function mediaUploadSync() { return RNReactNativeGutenbergBridge.mediaUploadSync(); } +export function requestImageFailedRetryDialog( mediaId ) { + return RNReactNativeGutenbergBridge.requestImageFailedRetryDialog( mediaId ); +} + +export function requestImageUploadCancelDialog( mediaId ) { + return RNReactNativeGutenbergBridge.requestImageUploadCancelDialog( mediaId ); +} + export default RNReactNativeGutenbergBridge; diff --git a/react-native-gutenberg-bridge/ios/Gutenberg.swift b/react-native-gutenberg-bridge/ios/Gutenberg.swift index 1df0d2c440..22bcfa7b98 100644 --- a/react-native-gutenberg-bridge/ios/Gutenberg.swift +++ b/react-native-gutenberg-bridge/ios/Gutenberg.swift @@ -71,7 +71,7 @@ public class Gutenberg: NSObject { bridgeModule.sendEvent(withName: EventName.updateHtml, body: ["html": html]) } - public func mediaUploadUpdate(id: Int, state: MediaUploadState, progress: Float, url: URL?, serverID: Int?) { + public func mediaUploadUpdate(id: Int32, state: MediaUploadState, progress: Float, url: URL?, serverID: Int32?) { var data: [String: Any] = ["mediaId": id, "state": state.rawValue, "progress": progress]; if let url = url { data["mediaUrl"] = url.absoluteString @@ -110,6 +110,7 @@ extension Gutenberg { case uploading = 1 case succeeded = 2 case failed = 3 + case reset = 4 } } diff --git a/react-native-gutenberg-bridge/ios/GutenbergBridgeDelegate.swift b/react-native-gutenberg-bridge/ios/GutenbergBridgeDelegate.swift index ce645968ed..de7b851151 100644 --- a/react-native-gutenberg-bridge/ios/GutenbergBridgeDelegate.swift +++ b/react-native-gutenberg-bridge/ios/GutenbergBridgeDelegate.swift @@ -1,4 +1,4 @@ -public typealias MediaPickerDidPickMediaCallback = (_ id: Int?, _ url: String?) -> Void +public typealias MediaPickerDidPickMediaCallback = (_ id: Int32?, _ url: String?) -> Void public enum MediaPickerSource: String { case mediaLibrary = "SITE_MEDIA_LIBRARY" @@ -29,6 +29,10 @@ public protocol GutenbergBridgeDelegate: class { /// func gutenbergDidRequestMediaUploadSync() + /// Tells the delegate that an image block requested for the actions available for the media upload. + /// + func gutenbergDidRequestMediaUploadActionDialog(for mediaID: Int32) + /// Tells the delegate that the Gutenberg module has finished loading. /// func gutenbergDidLoad() diff --git a/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.m b/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.m index 7d287bab95..e339ed81d3 100644 --- a/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.m +++ b/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.m @@ -5,6 +5,8 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject) RCT_EXTERN_METHOD(provideToNative_Html:(NSString *)html title:(NSString *)title changed:(BOOL)changed) RCT_EXTERN_METHOD(requestMediaPickFrom:(NSString *)source callback:(RCTResponseSenderBlock)callback) RCT_EXTERN_METHOD(mediaUploadSync) +RCT_EXTERN_METHOD(requestImageFailedRetryDialog:(int)mediaID) +RCT_EXTERN_METHOD(requestImageUploadCancelDialog:(int)mediaID) RCT_EXTERN_METHOD(editorDidLayout) @end diff --git a/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.swift b/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.swift index d3f5f8cbbe..08bbe77c95 100644 --- a/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.swift +++ b/react-native-gutenberg-bridge/ios/RNReactNativeGutenbergBridge.swift @@ -34,6 +34,20 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter { } } + @objc + func requestImageFailedRetryDialog(_ mediaID: Int32) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaUploadActionDialog(for: mediaID) + } + } + + @objc + func requestImageUploadCancelDialog(_ mediaID: Int32) { + DispatchQueue.main.async { + self.delegate?.gutenbergDidRequestMediaUploadActionDialog(for: mediaID) + } + } + @objc func editorDidLayout() { DispatchQueue.main.async { diff --git a/src/block-management/inline-toolbar/index.js b/src/block-management/inline-toolbar/index.js index 606e41b68d..85fe3a90a7 100644 --- a/src/block-management/inline-toolbar/index.js +++ b/src/block-management/inline-toolbar/index.js @@ -6,6 +6,7 @@ import { View } from 'react-native'; import InlineToolbarActions from './actions'; import { ToolbarButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { InspectorControls } from '@wordpress/editor'; type PropsType = { clientId: string, @@ -59,6 +60,8 @@ export default class InlineToolbar extends React.Component { + +