diff --git a/.re-natal b/.re-natal index f98c607..622d497 100644 --- a/.re-natal +++ b/.re-natal @@ -5,7 +5,10 @@ "dev": "env/dev", "prod": "env/prod" }, - "modules": [], + "modules": [ + "react-native-fs", + "react-native-share" + ], "imageDirs": [ "images" ], diff --git a/android/app/build.gradle b/android/app/build.gradle index 529c5ac..03cdb10 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,6 +131,8 @@ android { } dependencies { + compile project(':react-native-share') + compile project(':react-native-fs') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules diff --git a/android/app/src/main/java/com/auricle/MainApplication.java b/android/app/src/main/java/com/auricle/MainApplication.java index 8aac811..e7ba03f 100644 --- a/android/app/src/main/java/com/auricle/MainApplication.java +++ b/android/app/src/main/java/com/auricle/MainApplication.java @@ -3,6 +3,8 @@ import android.app.Application; import com.facebook.react.ReactApplication; +import cl.json.RNSharePackage; +import com.rnfs.RNFSPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; @@ -22,7 +24,9 @@ public boolean getUseDeveloperSupport() { @Override protected List getPackages() { return Arrays.asList( - new MainReactPackage() + new MainReactPackage(), + new RNSharePackage(), + new RNFSPackage() ); } }; diff --git a/android/settings.gradle b/android/settings.gradle index db3d6ff..63b076c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,3 +1,7 @@ rootProject.name = 'auricle' +include ':react-native-share' +project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android') +include ':react-native-fs' +project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android') include ':app' diff --git a/ios/auricle.xcodeproj/project.pbxproj b/ios/auricle.xcodeproj/project.pbxproj index 604d85b..566accf 100644 --- a/ios/auricle.xcodeproj/project.pbxproj +++ b/ios/auricle.xcodeproj/project.pbxproj @@ -5,7 +5,6 @@ }; objectVersion = 46; objects = { - /* Begin PBXBuildFile section */ 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; @@ -22,6 +21,8 @@ 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; + B0314355B1B543DE84BD37CA /* libRNFS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 905F022AC5954080A6F57D3A /* libRNFS.a */; }; + 904A0798CB1B4D9EA0D492AC /* libRNShare.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F79A6A13FE9439B9A907FB8 /* libRNShare.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -224,6 +225,10 @@ 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + 1CD3E61BDF09456FAF06EECE /* RNFS.xcodeproj */ = {isa = PBXFileReference; name = "RNFS.xcodeproj"; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 905F022AC5954080A6F57D3A /* libRNFS.a */ = {isa = PBXFileReference; name = "libRNFS.a"; path = "libRNFS.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; + AE36A50852844F9AA3CD5FEA /* RNShare.xcodeproj */ = {isa = PBXFileReference; name = "RNShare.xcodeproj"; path = "../node_modules/react-native-share/ios/RNShare.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 4F79A6A13FE9439B9A907FB8 /* libRNShare.a */ = {isa = PBXFileReference; name = "libRNShare.a"; path = "libRNShare.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -242,6 +247,8 @@ 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + B0314355B1B543DE84BD37CA /* libRNFS.a in Frameworks */, + 904A0798CB1B4D9EA0D492AC /* libRNShare.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -386,6 +393,8 @@ 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + 1CD3E61BDF09456FAF06EECE /* RNFS.xcodeproj */, + AE36A50852844F9AA3CD5FEA /* RNShare.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -446,7 +455,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 610; ORGANIZATIONNAME = Facebook; TargetAttributes = { 13B07F861A680F5B00A75B9A = { @@ -767,6 +776,16 @@ PRODUCT_NAME = auricle; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/auricle\"", + "\"$(SRCROOT)/auricle\"", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-share/ios", + ); }; name = Debug; }; @@ -786,6 +805,16 @@ PRODUCT_NAME = auricle; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/auricle\"", + "\"$(SRCROOT)/auricle\"", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-share/ios", + ); }; name = Release; }; diff --git a/package.json b/package.json index d1799cd..2ae27e4 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,25 @@ { - "name": "auricle", - "version": "0.0.1", - "private": true, - "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start", - "test": "jest" - }, - "dependencies": { - "babel-plugin-transform-es2015-block-scoping": "6.15.0", - "react": "16.0.0-alpha.12", - "react-native": "0.46.1" - }, - "devDependencies": { - "babel-jest": "20.0.3", - "babel-preset-react-native": "2.1.0", - "jest": "20.0.4", - "react-test-renderer": "16.0.0-alpha.12" - }, - "jest": { - "preset": "react-native" - } -} \ No newline at end of file + "name": "auricle", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start", + "test": "jest" + }, + "dependencies": { + "babel-plugin-transform-es2015-block-scoping": "6.15.0", + "react": "16.0.0-alpha.12", + "react-native": "0.46.1", + "react-native-fs": "^2.3.3", + "react-native-share": "^1.0.20" + }, + "devDependencies": { + "babel-jest": "20.0.3", + "babel-preset-react-native": "2.1.0", + "jest": "20.0.4", + "react-test-renderer": "16.0.0-alpha.12" + }, + "jest": { + "preset": "react-native" + } +} diff --git a/src/auricle/events.cljs b/src/auricle/events.cljs index 8ffa22c..3184817 100644 --- a/src/auricle/events.cljs +++ b/src/auricle/events.cljs @@ -10,7 +10,6 @@ [day8.re-frame.http-fx] [auricle.async-storage-fx :as async-storage-fx])) - ;; -- Interceptors ------------------------------------------------------------ ;; ;; See https://github.com/Day8/re-frame/blob/master/docs/Interceptors.md @@ -119,53 +118,3 @@ validate-spec (fn [db [_ reason]] (assoc db :fail reason))) - -(reg-event-fx - :save-api-key - validate-spec - (fn [{:keys [db]} [_]] - (let [api-key (:api-key-input db)] - {:db (assoc db :api-key api-key) - :async-storage-fx/set-item (format-item :api-key api-key)}))) - -(reg-event-db - :api-key-input-changed - validate-spec - (fn [db [_ new-name]] - (assoc db :api-key-input new-name))) - -(reg-event-fx - :export-data - validate-spec - (fn - [{:keys [db]} [_]] - {:db (assoc db :loading true) - :http-xhrio {:method :post - :uri "https://api.paste.ee/v1/pastes" - :timeout 10000 - :format (ajax/json-request-format) - :response-format (ajax/json-response-format {:keywords? true}) - :params {:key (:api-key db) - :description "Auricle export" - :sections [{:name "Speakers" - :contents (str (:speakers db))}]} - :on-failure [:export-data-failure] - :on-success [:export-data-success]}})) - - -(reg-event-db - :export-data-failure - validate-spec - (fn [db [_ reason]] - (assoc db :export-fail reason - :loading false))) - - -(reg-event-db - :export-data-success - validate-spec - (fn [db [_ response]] - (assoc db :export-success response - :loading false))) - - diff --git a/src/auricle/subs.cljs b/src/auricle/subs.cljs index 8de999e..6d927af 100644 --- a/src/auricle/subs.cljs +++ b/src/auricle/subs.cljs @@ -42,7 +42,3 @@ (fn [db _] (:next-time db))) -(reg-sub - :api-key - (fn [db _] - (:api-key db))) diff --git a/src/auricle/ui.cljs b/src/auricle/ui.cljs index 5ecfecf..6cd1032 100644 --- a/src/auricle/ui.cljs +++ b/src/auricle/ui.cljs @@ -70,13 +70,49 @@ :render-row #(r/as-element [speaker-item %]) :enableEmptySections true}]) -(defn export-button [] - [touchable-highlight {:on-press #(dispatch [:export-data])} - [text {:style {:padding 10 :background-color "#FFDD67" :margin-bottom 10} } "Export to Paste.ee"]]) +(def RNFS (js/require "react-native-fs")) +(def what-platform (keyword (str (.-OS (.-Platform ReactNative))))) +(def the-relative-file-path "auricle-saved.txt") +(def the-file-path (str (case what-platform ;;maybe could have used .-DocumentDirectoryPath here + :android (.-ExternalDirectoryPath RNFS) + :ios (.-LibraryDirectoryPath RNFS)) "/" the-relative-file-path)) +(defn rnfs-write-to-file [speakers] (.writeFile RNFS the-file-path (str speakers) "utf8")) +(def alert-class (.-Alert ReactNative)) +(defn alert-buttons-constructor [input] + (loop [input input] + (if (or (string? input) (map? input)) (recur [input]) + (mapv #(loop [input %] (cond (map? input) input + (string? input) {:text input})) input)))) +(defn alert + ([title message buttons] + (alert title message buttons {} )) + ([title message buttons options] + (.alert alert-class (str title) (str message) + (clj->js (alert-buttons-constructor buttons)) + (clj->js options))) + ) +(defn alert-about-write-failed [e] (alert "Writing to file failed" + (str "There was an error writing to file: " e + " →the path: " the-file-path) + "Too bad")) +(defn write-to-file-fn [what-then-fn] + (fn write-to-file [speakers] (-> speakers + rnfs-write-to-file + (.then (what-then-fn)) + (.catch #(alert-about-write-failed %))))) +(def esteban-share (js/require "react-native-share")) +(defn share-filepath [] (.open esteban-share + (clj->js {:url (str "file://" the-file-path) + ;;maybe setting :title or :subject (not :message) would prevent crashes + }))) +(def write-to-file-and-share (write-to-file-fn share-filepath)) +(defn share-button [text-on on-press-func speakers] + [touchable-highlight {:on-press #(on-press-func speakers)} + [text {:style {:padding 10 :background-color "#999999" :margin-bottom 10}} text-on]]) +(defn export-button [speakers] (share-button "Export" write-to-file-and-share speakers)) (defn new-speaker [] - (let [speakers (subscribe [:speakers]) - api-key (subscribe [:api-key])] + (let [speakers (subscribe [:speakers])] (fn [] [view {:style {:flex 1 :flex-direction "column" :justify-content "space-between" :align-items "stretch" :margin-left 20 :margin-right 20}} [text-input {:onChangeText #(dispatch [:speaker-input-changed %]) @@ -86,16 +122,9 @@ :autoCorrect false :style {:flex 1}}] [speaker-list @speakers] - (if-not @api-key - [view {:flex 1 :flex-direction "row"} - [text-input {:onChangeText #(dispatch [:api-key-input-changed %]) - :onSubmitEditing #(dispatch [:save-api-key]) - :placeholder "Paste.ee api key" - :autoCorrect false - :style {:flex 1}}] - [touchable-highlight {:on-press #(dispatch [:load-data :api-key])} - [text "Load api-key"]]] - [export-button])]))) + [view {:flex 1 :flex-direction "row" :justify-content "center" :align-items "center"} + [export-button @speakers]] + ]))) (defn pages [] (let [current-speaker (subscribe [:current-speaker])