From 504e3493ef50782241d581367d1d93ca154d2c6a Mon Sep 17 00:00:00 2001 From: alwx Date: Tue, 7 Mar 2017 20:29:59 +0300 Subject: [PATCH] Runtime permissions (#762) --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 27 +++++- android/app/src/release/AndroidManifest.xml | 7 ++ bots/console/bot.js | 8 ++ ios/StatusIm.xcodeproj/project.pbxproj | 35 -------- .../status/ethereum/module/StatusModule.java | 89 ++++++++++++++++++- .../ios/RCTStatus/RCTStatus.m | 16 ++++ package.json | 2 +- postinstall.sh | 3 +- resources/status.js | 1 + src/status_im/chat/constants.cljs | 2 +- src/status_im/chat/handlers.cljs | 20 ++++- src/status_im/chat/handlers/commands.cljs | 10 +++ src/status_im/chat/handlers/input.cljs | 4 +- src/status_im/chat/sign_up.cljs | 33 +++++-- .../chat/views/message/request_message.cljs | 8 +- src/status_im/components/permissions.cljs | 24 +++++ src/status_im/components/status.cljs | 8 ++ src/status_im/handlers.cljs | 26 +++++- src/status_im/profile/edit/screen.cljs | 9 +- src/status_im/profile/handlers.cljs | 12 --- src/status_im/translations/en.cljs | 1 + 22 files changed, 268 insertions(+), 79 deletions(-) create mode 100644 android/app/src/release/AndroidManifest.xml create mode 100644 src/status_im/components/permissions.cljs diff --git a/android/app/build.gradle b/android/app/build.gradle index 13d3259bf9..91fe9a7289 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,7 +106,7 @@ android { defaultConfig { applicationId "im.status.ethereum" minSdkVersion 18 - targetSdkVersion 22 + targetSdkVersion 23 versionCode getVersionCode() versionName getVersionName() ndk { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index edf372426e..03f6175655 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,12 +1,31 @@ + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bots/console/bot.js b/bots/console/bot.js index f4463508c8..4e9960030a 100644 --- a/bots/console/bot.js +++ b/bots/console/bot.js @@ -705,6 +705,14 @@ status.response({ } }); +status.response({ + name: "grant-permissions", + color: "#7099e6", + description: "Grant permissions", + icon: "lock_white", + executeImmediately: true +}); + status.addListener("on-message-input-change", function (params, context) { return jsSuggestions({code: params.message}, context); }); diff --git a/ios/StatusIm.xcodeproj/project.pbxproj b/ios/StatusIm.xcodeproj/project.pbxproj index 84f8b6096f..e8134e35a9 100644 --- a/ios/StatusIm.xcodeproj/project.pbxproj +++ b/ios/StatusIm.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ 9EE89E271E03FCB7007D3C25 /* libSplashScreen.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B24FC7F21DE718EF00D694FF /* libSplashScreen.a */; }; 9EE89E2D1E03FD9F007D3C25 /* libimageCropPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20A5C9531D927137002C4965 /* libimageCropPicker.a */; }; A6AF670051B842249D520C7B /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7ED174A34D7D42358313368B /* Foundation.ttf */; }; - AD5063BC2B2A4C52ACE0A0B4 /* libUdpSockets.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A96279092BEC4C4B93914F48 /* libUdpSockets.a */; }; AE97D4B08C9F4821B8E9C50B /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 359B076A658B4FBAB5128B03 /* Ionicons.ttf */; }; B23B48FF1E76917B006D4535 /* RobotoMono-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B23B48FE1E76917B006D4535 /* RobotoMono-Medium.ttf */; }; B24FC7FD1DE7195700D694FF /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B24FC7FC1DE7195700D694FF /* Social.framework */; }; @@ -143,13 +142,6 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = TcpSockets; }; - 201067711D477F5E00FA83B6 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 2F0276A9E90843E996A0E762 /* UdpSockets.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = UdpSockets; - }; 201067C31D4789F700FA83B6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 439B6B4B407A4E2AACAFE5BE /* RCTStatus.xcodeproj */; @@ -452,7 +444,6 @@ 20B6B6861D92C42600CC5C6A /* QBImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = QBImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2756305FAFF144C4A6B0A039 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; 2BEE3436791D42248F853999 /* libRCTImageResizer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTImageResizer.a; sourceTree = ""; }; - 2F0276A9E90843E996A0E762 /* UdpSockets.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = UdpSockets.xcodeproj; path = "../node_modules/react-native-udp/ios/UdpSockets.xcodeproj"; sourceTree = ""; }; 305F194186D848FDB07AF34C /* RNFS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFS.xcodeproj; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = ""; }; 359B076A658B4FBAB5128B03 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = ""; }; 38A44830EC5708E89387F641 /* Pods-StatusIm.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatusIm.release.xcconfig"; path = "Pods/Target Support Files/Pods-StatusIm/Pods-StatusIm.release.xcconfig"; sourceTree = ""; }; @@ -486,7 +477,6 @@ 9ED2F45D1D9D52DD00B36508 /* SF-UI-Text-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-UI-Text-Regular.otf"; sourceTree = ""; }; 9ED2F4601D9D577B00B36508 /* SF-UI-Text-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-UI-Text-Bold.otf"; sourceTree = ""; }; 9F1854E6D9654226B1FC8308 /* RCTCamera.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTCamera.xcodeproj; path = "../node_modules/react-native-camera/ios/RCTCamera.xcodeproj"; sourceTree = ""; }; - A96279092BEC4C4B93914F48 /* libUdpSockets.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libUdpSockets.a; sourceTree = ""; }; ACA66A8F16CD2FE21F38738B /* Pods-StatusIm.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatusIm.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StatusIm/Pods-StatusIm.debug.xcconfig"; sourceTree = ""; }; B23B48FE1E76917B006D4535 /* RobotoMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "RobotoMono-Medium.ttf"; sourceTree = ""; }; B24FC7FC1DE7195700D694FF /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; @@ -554,7 +544,6 @@ 25DC9C9DC25846BD8D084888 /* libc++.tbd in Frameworks */, BA68A2377A20496EA737000D /* libz.tbd in Frameworks */, 3E15DFEC1F6F4D7CAE088F49 /* libTcpSockets.a in Frameworks */, - AD5063BC2B2A4C52ACE0A0B4 /* libUdpSockets.a in Frameworks */, E0AD9E8F495A4907B65104BF /* libRCTImageResizer.a in Frameworks */, 5F8585D411844E5981B94F40 /* libRNInstabug.a in Frameworks */, 8E55E6877F950B81C8D711C5 /* libPods-StatusIm.a in Frameworks */, @@ -695,14 +684,6 @@ name = Products; sourceTree = ""; }; - 2010676E1D477F5E00FA83B6 /* Products */ = { - isa = PBXGroup; - children = ( - 201067721D477F5E00FA83B6 /* libUdpSockets.a */, - ); - name = Products; - sourceTree = ""; - }; 201067BA1D4789F700FA83B6 /* Products */ = { isa = PBXGroup; children = ( @@ -857,7 +838,6 @@ 8AE71EE8751F4652B13BFE83 /* RNVectorIcons.xcodeproj */, F090E261B9854867A728CE4F /* RealmReact.xcodeproj */, 38E1A2C8D0734EE99E2B16CE /* TcpSockets.xcodeproj */, - 2F0276A9E90843E996A0E762 /* UdpSockets.xcodeproj */, 439B6B4B407A4E2AACAFE5BE /* RCTStatus.xcodeproj */, 5E5A7625B76441D984EA8C0D /* RCTImageResizer.xcodeproj */, F3548417D8DA4362B6796A54 /* RNInstabug.xcodeproj */, @@ -1181,10 +1161,6 @@ ProductGroup = 201067551D477F5E00FA83B6 /* Products */; ProjectRef = 38E1A2C8D0734EE99E2B16CE /* TcpSockets.xcodeproj */; }, - { - ProductGroup = 2010676E1D477F5E00FA83B6 /* Products */; - ProjectRef = 2F0276A9E90843E996A0E762 /* UdpSockets.xcodeproj */; - }, ); projectRoot = ""; targets = ( @@ -1258,13 +1234,6 @@ remoteRef = 2010676C1D477F5E00FA83B6 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 201067721D477F5E00FA83B6 /* libUdpSockets.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libUdpSockets.a; - remoteRef = 201067711D477F5E00FA83B6 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 201067C41D4789F700FA83B6 /* libRCTStatus.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1813,7 +1782,6 @@ "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", - "$(SRCROOT)/../node_modules/react-native-udp/ios/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer", "$(SRCROOT)/../node_modules/instabug-reactnative/ios/RNInstabug", @@ -1863,7 +1831,6 @@ "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", - "$(SRCROOT)/../node_modules/react-native-udp/ios/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer", "$(SRCROOT)/../node_modules/instabug-reactnative/ios/RNInstabug", @@ -1934,7 +1901,6 @@ "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", - "$(SRCROOT)/../node_modules/react-native-udp/ios/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer", @@ -1993,7 +1959,6 @@ "$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager", "$(SRCROOT)/../node_modules/realm/src/**", "$(SRCROOT)/../node_modules/react-native-tcp/ios/**", - "$(SRCROOT)/../node_modules/react-native-udp/ios/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../modules/react-native-status/ios/RCTStatus/**", "$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer", diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index a0e1503fe0..1564c59775 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -14,6 +14,10 @@ import com.github.status_im.status_go.cmd.Statusgo; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -103,10 +107,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL Activity currentActivity = getCurrentActivity(); - File extStore = Environment.getExternalStorageDirectory(); - String dataFolder = extStore.exists() ? - extStore.getAbsolutePath() + "/ethereum/testnet" : - currentActivity.getApplicationInfo().dataDir + "/ethereum/testnet"; + String dataFolder = currentActivity.getApplicationInfo().dataDir + "/ethereum/testnet"; Log.d(TAG, "Starting Geth node in folder: " + dataFolder); try { @@ -158,6 +159,86 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL Log.d(TAG, "Geth node started"); } + private String getOldExternalDir() { + File extStore = Environment.getExternalStorageDirectory(); + return extStore.exists() ? extStore.getAbsolutePath() + "/ethereum/testnet" : getNewInternalDir(); + } + + private String getNewInternalDir() { + Activity currentActivity = getCurrentActivity(); + return currentActivity.getApplicationInfo().dataDir + "/ethereum/testnet"; + } + + private void deleteDirectory(File folder) { + File[] files = folder.listFiles(); + if (files != null) { + for (File f : files) { + if (f.isDirectory()) { + deleteDirectory(f); + } else { + f.delete(); + } + } + } + folder.delete(); + } + + private void copyDirectory(File sourceLocation, File targetLocation) throws IOException { + if (sourceLocation.isDirectory()) { + if (!targetLocation.exists() && !targetLocation.mkdirs()) { + throw new IOException("Cannot create dir " + targetLocation.getAbsolutePath()); + } + + String[] children = sourceLocation.list(); + for (int i = 0; i < children.length; i++) { + copyDirectory(new File(sourceLocation, children[i]), new File(targetLocation, children[i])); + } + } else { + File directory = targetLocation.getParentFile(); + if (directory != null && !directory.exists() && !directory.mkdirs()) { + throw new IOException("Cannot create dir " + directory.getAbsolutePath()); + } + + InputStream in = new FileInputStream(sourceLocation); + OutputStream out = new FileOutputStream(targetLocation); + + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } + } + + @ReactMethod + public void shouldMoveToInternalStorage(Callback callback) { + String oldDir = getOldExternalDir(); + String newDir = getNewInternalDir(); + + File oldDirFile = new File(oldDir); + File newDirFile = new File(newDir); + + callback.invoke(oldDirFile.exists() && !newDirFile.exists()); + } + + @ReactMethod + public void moveToInternalStorage(Callback callback) { + String oldDir = getOldExternalDir(); + String newDir = getNewInternalDir(); + + try { + File oldDirFile = new File(oldDir); + copyDirectory(oldDirFile, new File(newDir)); + deleteDirectory(oldDirFile); + } catch (IOException e) { + Log.d(TAG, "Moving error: " + e); + } + + callback.invoke(); + } + @ReactMethod public void startNode(Callback callback) { Log.d(TAG, "startNode"); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 2b0ae1d895..90dbc22676 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -185,6 +185,22 @@ RCT_EXPORT_METHOD(startNode:(RCTResponseSenderBlock)onResultCallback) { } } +//////////////////////////////////////////////////////////////////// +#pragma mark - shouldMoveToInternalStorage +//////////////////////////////////////////////////////////////////// shouldMoveToInternalStorage +RCT_EXPORT_METHOD(shouldMoveToInternalStorage:(RCTResponseSenderBlock)onResultCallback) { + // Android only + onResultCallback(@[[NSNull null]]); +} + +//////////////////////////////////////////////////////////////////// +#pragma mark - moveToInternalStorage +//////////////////////////////////////////////////////////////////// moveToInternalStorage +RCT_EXPORT_METHOD(moveToInternalStorage:(RCTResponseSenderBlock)onResultCallback) { + // Android only + onResultCallback(@[[NSNull null]]); +} + //////////////////////////////////////////////////////////////////// #pragma mark - StartNodeRPCServer method //////////////////////////////////////////////////////////////////// createAccount diff --git a/package.json b/package.json index 02535c727b..61707e294f 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "react-native-udp": "^2.0.0", "react-native-vector-icons": "^4.0.1", "react-native-webview-bridge": "github:status-im/react-native-webview-bridge#0.33.12", - "readable-stream": "^1.0.33", + "readable-stream": "1.0.33", "realm": "^0.14.3", "stream-browserify": "^1.0.0", "string_decoder": "^0.10.31", diff --git a/postinstall.sh b/postinstall.sh index 31f6436159..a933169e9e 100755 --- a/postinstall.sh +++ b/postinstall.sh @@ -2,10 +2,9 @@ # rn-nodeify # temporary hack due to https://github.com/facebook/react-native/issues/4968 -./node_modules/.bin/rn-nodeify --install --hack; +./node_modules/.bin/rn-nodeify --install "assert,zlib,buffer,inherits,console,constants,crypto,dns,domain,events,http,https,os,path,process,punycode,querystring,fs,stream,string_decoder,timers,tty,url,util,net,vm" --hack; npm install --save react@16.0.0-alpha.6; npm install --save react-native-tcp@3.2.1; -npm install --save react-native-udp@2.0.0; # symlink for re-natal if ! [ -f re-natal ]; then diff --git a/resources/status.js b/resources/status.js index 42fcb2cd78..39b43c1588 100644 --- a/resources/status.js +++ b/resources/status.js @@ -37,6 +37,7 @@ Command.prototype.create = function (com) { this["on-send"] = com.onSend; this.fullscreen = com.fullscreen; this.request = com.request; + this["execute-immediately?"] = com.executeImmediately; this["sequential-params"] = com.sequentialParams; this.addToCatalog(); diff --git a/src/status_im/chat/constants.cljs b/src/status_im/chat/constants.cljs index a810f96803..9bfee170d5 100644 --- a/src/status_im/chat/constants.cljs +++ b/src/status_im/chat/constants.cljs @@ -3,7 +3,6 @@ (def command-char "/") (def spacing-char " ") (def arg-wrapping-char "\"") -(def masking-char "*") (def bot-char "@") (def input-height 56) @@ -11,6 +10,7 @@ (def input-spacing-top 16) (def crazy-math-message-id "crazy-math-message") +(def move-to-internal-failure-message-id "move-to-internal-failure-message") (def passphrase-message-id "passphraze-message") (def intro-status-message-id "intro-status") (def intro-message1-id "intro-message1") diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index ac987341f2..f5c32eb479 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -132,26 +132,42 @@ (when-not (messages/get-by-id chat-consts/passphrase-message-id) (sign-up-service/account-generation-message))))) +(register-handler :move-to-internal-failure-message + (u/side-effect! + (fn [_] + (when-not (messages/get-by-id chat-consts/move-to-internal-failure-message-id) + (sign-up-service/move-to-internal-failure-message))))) + (register-handler :show-mnemonic (u/side-effect! (fn [_ [_ mnemonic]] (let [crazy-math-message? (messages/get-by-id chat-consts/crazy-math-message-id)] (sign-up-service/passphrase-messages mnemonic crazy-math-message?))))) +(defn- handle-sms [{body :body}] + (when-let [matches (re-matches #"(\d{4})" body)] + (dispatch [:sign-up-confirm (second matches)]))) + (register-handler :sign-up (after (fn [_ [_ phone-number]] (dispatch [:account-update {:phone phone-number}]))) (fn [db [_ phone-number message-id]] + (sign-up-service/start-listening-confirmation-code-sms) (let [formatted (format-phone-number phone-number)] (-> db (assoc :user-phone-number formatted) - sign-up-service/start-listening-confirmation-code-sms (server/sign-up formatted message-id sign-up-service/on-sign-up-response))))) +(register-handler :start-listening-confirmation-code-sms + (fn [db [_ listener]] + (if-not (:confirmation-code-sms-listener db) + (assoc db :confirmation-code-sms-listener listener) + db))) + (register-handler :stop-listening-confirmation-code-sms - (fn [db [_]] + (fn [db] (if (:confirmation-code-sms-listener db) (sign-up-service/stop-listening-confirmation-code-sms db) db))) diff --git a/src/status_im/chat/handlers/commands.cljs b/src/status_im/chat/handlers/commands.cljs index 7a1344cdad..2d88bbabc3 100644 --- a/src/status_im/chat/handlers/commands.cljs +++ b/src/status_im/chat/handlers/commands.cljs @@ -35,3 +35,13 @@ (dispatch [:set-in [:message-data data-type message-id] result]) (when on-requested (on-requested result)))] (status/call-jail jail-id path params callback))))))) + +(handlers/register-handler :execute-command-immediately + (handlers/side-effect! + (fn [_ [_ {command-name :name :as command}]] + (case (keyword command-name) + :grant-permissions + (dispatch [:request-permissions + [:read-external-storage] + #(dispatch [:initialize-geth])]) + (log/debug "ignoring command: " command))))) diff --git a/src/status_im/chat/handlers/input.cljs b/src/status_im/chat/handlers/input.cljs index 58510d09df..3c58c482e4 100644 --- a/src/status_im/chat/handlers/input.cljs +++ b/src/status_im/chat/handlers/input.cljs @@ -97,7 +97,9 @@ (fn [{:keys [current-chat-id] :as db} [_ chat-id text]] (let [chat-id (or chat-id current-chat-id) chat-text (or text (get-in db [:chats chat-id :input-text]) "") - requests (suggestions/get-request-suggestions db chat-text) + requests (->> (suggestions/get-request-suggestions db chat-text) + (remove (fn [{:keys [type]}] + (= type :grant-permissions)))) suggestions (suggestions/get-command-suggestions db chat-text) global-commands (suggestions/get-global-command-suggestions db chat-text) {:keys [dapp?]} (get-in db [:contacts chat-id])] diff --git a/src/status_im/chat/sign_up.cljs b/src/status_im/chat/sign_up.cljs index bed1ef6256..a2ae49677d 100644 --- a/src/status_im/chat/sign_up.cljs +++ b/src/status_im/chat/sign_up.cljs @@ -42,14 +42,15 @@ :from console-chat-id :to "me"}]))) -(defn handle-sms [{body :body}] - (when-let [matches (re-matches #"(\d{4})" body)] - (dispatch [:sign-up-confirm (second matches)]))) - -(defn start-listening-confirmation-code-sms [db] - (if-not (:confirmation-code-sms-listener db) - (assoc db :confirmation-code-sms-listener (add-sms-listener handle-sms)) - db)) +(defn start-listening-confirmation-code-sms [] + (dispatch [:request-permissions + [:receive-sms] + (fn [] + (let [listener (add-sms-listener + (fn [{body :body}] + (when-let [matches (re-matches #"(\d{4})" body)] + (dispatch [:sign-up-confirm (second matches)]))))] + (dispatch [:start-listening-confirmation-code-sms listener])))])) (defn stop-listening-confirmation-code-sms [db] (when-let [listener (:confirmation-code-sms-listener db)] @@ -70,7 +71,9 @@ (defn sync-contacts [] ;; TODO 'on-sync-contacts' is never called - (dispatch [:sync-contacts on-sync-contacts])) + (dispatch [:request-permissions + [:read-contacts] + #(dispatch [:sync-contacts on-sync-contacts])])) (defn on-send-code-response [body] (dispatch [:received-message @@ -122,6 +125,18 @@ :from console-chat-id :to "me"}])) +(defn move-to-internal-failure-message [] + (dispatch [:received-message + {:message-id const/move-to-internal-failure-message-id + :content (command-content + :grant-permissions + (label :t/move-to-internal-failure-message)) + :content-type content-type-command-request + :outgoing false + :chat-id console-chat-id + :from console-chat-id + :to "me"}])) + (defn passphrase-messages [mnemonic crazy-math-message?] (dispatch [:received-message {:message-id const/passphrase-message-id diff --git a/src/status_im/chat/views/message/request_message.cljs b/src/status_im/chat/views/message/request_message.cljs index 43975177a8..b50419ae27 100644 --- a/src/status_im/chat/views/message/request_message.cljs +++ b/src/status_im/chat/views/message/request_message.cljs @@ -56,11 +56,13 @@ :component-will-unmount #(reset! loop? false) :reagent-render - (fn [message-id {command-icon :icon :as command} status-initialized?] + (fn [message-id {:keys [execute-immediately?] command-icon :icon :as command} status-initialized?] (when command [touchable-highlight - {:on-press (when (and (not @answered?) status-initialized?) - #(set-chat-command message-id command)) + {:on-press (if execute-immediately? + #(dispatch [:execute-command-immediately command]) + (when (and (not @answered?) status-initialized?) + #(set-chat-command message-id command))) :style (st/command-request-image-touchable) :accessibility-label (id/chat-request-message-button (:name command))} [animated-view {:style (st/command-request-image-view command scale-anim-val)} diff --git a/src/status_im/components/permissions.cljs b/src/status_im/components/permissions.cljs new file mode 100644 index 0000000000..001dac5875 --- /dev/null +++ b/src/status_im/components/permissions.cljs @@ -0,0 +1,24 @@ +(ns status-im.components.permissions + (:require [taoensso.timbre :as log])) + +(def permissions-class (.-PermissionsAndroid js/ReactNative)) + +(def permissions-map + {:read-external-storage "android.permission.READ_EXTERNAL_STORAGE" + :write-external-storage "android.permission.WRITE_EXTERNAL_STORAGE" + :read-contacts "android.permission.READ_CONTACTS" + :camera "android.permission.CAMERA" + :receive-sms "android.permission.RECEIVE_SMS"}) + +(defn all-granted? [permissions] + (let [permission-vals (distinct (vals permissions))] + (and (= (count permission-vals) 1) + (not= (first permission-vals) "denied")))) + +(defn request-permissions [permissions then else] + (let [permissions (mapv #(get permissions-map %) permissions) + result (.requestMultiple permissions-class (clj->js permissions)) + result (.then result #(if (all-granted? (js->clj %)) + (then) + (when else (else)))) + result (.catch result #(else))])) \ No newline at end of file diff --git a/src/status_im/components/status.cljs b/src/status_im/components/status.cljs index a61ba717c3..69904cd784 100644 --- a/src/status_im/components/status.cljs +++ b/src/status_im/components/status.cljs @@ -61,6 +61,14 @@ (.addListener r/device-event-emitter "gethEvent" #(dispatch [:signal-event (.-jsonEvent %)]))) +(defn should-move-to-internal-storage? [on-result] + (when status + (call-module #(.shouldMoveToInternalStorage status on-result)))) + +(defn move-to-internal-storage [on-result] + (when status + (call-module #(.moveToInternalStorage status on-result)))) + (defn start-node [on-result] (when status (call-module #(.startNode status on-result)))) diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index d5c854d532..ef303951ab 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -6,6 +6,7 @@ [taoensso.timbre :as log] [status-im.utils.crypt :refer [gen-random-bytes]] [status-im.components.status :as status] + [status-im.components.permissions :as permissions] [status-im.utils.handlers :refer [register-handler] :as u] status-im.chat.handlers status-im.group-settings.handlers @@ -122,11 +123,24 @@ (log/debug "Started Node") (enet/get-network #(dispatch [:set :network %]))) +(defn move-to-internal-storage [db] + (status/move-to-internal-storage + (fn [] + (status/start-node + (fn [result] + (node-started db result)))))) + (register-handler :initialize-geth (u/side-effect! (fn [db _] - (log/debug "Starting node") - (status/start-node (fn [result] (node-started db result)))))) + (status/should-move-to-internal-storage? + (fn [should-move?] + (if should-move? + (dispatch [:request-permissions + [:read-external-storage] + #(move-to-internal-storage db) + #(dispatch [:move-to-internal-failure-message])]) + (status/start-node (fn [result] (node-started db result))))))))) (register-handler :signal-event (u/side-effect! @@ -168,6 +182,14 @@ (.resetOkHttpClient webview-bridge))) nil)))) +(register-handler :request-permissions + (u/side-effect! + (fn [_ [_ permissions then else]] + (permissions/request-permissions + permissions + then + else)))) + ;; -- User data -------------------------------------------------------------- (register-handler :load-user-phone-number (fn [db [_]] diff --git a/src/status_im/profile/edit/screen.cljs b/src/status_im/profile/edit/screen.cljs index d628b9bbe8..65e11f171b 100644 --- a/src/status_im/profile/edit/screen.cljs +++ b/src/status_im/profile/edit/screen.cljs @@ -37,8 +37,13 @@ :on-change-text #(dispatch [:set-in [:profile-edit :name] %])}]]) (def profile-icon-options - [{:text (label :t/image-source-gallery) :value #(dispatch [:open-image-picker])} - {:text (label :t/image-source-make-photo) :value #(dispatch [:navigate-to :profile-photo-capture])}]) + [{:text (label :t/image-source-gallery) + :value #(dispatch [:open-image-picker])} + {:text (label :t/image-source-make-photo) + :value (fn [] + (dispatch [:request-permissions + [:camera :write-external-storage] + #(dispatch [:navigate-to :profile-photo-capture])]))}]) (defn edit-profile-bage [contact] [view st/edit-profile-bage diff --git a/src/status_im/profile/handlers.cljs b/src/status_im/profile/handlers.cljs index 27ab94c80f..d90457f59c 100644 --- a/src/status_im/profile/handlers.cljs +++ b/src/status_im/profile/handlers.cljs @@ -26,18 +26,6 @@ (.log js/console type error))] (img->base64 path on-success on-error))))))) -(register-handler :open-image-source-selector - (u/side-effect! - (fn [_ [_ list-selection-fn]] - (list-selection-fn {:title (label :t/image-source-title) - :options [(label :t/image-source-make-photo) (label :t/image-source-gallery)] - :callback (fn [index] - (case index - 0 (dispatch [:navigate-to :profile-photo-capture]) - 1 (dispatch [:open-image-picker]) - :default)) - :cancel-text (label :t/image-source-cancel)})))) - (register-handler :phone-number-change-requested ;; Switch user to the console issuing the !phone command automatically to let him change his phone number. ;; We allow to change phone number only from console because this requires entering SMS verification code. diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index f7d5153fbf..99f8d1bb86 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -122,6 +122,7 @@ :intro-status "Chat with me to setup your account and change your settings!" :intro-message1 "Welcome to Status\nTap this message to set your password & get started!" :account-generation-message "Gimmie a sec, I gotta do some crazy math to generate your account!" + :move-to-internal-failure-message "We need to move some important files from external to internal storage. To do this, we need your permission. We won't be using external storage in future versions." :debug-enabled "Debug server has been launched! Your IP address is {{ip}}. You can now add your DApp by running *status-dev-cli add-dapp --ip {{ip}}* from your computer" ;phone types