diff --git a/.eslintrc.json b/.eslintrc.json index 229d31473..b4435ff30 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -69,7 +69,7 @@ "newlines-between": "always", "pathGroups": [ { - "pattern": "{@(@actions|@app|@assets|@calls|@client|@components|@constants|@context|@database|@helpers|@hooks|@init|@managers|@queries|@screens|@selectors|@share|@store|@telemetry|@typings|@test|@utils)/**,@(@constants|@i18n|@notifications|@store|@websocket)}", + "pattern": "{@(@actions|@app|@assets|@calls|@client|@components|@constants|@context|@database|@helpers|@hooks|@init|@managers|@queries|@screens|@selectors|@share|@store|@telemetry|@typings|@test|@utils)/**,@(@constants|@i18n|@store|@websocket)}", "group": "external", "position": "after" }, diff --git a/.github/workflows/build-android-beta.yml b/.github/workflows/build-android-beta.yml index 2b9f80912..e24d6c663 100644 --- a/.github/workflows/build-android-beta.yml +++ b/.github/workflows/build-android-beta.yml @@ -8,7 +8,7 @@ on: - build-beta-android-[0-9]+ env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.github/workflows/build-android-release.yml b/.github/workflows/build-android-release.yml index d570d66c2..cfdefb84b 100644 --- a/.github/workflows/build-android-release.yml +++ b/.github/workflows/build-android-release.yml @@ -8,7 +8,7 @@ on: - build-release-android-[0-9]+ env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.github/workflows/build-ios-beta.yml b/.github/workflows/build-ios-beta.yml index 4c71a3108..da4d289bd 100644 --- a/.github/workflows/build-ios-beta.yml +++ b/.github/workflows/build-ios-beta.yml @@ -9,7 +9,7 @@ on: - build-beta-sim-[0-9]+ env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.github/workflows/build-ios-release.yml b/.github/workflows/build-ios-release.yml index 0143b5c89..ad1009e08 100644 --- a/.github/workflows/build-ios-release.yml +++ b/.github/workflows/build-ios-release.yml @@ -9,7 +9,7 @@ on: - build-release-sim-[0-9]+ env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index e8b01b65d..98757478d 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -6,7 +6,7 @@ on: - labeled env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57e9448c2..b9cecf7ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: pull_request: env: - NODE_VERSION: 18.7.0 + NODE_VERSION: 20.13.1 TERM: xterm jobs: diff --git a/.gitignore b/.gitignore index 600fc0dfa..9b208b47c 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,6 @@ launch.json # Temporary files created by Metro to check the health of the file watcher .metro-health-check* + +libraries/**/**/build +libraries/**/**/.build \ No newline at end of file diff --git a/.node-version b/.node-version index aacb51810..297d47ba0 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.17 +20.13.1 \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index aacb51810..f203ab89b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17 +20.13.1 diff --git a/.solidarity b/.solidarity index f7aa6366b..61768427b 100644 --- a/.solidarity +++ b/.solidarity @@ -39,7 +39,7 @@ { "rule": "cli", "binary": "pod", - "semver": "1.14.3", + "semver": "1.15.2", "platform": "darwin" } ], diff --git a/ios/Gemfile b/Gemfile similarity index 54% rename from ios/Gemfile rename to Gemfile index 8c4e3a146..813f0e00d 100644 --- a/ios/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem "cocoapods", "1.14.3" +gem "cocoapods", "1.15.2" diff --git a/ios/Gemfile.lock b/Gemfile.lock similarity index 82% rename from ios/Gemfile.lock rename to Gemfile.lock index d4d156e90..4751c61aa 100644 --- a/ios/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,11 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.1.2) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -13,19 +15,19 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.4) + bigdecimal (3.1.8) claide (1.1.0) - cocoapods (1.14.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.3) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -40,7 +42,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.3) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -60,36 +62,37 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.3) connection_pool (2.4.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.16.3) + ffi (1.17.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) - json (2.6.3) - minitest (5.20.0) + json (2.7.2) + minitest (5.23.1) molinillo (0.8.0) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.2.6) + rexml (3.2.9) + strscan ruby-macho (2.5.1) - ruby2_keywords (0.0.5) + strscan (3.1.0) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -101,7 +104,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.14.3) + cocoapods (= 1.15.2) BUNDLED WITH - 2.3.26 + 2.5.11 diff --git a/NOTICE.txt b/NOTICE.txt index 441354b1e..13569ac5c 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1163,42 +1163,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## jail-monkey - -This product contains 'jail-monkey' by Gant Laborde. - -A React Native module for identifying jail-broken, rooted, or mock locations on iOS and Android - -* HOMEPAGE: - * https://github.com/GantMan/jail-monkey#readme - -* LICENSE: MIT - -MIT License - -Copyright (c) 2017 Gant Laborde - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## mime-db @@ -1526,77 +1490,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-button - -This product contains 'react-native-button' by James Ide. - -A button for React apps - -* HOMEPAGE: - * https://github.com/ide/react-native-button - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2015 James Ide - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---- - -## react-native-calendars - -This product contains a modified version of 'react-native-calendars' by Wix.com. - -React Native Calendar Components - -* HOMEPAGE: - * https://github.com/wix/react-native-calendars#readme - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2017 Wix.com - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --- ## react-native-create-thumbnail @@ -1633,42 +1526,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-device-info - -This product contains a modified version of 'react-native-device-info' by Rebecca Hughes. - -Get device information using react-native - -* HOMEPAGE: - * https://github.com/react-native-community/react-native-device-info#readme - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2015 Rebecca Hughes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-document-picker @@ -1741,40 +1598,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-elements - -This product contains 'react-native-elements' by React Native Elements. - -Cross Platform React Native UI Toolkit - -* HOMEPAGE: - * https://github.com/react-native-elements/react-native-elements - -* LICENSE: The MIT License (MIT) - -Copyright (c) 2016 Nader Dabit - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-exception-handler @@ -1881,42 +1704,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-fs - -This product contains 'react-native-fs' by Hagen Hübel. - -Native filesystem access for react-native - -* HOMEPAGE: - * https://github.com/itinance/react-native-fs - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2015 Johannes Lumpe - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-gesture-handler @@ -2049,42 +1836,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-in-app-review - -This product contains 'react-native-in-app-review' by Mina Samir Shafik. - -react native in app review, to rate on Play store, App Store, Generally, the in-app review flow (see figure 1 for play store, figure 2 for ios) can be triggered at any time throughout the user journey of your app. During the flow, the user has the ability - -* HOMEPAGE: - * https://github.com/MinaSamir11/react-native-in-app-review#readme - -* LICENSE: MIT - -MIT License - -Copyright (c) 2020 Mina Samir - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-incall-manager @@ -2151,42 +1902,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-keyboard-tracking-view - -This product contains a modified version of 'react-native-keyboard-tracking-view' by Artal Druk. - -React Native UI component which tracks the keyboard - -* HOMEPAGE: - * https://github.com/wix/react-native-keyboard-tracking-view - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2016 Wix.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-keychain @@ -2223,42 +1938,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-linear-gradient - -This product contains 'react-native-linear-gradient' by Brent Vatne. - -A element for React Native - -* HOMEPAGE: - * https://github.com/react-native-community/react-native-linear-gradient#readme - -* LICENSE: MIT - -MIT License - -Copyright (c) 2016 React Native Community - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-localize @@ -2848,42 +2527,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-webview - -This product contains a modified version of 'react-native-webview' by Jamon Holmgren. - -React Native WebView component for iOS, Android, and Windows 10 (coming soon) - -* HOMEPAGE: - * https://github.com/react-native-community/react-native-webview#readme - -* LICENSE: MIT - -MIT License - -Copyright (c) 2015-present, Facebook, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-syntax-highlighter diff --git a/android/app/build.gradle b/android/app/build.gradle index 4ac525a5d..488d3a6c6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -166,6 +166,7 @@ android { matchingFallbacks = ['release'] } } + // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> @@ -190,7 +191,6 @@ repositories { dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation("com.facebook.react:flipper-integration") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") @@ -198,14 +198,9 @@ dependencies { implementation jscFlavor } - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' - implementation 'io.reactivex.rxjava3:rxjava:3.1.6' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' - implementation 'androidx.window:window-core:1.1.0' - implementation 'androidx.window:window-rxjava3:1.0.0' - implementation 'androidx.window:window:1.0.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "com.google.firebase:firebase-messaging:$firebaseVersion" @@ -247,7 +242,7 @@ configurations.all { // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use -task copyDownloadableDepsToLibs(type: Copy) { +tasks.register('copyDownloadableDepsToLibs', Copy) { from configurations.implementation into 'libs' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3b44cb982..227ee36dc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -94,7 +94,7 @@ android:exported="false" /> inputMap = new HashMap<>(); - saveMap(context, inputMap); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static int getNotificationId(Bundle notification) { - final String postId = notification.getString("post_id"); - final String channelId = notification.getString("channel_id"); - - int notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID; - if (postId != null) { - notificationId = postId.hashCode(); - } else if (channelId != null) { - notificationId = channelId.hashCode(); - } - - return notificationId; - } - - public static StatusBarNotification[] getDeliveredNotifications(Context context) { - final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - return notificationManager.getActiveNotifications(); - } - - public static boolean addNotificationToPreferences(Context context, int notificationId, Bundle notification) { - try { - boolean createSummary = true; - final String serverUrl = notification.getString("server_url"); - final String channelId = notification.getString("channel_id"); - final String rootId = notification.getString("root_id"); - final boolean isCRTEnabled = notification.containsKey("is_crt_enabled") && notification.getString("is_crt_enabled").equals("true"); - - final boolean isThreadNotification = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId); - final String groupId = isThreadNotification ? rootId : channelId; - - Map notificationsPerServer = loadMap(context); - JSONObject notificationsInServer = notificationsPerServer.get(serverUrl); - if (notificationsInServer == null) { - notificationsInServer = new JSONObject(); - } - - JSONObject notificationsInGroup = notificationsInServer.optJSONObject(groupId); - if (notificationsInGroup == null) { - notificationsInGroup = new JSONObject(); - } - - if (notificationsInGroup.length() > 0) { - createSummary = false; - } - - notificationsInGroup.put(String.valueOf(notificationId), false); - - if (createSummary) { - // Add the summary notification id as well - notificationsInGroup.put(String.valueOf(notificationId + 1), true); - } - notificationsInServer.put(groupId, notificationsInGroup); - notificationsPerServer.put(serverUrl, notificationsInServer); - saveMap(context, notificationsPerServer); - - return createSummary; - } catch(Exception e) { - e.printStackTrace(); - return false; - } - } - - public static void dismissNotification(Context context, Bundle notification) { - final boolean isCRTEnabled = notification.containsKey("is_crt_enabled") && notification.getString("is_crt_enabled").equals("true"); - final String serverUrl = notification.getString("server_url"); - final String channelId = notification.getString("channel_id"); - final String rootId = notification.getString("root_id"); - - int notificationId = getNotificationId(notification); - - if (!android.text.TextUtils.isEmpty(serverUrl) && !android.text.TextUtils.isEmpty(channelId)) { - boolean isThreadNotification = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId); - String notificationIdStr = String.valueOf(notificationId); - String groupId = isThreadNotification ? rootId : channelId; - - Map notificationsPerServer = loadMap(context); - JSONObject notificationsInServer = notificationsPerServer.get(serverUrl); - if (notificationsInServer == null) { - return; - } - - JSONObject notificationsInGroup = notificationsInServer.optJSONObject(groupId); - if (notificationsInGroup == null) { - return; - } - boolean isSummary = notificationsInGroup.optBoolean(notificationIdStr); - notificationsInGroup.remove(notificationIdStr); - - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - notificationManager.cancel(notificationId); - StatusBarNotification[] statusNotifications = getDeliveredNotifications(context); - boolean hasMore = false; - - for (final StatusBarNotification status : statusNotifications) { - Bundle bundle = status.getNotification().extras; - if (isThreadNotification) { - hasMore = bundle.containsKey("root_id") && bundle.getString("root_id").equals(rootId); - } else { - hasMore = bundle.containsKey("channel_id") && bundle.getString("channel_id").equals(channelId); - } - if (hasMore) break; - } - - if (!hasMore || isSummary) { - notificationsInServer.remove(groupId); - } else { - try { - notificationsInServer.put(groupId, notificationsInGroup); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - notificationsPerServer.put(serverUrl, notificationsInServer); - saveMap(context, notificationsPerServer); - } - } - - public static void removeChannelNotifications(Context context, String serverUrl, String channelId) { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - Map notificationsPerServer = loadMap(context); - JSONObject notificationsInServer = notificationsPerServer.get(serverUrl); - - if (notificationsInServer != null) { - notificationsInServer.remove(channelId); - notificationsPerServer.put(serverUrl, notificationsInServer); - saveMap(context, notificationsPerServer); - } - - StatusBarNotification[] notifications = getDeliveredNotifications(context); - for (StatusBarNotification sbn:notifications) { - Notification n = sbn.getNotification(); - Bundle bundle = n.extras; - String cId = bundle.getString("channel_id"); - String rootId = bundle.getString("root_id"); - boolean isCRTEnabled = bundle.containsKey("is_crt_enabled") && bundle.getString("is_crt_enabled").equals("true"); - boolean skipThreadNotification = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId); - if (Objects.equals(cId, channelId) && !skipThreadNotification) { - notificationManager.cancel(sbn.getId()); - } - } - } - - public static void removeThreadNotifications(Context context, String serverUrl, String threadId) { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - Map notificationsPerServer = loadMap(context); - JSONObject notificationsInServer = notificationsPerServer.get(serverUrl); - - StatusBarNotification[] notifications = getDeliveredNotifications(context); - for (StatusBarNotification sbn:notifications) { - Notification n = sbn.getNotification(); - Bundle bundle = n.extras; - String rootId = bundle.getString("root_id"); - String postId = bundle.getString("post_id"); - if (Objects.equals(rootId, threadId)) { - notificationManager.cancel(sbn.getId()); - } - - if (Objects.equals(postId, threadId)) { - String channelId = bundle.getString("channel_id"); - int id = sbn.getId(); - if (notificationsInServer != null && channelId != null) { - JSONObject notificationsInChannel = notificationsInServer.optJSONObject(channelId); - if (notificationsInChannel != null) { - notificationsInChannel.remove(String.valueOf(id)); - try { - notificationsInServer.put(channelId, notificationsInChannel); - } catch (JSONException e) { - e.printStackTrace(); - } - } - } - notificationManager.cancel(id); - } - } - - if (notificationsInServer != null) { - notificationsInServer.remove(threadId); - notificationsPerServer.put(serverUrl, notificationsInServer); - saveMap(context, notificationsPerServer); - } - } - - public static void removeServerNotifications(Context context, String serverUrl) { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - Map notificationsPerServer = loadMap(context); - notificationsPerServer.remove(serverUrl); - saveMap(context, notificationsPerServer); - StatusBarNotification[] notifications = getDeliveredNotifications(context); - for (StatusBarNotification sbn:notifications) { - Notification n = sbn.getNotification(); - Bundle bundle = n.extras; - String url = bundle.getString("server_url"); - if (Objects.equals(url, serverUrl)) { - notificationManager.cancel(sbn.getId()); - } - } - } - - public static void clearChannelOrThreadNotifications(Context context, Bundle notification) { - final String serverUrl = notification.getString("server_url"); - final String channelId = notification.getString("channel_id"); - final String rootId = notification.getString("root_id"); - if (channelId != null) { - final boolean isCRTEnabled = notification.containsKey("is_crt_enabled") && notification.getString("is_crt_enabled").equals("true"); - // rootId is available only when CRT is enabled & clearing the thread - final boolean isClearThread = isCRTEnabled && !android.text.TextUtils.isEmpty(rootId); - - if (isClearThread) { - removeThreadNotifications(context, serverUrl, rootId); - } else { - removeChannelNotifications(context, serverUrl, channelId); - } - } - } - - - /** - * Map Structure - * - * { serverUrl: { groupId: { notification1: true, notification2: false } } } - * summary notification has a value of true - * - */ - - private static void saveMap(Context context, Map inputMap) { - SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE); - if (pSharedPref != null) { - JSONObject json = new JSONObject(inputMap); - String jsonString = json.toString(); - SharedPreferences.Editor editor = pSharedPref.edit(); - editor.remove(NOTIFICATIONS_IN_GROUP).apply(); - editor.putString(NOTIFICATIONS_IN_GROUP, jsonString); - editor.apply(); - } - } - - private static Map loadMap(Context context) { - Map outputMap = new HashMap<>(); - if (context != null) { - SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE); - try { - if (pSharedPref != null) { - String jsonString = pSharedPref.getString(NOTIFICATIONS_IN_GROUP, (new JSONObject()).toString()); - JSONObject json = new JSONObject(jsonString); - Iterator servers = json.keys(); - - while (servers.hasNext()) { - String serverUrl = servers.next(); - JSONObject notificationGroup = json.getJSONObject(serverUrl); - outputMap.put(serverUrl, notificationGroup); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - return outputMap; - } -} diff --git a/android/app/src/main/java/com/mattermost/helpers/RealPathUtil.java b/android/app/src/main/java/com/mattermost/helpers/RealPathUtil.java deleted file mode 100644 index 1508b40c1..000000000 --- a/android/app/src/main/java/com/mattermost/helpers/RealPathUtil.java +++ /dev/null @@ -1,274 +0,0 @@ -package com.mattermost.helpers; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.provider.OpenableColumns; -import android.content.ContentResolver; -import android.os.Environment; -import android.webkit.MimeTypeMap; -import android.util.Log; -import android.text.TextUtils; - -import android.os.ParcelFileDescriptor; - -import java.io.*; -import java.nio.channels.FileChannel; - -// Class based on DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3 - -public class RealPathUtil { - public static final String CACHE_DIR_NAME = "mmShare"; - public static String getRealPathFromURI(final Context context, final Uri uri) { - - // DocumentProvider - if (DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - if ("primary".equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } - } else if (isDownloadsDocument(uri)) { - // DownloadsProvider - - final String id = DocumentsContract.getDocumentId(uri); - if (!TextUtils.isEmpty(id)) { - if (id.startsWith("raw:")) { - return id.replaceFirst("raw:", ""); - } - try { - return getPathFromSavingTempFile(context, uri); - } catch (NumberFormatException e) { - Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri); - return null; - } - } - } else if (isMediaDocument(uri)) { - // MediaProvider - - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[] { - split[1] - }; - - String name = getDataColumn(context, contentUri, selection, selectionArgs); - if (!TextUtils.isEmpty(name)) { - return name; - } - - return getPathFromSavingTempFile(context, uri); - } - } - - if ("content".equalsIgnoreCase(uri.getScheme())) { - // MediaStore (and general) - - if (isGooglePhotosUri(uri)) { - return uri.getLastPathSegment(); - } - - // Try save to tmp file, and return tmp file path - return getPathFromSavingTempFile(context, uri); - } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - - public static String getPathFromSavingTempFile(Context context, final Uri uri) { - File tmpFile; - String fileName = ""; - - if (uri == null || uri.isRelative()) { - return null; - } - - // Try and get the filename from the Uri - try { - Cursor returnCursor = - context.getContentResolver().query(uri, null, null, null, null); - int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - returnCursor.moveToFirst(); - fileName = sanitizeFilename(returnCursor.getString(nameIndex)); - returnCursor.close(); - } catch (Exception e) { - // just continue to get the filename with the last segment of the path - } - - try { - if (TextUtils.isEmpty(fileName)) { - fileName = sanitizeFilename(uri.getLastPathSegment().trim()); - } - - - File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME); - boolean cacheDirExists = cacheDir.exists(); - if (!cacheDirExists) { - cacheDirExists = cacheDir.mkdirs(); - } - - if (cacheDirExists) { - tmpFile = new File(cacheDir, fileName); - boolean fileCreated = tmpFile.createNewFile(); - - if (fileCreated) { - ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); - - try (FileInputStream inputSrc = new FileInputStream(pfd.getFileDescriptor())) { - FileChannel src = inputSrc.getChannel(); - try (FileOutputStream outputDst = new FileOutputStream(tmpFile)) { - FileChannel dst = outputDst.getChannel(); - dst.transferFrom(src, 0, src.size()); - src.close(); - dst.close(); - } - } - - pfd.close(); - } - return tmpFile.getAbsolutePath(); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - - return null; - } - - public static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) { - - Cursor cursor = null; - final String column = "_data"; - final String[] projection = { - column - }; - - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, - null); - if (cursor != null && cursor.moveToFirst()) { - final int index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(index); - } - } finally { - if (cursor != null) - cursor.close(); - } - return null; - } - - - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - public static boolean isGooglePhotosUri(Uri uri) { - return "com.google.android.apps.photos.content".equals(uri.getAuthority()); - } - - public static String getExtension(String uri) { - String extension = ""; - if (uri == null) { - return extension; - } - - extension = MimeTypeMap.getFileExtensionFromUrl(uri); - if (!extension.equals("")) { - return extension; - } - - int dot = uri.lastIndexOf("."); - if (dot >= 0) { - return uri.substring(dot); - } else { - // No extension. - return ""; - } - } - - public static String getMimeType(File file) { - - String extension = getExtension(file.getName()); - - if (extension.length() > 0) - return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); - - return "application/octet-stream"; - } - - public static String getMimeType(String filePath) { - File file = new File(filePath); - return getMimeType(file); - } - - public static String getMimeTypeFromUri(final Context context, final Uri uri) { - try { - ContentResolver cR = context.getContentResolver(); - return cR.getType(uri); - } catch (Exception e) { - return "application/octet-stream"; - } - } - - public static void deleteTempFiles(final File dir) { - try { - if (dir.isDirectory()) { - deleteRecursive(dir); - } - } catch (Exception e) { - // do nothing - } - } - - private static void deleteRecursive(File fileOrDirectory) { - if (fileOrDirectory.isDirectory()) { - File[] files = fileOrDirectory.listFiles(); - if (files != null) { - for (File child : files) - deleteRecursive(child); - } - } - - if (!fileOrDirectory.delete()) { - Log.i("ReactNative", "Couldn't delete file " + fileOrDirectory.getName()); - } - } - - private static String sanitizeFilename(String filename) { - if (filename == null) { - return null; - } - - File f = new File(filename); - return f.getName(); - } -} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.kt b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.kt index 192355c6c..f19ee2d20 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.kt +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.kt @@ -8,10 +8,9 @@ import androidx.core.app.NotificationCompat import com.mattermost.helpers.CustomPushNotificationHelper import com.mattermost.helpers.DatabaseHelper import com.mattermost.helpers.Network -import com.mattermost.helpers.NotificationHelper import com.mattermost.helpers.PushNotificationDataHelper import com.mattermost.helpers.database_extension.getServerUrlForIdentifier -import com.mattermost.share.ShareModule +import com.mattermost.rnutils.helpers.NotificationHelper import com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME import com.wix.reactnativenotifications.core.AppLaunchHelper import com.wix.reactnativenotifications.core.AppLifecycleFacade @@ -104,8 +103,7 @@ class CustomPushNotification( when (type) { CustomPushNotificationHelper.PUSH_TYPE_MESSAGE, CustomPushNotificationHelper.PUSH_TYPE_SESSION -> { - val shareModule = ShareModule.getInstance() - val currentActivityName = shareModule?.currentActivityName ?: "" + val currentActivityName = mAppLifecycleFacade.runningReactContext.currentActivity?.componentName?.className ?: "" Log.i("ReactNative", currentActivityName) if (!mAppLifecycleFacade.isAppVisible() || currentActivityName != "MainActivity") { var createSummary = type == CustomPushNotificationHelper.PUSH_TYPE_MESSAGE diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.kt b/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.kt index 720bc5666..049f88542 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.kt +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.kt @@ -1,15 +1,15 @@ package com.mattermost.rnbeta -import android.os.Bundle - -import android.view.KeyEvent import android.content.res.Configuration - +import android.os.Bundle +import android.view.KeyEvent import com.facebook.react.ReactActivityDelegate -import com.reactnativenavigation.NavigationActivity -import com.github.emilioicai.hwkeyboardevent.HWKeyboardEventModule import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint import com.facebook.react.defaults.DefaultReactActivityDelegate +import com.mattermost.hardware.keyboard.MattermostHardwareKeyboardImpl +import com.mattermost.rnutils.helpers.FoldableObserver +import com.reactnativenavigation.NavigationActivity +import expo.modules.ReactActivityDelegateWrapper class MainActivity : NavigationActivity() { private var HWKeyboardConnected = false @@ -26,7 +26,8 @@ class MainActivity : NavigationActivity() { * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] */ override fun createReactActivityDelegate(): ReactActivityDelegate = - DefaultReactActivityDelegate(this, mainComponentName, DefaultNewArchitectureEntryPoint.fabricEnabled) + ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, + DefaultReactActivityDelegate(this, mainComponentName, DefaultNewArchitectureEntryPoint.fabricEnabled)) override fun onCreate(savedInstanceState: Bundle?) { @@ -46,6 +47,11 @@ class MainActivity : NavigationActivity() { foldableObserver.onStop() } + override fun onDestroy() { + super.onDestroy() + foldableObserver.onDestroy() + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) { @@ -60,24 +66,11 @@ class MainActivity : NavigationActivity() { reactGateway.onWindowFocusChanged(hasFocus) } - /* - https://mattermost.atlassian.net/browse/MM-10601 - Required by react-native-hw-keyboard-event - (https://github.com/emilioicai/react-native-hw-keyboard-event) - */ override fun dispatchKeyEvent(event: KeyEvent): Boolean { if (HWKeyboardConnected) { - val keyCode = event.keyCode - val keyAction = event.action - if (keyAction == KeyEvent.ACTION_UP) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - val keyPressed = if (event.isShiftPressed) "shift-enter" else "enter" - HWKeyboardEventModule.getInstance().keyPressed(keyPressed) - return true - } else if (keyCode == KeyEvent.KEYCODE_K && event.isCtrlPressed) { - HWKeyboardEventModule.getInstance().keyPressed("find-channels") - return true - } + val ok = MattermostHardwareKeyboardImpl.dispatchKeyEvent(event) + if (ok) { + return true } } return super.dispatchKeyEvent(event) diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.kt b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.kt index 6d7693f55..fb323ac48 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.kt +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.kt @@ -1,151 +1,67 @@ package com.mattermost.rnbeta + +import android.annotation.SuppressLint import android.content.Context +import android.content.res.Configuration import android.os.Bundle import android.util.Log - import com.facebook.react.PackageList import com.facebook.react.ReactHost +import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage -import com.facebook.react.TurboReactPackage -import com.facebook.react.bridge.JSIModulePackage -import com.facebook.react.bridge.JSIModuleSpec -import com.facebook.react.bridge.JavaScriptContextHolder -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.flipper.ReactNativeFlipper -import com.facebook.react.module.model.ReactModuleInfo -import com.facebook.react.module.model.ReactModuleInfoProvider import com.facebook.react.modules.network.OkHttpClientProvider import com.facebook.soloader.SoLoader - -import com.mattermost.helpers.RealPathUtil import com.mattermost.networkclient.RCTOkHttpClientFactory -import com.mattermost.share.ShareModule - -import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage - +import com.mattermost.rnshare.helpers.RealPathUtil +import com.nozbe.watermelondb.jsi.JSIInstaller import com.reactnativenavigation.NavigationApplication - import com.wix.reactnativenotifications.RNNotificationsPackage import com.wix.reactnativenotifications.core.AppLaunchHelper import com.wix.reactnativenotifications.core.AppLifecycleFacade import com.wix.reactnativenotifications.core.JsIOHelper import com.wix.reactnativenotifications.core.notification.INotificationsApplication import com.wix.reactnativenotifications.core.notification.IPushNotification - +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper +import expo.modules.image.okhttp.ExpoImageOkHttpClientGlideModule import java.io.File class MainApplication : NavigationApplication(), INotificationsApplication { - var instance: MainApplication? = null - var sharedExtensionIsOpened = false + private var listenerAdded = false override val reactNativeHost: ReactNativeHost = - object : DefaultReactNativeHost(this) { - override fun getPackages(): List = - PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here, for example: - // add(MyReactNativePackage()) - add(RNNotificationsPackage(this@MainApplication)) - add(object : TurboReactPackage() { - override fun getModule( - name: String, - reactContext: ReactApplicationContext - ): NativeModule { - return when (name) { - "MattermostManaged" -> MattermostManagedModule.getInstance( - reactContext - ) - "MattermostShare" -> ShareModule.getInstance(reactContext) - "Notifications" -> NotificationsModule.getInstance( - instance, - reactContext - ) - "SplitView" -> SplitViewModule.getInstance( - reactContext - ) - else -> - throw IllegalArgumentException("Could not find module $name") - } - } + ReactNativeHostWrapper(this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + add(RNNotificationsPackage(this@MainApplication)) + } - override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { - return ReactModuleInfoProvider { - val map: MutableMap = java.util.HashMap() - map["MattermostManaged"] = ReactModuleInfo( - "MattermostManaged", - "com.mattermost.rnbeta.MattermostManagedModule", - false, - false, - false, - false - ) - map["MattermostShare"] = ReactModuleInfo( - "MattermostShare", - "com.mattermost.share.ShareModule", - false, - false, - false, - false - ) - map["Notifications"] = ReactModuleInfo( - "Notifications", - "com.mattermost.rnbeta.NotificationsModule", - false, - false, - false, - false - ) - map["SplitView"] = ReactModuleInfo( - "SplitView", - "com.mattermost.rnbeta.SplitViewModule", - false, - false, - false, - false - ) - map - } - } - }) - } + override fun getJSMainModuleName(): String = "index" - override fun getJSIModulePackage(): JSIModulePackage { - return JSIModulePackage { reactApplicationContext: ReactApplicationContext?, jsContext: JavaScriptContextHolder? -> - val modules = - mutableListOf>() - modules.addAll( - WatermelonDBJSIPackage().getJSIModules( - reactApplicationContext, - jsContext - ) - ) - modules - } - } + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - override fun getJSMainModuleName(): String = "index" - - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + }) override val reactHost: ReactHost - get() = getDefaultReactHost(this.applicationContext, reactNativeHost) + get() = getDefaultReactHost(applicationContext, reactNativeHost) override fun onCreate() { super.onCreate() - instance = this - val context: Context = applicationContext // Delete any previous temp files created by the app - val tempFolder = File(context.cacheDir, RealPathUtil.CACHE_DIR_NAME) + val tempFolder = File(applicationContext.cacheDir, RealPathUtil.CACHE_DIR_NAME) RealPathUtil.deleteTempFiles(tempFolder) Log.i("ReactNative", "Cleaning temp cache " + tempFolder.absolutePath) @@ -157,13 +73,20 @@ class MainApplication : NavigationApplication(), INotificationsApplication { // with a cookie jar defined in APIClientModule and an interceptor to intercept all // requests that originate from React Native's OKHttpClient OkHttpClientProvider.setOkHttpClientFactory(RCTOkHttpClientFactory()) + ExpoImageOkHttpClientGlideModule.okHttpClient = RCTOkHttpClientFactory().createNewNetworkModuleClient() SoLoader.init(this, false) if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. - load() + load(bridgelessEnabled = false) } - ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + ApplicationLifecycleDispatcher.onApplicationCreate(this) + registerJSIModules() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) } override fun getPushNotification( @@ -180,4 +103,38 @@ class MainApplication : NavigationApplication(), INotificationsApplication { JsIOHelper() ) } + + @SuppressLint("VisibleForTests") + private fun runOnJSQueueThread(action: () -> Unit) { + reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread { + action() + } ?: UiThreadUtil.runOnUiThread { + reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread { + action() + } + } + } + + @Suppress("DEPRECATION") + private fun registerJSIModules() { + val reactInstanceManager = reactNativeHost.reactInstanceManager + + if (!listenerAdded) { + listenerAdded = true + reactInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener { + override fun onReactContextInitialized(context: ReactContext) { + runOnJSQueueThread { + registerWatermelonJSI(context) + } + } + }) + } + } + + private fun registerWatermelonJSI(context: ReactContext) { + val holder = context.javaScriptContextHolder?.get() + if (holder != null) { + JSIInstaller.install(context, holder) + } + } } diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java b/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java deleted file mode 100644 index 765fc2fe8..000000000 --- a/android/app/src/main/java/com/mattermost/rnbeta/MattermostManagedModule.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.mattermost.rnbeta; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.FileProvider; - -import com.facebook.react.bridge.ActivityEventListener; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.BaseActivityEventListener; -import com.facebook.react.bridge.GuardedResultAsyncTask; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; - -import com.mattermost.helpers.Credentials; -import com.reactlibrary.createthumbnail.CreateThumbnailModule; -import com.mattermost.helpers.RealPathUtil; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.lang.ref.WeakReference; -import java.net.URL; -import java.nio.channels.FileChannel; -import java.util.Objects; - -public class MattermostManagedModule extends ReactContextBaseJavaModule { - private static final String SAVE_EVENT = "MattermostManagedSaveFile"; - private static final Integer SAVE_REQUEST = 38641; - private static MattermostManagedModule instance; - private ReactApplicationContext reactContext; - - private Promise mPickerPromise; - private String fileContent; - - private MattermostManagedModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - // Let the document provider know you're done by closing the stream. - ActivityEventListener mActivityEventListener = new BaseActivityEventListener() { - @Override - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) { - if (requestCode == SAVE_REQUEST) { - if (mPickerPromise != null) { - if (resultCode == Activity.RESULT_CANCELED) { - mPickerPromise.reject(SAVE_EVENT, "Save operation cancelled"); - } else if (resultCode == Activity.RESULT_OK) { - Uri uri = intent.getData(); - if (uri == null) { - mPickerPromise.reject(SAVE_EVENT, "No data found"); - } else { - try { - new SaveDataTask(reactContext, fileContent, uri).execute(); - mPickerPromise.resolve(uri.toString()); - } catch (Exception e) { - mPickerPromise.reject(SAVE_EVENT, e.getMessage()); - } - } - } - - mPickerPromise = null; - } else if (resultCode == Activity.RESULT_OK) { - try { - Uri uri = intent.getData(); - if (uri != null) - new SaveDataTask(reactContext, fileContent, uri).execute(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - }; - reactContext.addActivityEventListener(mActivityEventListener); - } - - public static MattermostManagedModule getInstance(ReactApplicationContext reactContext) { - if (instance == null) { - instance = new MattermostManagedModule(reactContext); - } else { - instance.reactContext = reactContext; - } - - return instance; - } - - public static MattermostManagedModule getInstance() { - return instance; - } - - public void sendEvent(String eventName, - @Nullable WritableMap params) { - this.reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); - } - - @Override - @NonNull - public String getName() { - return "MattermostManaged"; - } - - @ReactMethod - public void getFilePath(String filePath, Promise promise) { - Activity currentActivity = getCurrentActivity(); - WritableMap map = Arguments.createMap(); - - if (currentActivity != null) { - Uri uri = Uri.parse(filePath); - String path = RealPathUtil.getRealPathFromURI(currentActivity, uri); - if (path != null) { - String text = "file://" + path; - map.putString("filePath", text); - } - } - - promise.resolve(map); - } - - @ReactMethod - public void saveFile(String path, final Promise promise) { - Uri contentUri; - String filename = ""; - if(path.startsWith("content://")) { - contentUri = Uri.parse(path); - } else { - File newFile = new File(path); - filename = newFile.getName(); - Activity currentActivity = getCurrentActivity(); - if(currentActivity == null) { - promise.reject(SAVE_EVENT, "Activity doesn't exist"); - return; - } - try { - final String packageName = currentActivity.getPackageName(); - final String authority = packageName + ".provider"; - contentUri = FileProvider.getUriForFile(currentActivity, authority, newFile); - } - catch(IllegalArgumentException e) { - promise.reject(SAVE_EVENT, e.getMessage()); - return; - } - } - - if(contentUri == null) { - promise.reject(SAVE_EVENT, "Invalid file"); - return; - } - - String extension = MimeTypeMap.getFileExtensionFromUrl(path).toLowerCase(); - String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mimeType == null) { - mimeType = RealPathUtil.getMimeType(path); - } - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_CREATE_DOCUMENT); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(mimeType); - intent.putExtra(Intent.EXTRA_TITLE, filename); - - PackageManager pm = Objects.requireNonNull(getCurrentActivity()).getPackageManager(); - if (intent.resolveActivity(pm) != null) { - try { - getCurrentActivity().startActivityForResult(intent, SAVE_REQUEST); - mPickerPromise = promise; - fileContent = path; - } - catch(Exception e) { - promise.reject(SAVE_EVENT, e.getMessage()); - } - } else { - try { - if(mimeType == null) { - throw new Exception("It wasn't possible to detect the type of the file"); - } - throw new Exception("No app associated with this mime type"); - } - catch(Exception e) { - promise.reject(SAVE_EVENT, e.getMessage()); - } - } - } - - @ReactMethod - public void createThumbnail(ReadableMap options, Promise promise) { - try { - WritableMap optionsMap = Arguments.createMap(); - optionsMap.merge(options); - String url = options.hasKey("url") ? options.getString("url") : ""; - URL videoUrl = new URL(url); - String serverUrl = videoUrl.getProtocol() + "://" + videoUrl.getHost() + ":" + videoUrl.getPort(); - String token = Credentials.getCredentialsForServerSync(this.reactContext, serverUrl); - if (!TextUtils.isEmpty(token)) { - WritableMap headers = Arguments.createMap(); - if (optionsMap.hasKey("headers")) { - headers.merge(Objects.requireNonNull(optionsMap.getMap("headers"))); - } - headers.putString("Authorization", "Bearer " + token); - optionsMap.putMap("headers", headers); - } - CreateThumbnailModule thumb = new CreateThumbnailModule(this.reactContext); - thumb.create(optionsMap.copy(), promise); - } catch (Exception e) { - promise.reject("CreateThumbnail_ERROR", e); - } - } - - private static class SaveDataTask extends GuardedResultAsyncTask { - private final WeakReference weakContext; - private final String fromFile; - private final Uri toFile; - - protected SaveDataTask(ReactApplicationContext reactContext, String path, Uri destination) { - super(reactContext.getExceptionHandler()); - weakContext = new WeakReference<>(reactContext.getApplicationContext()); - fromFile = path; - toFile = destination; - } - - @Override - protected Object doInBackgroundGuarded() { - try { - ParcelFileDescriptor pfd = weakContext.get().getContentResolver().openFileDescriptor(toFile, "w"); - File input = new File(this.fromFile); - try (FileInputStream fileInputStream = new FileInputStream(input)) { - try (FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor())) { - FileChannel source = fileInputStream.getChannel(); - FileChannel dest = fileOutputStream.getChannel(); - dest.transferFrom(source, 0, source.size()); - source.close(); - dest.close(); - } - } - pfd.close(); - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - @Override - protected void onPostExecuteGuarded(Object o) { - - } - } -} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java index 3299145ee..6f72fb5ff 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationDismissService.java @@ -6,7 +6,7 @@ import android.app.IntentService; import android.os.Bundle; import android.util.Log; -import com.mattermost.helpers.NotificationHelper; +import com.mattermost.rnutils.helpers.NotificationHelper; import com.wix.reactnativenotifications.core.NotificationIntentAdapter; public class NotificationDismissService extends IntentService { @@ -19,7 +19,7 @@ public class NotificationDismissService extends IntentService { final Context context = getApplicationContext(); final Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent); - NotificationHelper.dismissNotification(context, bundle); + NotificationHelper.INSTANCE.dismissNotification(context, bundle); Log.i("ReactNative", "Dismiss notification"); } } diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsModule.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationsModule.java deleted file mode 100644 index 6de0faccd..000000000 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsModule.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.mattermost.rnbeta; - -import android.app.Notification; -import android.content.Context; -import android.os.Bundle; -import android.service.notification.StatusBarNotification; - -import androidx.annotation.NonNull; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.mattermost.helpers.NotificationHelper; - -import java.util.Set; - -public class NotificationsModule extends ReactContextBaseJavaModule { - private static NotificationsModule instance; - private final MainApplication mApplication; - - private NotificationsModule(MainApplication application, ReactApplicationContext reactContext) { - super(reactContext); - mApplication = application; - } - - public static NotificationsModule getInstance(MainApplication application, ReactApplicationContext reactContext) { - if (instance == null) { - instance = new NotificationsModule(application, reactContext); - } - - return instance; - } - - @NonNull - @Override - public String getName() { - return "Notifications"; - } - - @ReactMethod - public void getDeliveredNotifications(final Promise promise) { - Context context = mApplication.getApplicationContext(); - StatusBarNotification[] notifications = NotificationHelper.getDeliveredNotifications(context); - WritableArray result = Arguments.createArray(); - for (StatusBarNotification sbn:notifications) { - WritableMap map = Arguments.createMap(); - Notification n = sbn.getNotification(); - Bundle bundle = n.extras; - Set keys = bundle.keySet(); - for (String key: keys) { - map.putString(key, bundle.getString(key)); - } - result.pushMap(map); - } - promise.resolve(result); - } - - @ReactMethod - public void removeChannelNotifications(String serverUrl, String channelId) { - Context context = mApplication.getApplicationContext(); - NotificationHelper.removeChannelNotifications(context, serverUrl, channelId); - } - - @ReactMethod - public void removeThreadNotifications(String serverUrl, String threadId) { - Context context = mApplication.getApplicationContext(); - NotificationHelper.removeThreadNotifications(context, serverUrl, threadId); - } - - @ReactMethod - public void removeServerNotifications(String serverUrl) { - Context context = mApplication.getApplicationContext(); - NotificationHelper.removeServerNotifications(context, serverUrl); - } -} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/SplitViewModule.kt b/android/app/src/main/java/com/mattermost/rnbeta/SplitViewModule.kt deleted file mode 100644 index 89a99fdea..000000000 --- a/android/app/src/main/java/com/mattermost/rnbeta/SplitViewModule.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.mattermost.rnbeta - -import com.facebook.react.bridge.* -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import com.learnium.RNDeviceInfo.resolver.DeviceTypeResolver - -class SplitViewModule(private var reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - private var listenerCount = 0 - - companion object { - private var instance: SplitViewModule? = null - - fun getInstance(reactContext: ReactApplicationContext): SplitViewModule { - if (instance == null) { - instance = SplitViewModule(reactContext) - } else { - instance!!.reactContext = reactContext - } - - return instance!! - } - - fun getInstance(): SplitViewModule? { - return instance - } - } - - - override fun getName() = "SplitView" - - private fun sendEvent(params: WritableMap?) { - reactContext - .getJSModule(RCTDeviceEventEmitter::class.java) - .emit("SplitViewChanged", params) - } - - private fun getSplitViewResults(folded: Boolean) : WritableMap? { - if (currentActivity != null) { - val deviceResolver = DeviceTypeResolver(this.reactContext) - val map = Arguments.createMap() - var isSplitView = folded; - if (currentActivity?.isInMultiWindowMode == true) { - isSplitView = FoldableObserver.getInstance()?.isCompactView() == true - } - map.putBoolean("isSplitView", isSplitView) - map.putBoolean("isTablet", deviceResolver.isTablet) - return map - } - - return null - } - - fun setDeviceFolded() { - val map = getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true) - if (listenerCount > 0) { - sendEvent(map) - } - } - - @ReactMethod(isBlockingSynchronousMethod = true) - fun isRunningInSplitView(): WritableMap? { - return getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true) - } - - @ReactMethod - fun addListener(eventName: String) { - listenerCount += 1 - } - - @ReactMethod - fun removeListeners(count: Int) { - listenerCount -= count - } -} diff --git a/android/app/src/main/java/com/mattermost/share/ShareActivity.java b/android/app/src/main/java/com/mattermost/share/ShareActivity.java deleted file mode 100644 index 7f9d19cae..000000000 --- a/android/app/src/main/java/com/mattermost/share/ShareActivity.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.mattermost.share; - -import android.os.Bundle; - -import com.facebook.react.ReactActivity; -import com.mattermost.rnbeta.MainApplication; - -public class ShareActivity extends ReactActivity { - @Override - protected String getMainComponentName() { - return "MattermostShare"; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - MainApplication app = (MainApplication) this.getApplication(); - app.setSharedExtensionIsOpened(true); - } -} diff --git a/android/app/src/main/java/com/mattermost/share/ShareModule.java b/android/app/src/main/java/com/mattermost/share/ShareModule.java deleted file mode 100644 index e97f36639..000000000 --- a/android/app/src/main/java/com/mattermost/share/ShareModule.java +++ /dev/null @@ -1,259 +0,0 @@ -package com.mattermost.share; - -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.Arguments; -import com.mattermost.helpers.Credentials; -import com.mattermost.rnbeta.MainApplication; -import com.mattermost.helpers.RealPathUtil; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.File; -import java.util.ArrayList; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import okhttp3.Response; - -public class ShareModule extends ReactContextBaseJavaModule { - private final OkHttpClient client = new OkHttpClient(); - public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static ShareModule instance; - private final MainApplication mApplication; - private ReactApplicationContext mReactContext; - private File tempFolder; - - private ShareModule(ReactApplicationContext reactContext) { - super(reactContext); - mReactContext = reactContext; - mApplication = (MainApplication)reactContext.getApplicationContext(); - } - - public static ShareModule getInstance(ReactApplicationContext reactContext) { - if (instance == null) { - instance = new ShareModule(reactContext); - } else { - instance.mReactContext = reactContext; - } - - return instance; - } - - public static ShareModule getInstance() { - return instance; - } - - @NonNull - @Override - public String getName() { - return "MattermostShare"; - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public String getCurrentActivityName() { - Activity currentActivity = getCurrentActivity(); - if (currentActivity != null) { - String activityName = currentActivity.getComponentName().getClassName(); - String[] components = activityName.split("\\."); - return components[components.length - 1]; - } - - return ""; - } - - @ReactMethod - public void clear() { - Activity currentActivity = getCurrentActivity(); - if (currentActivity != null && this.getCurrentActivityName().equals("ShareActivity")) { - Intent intent = currentActivity.getIntent(); - intent.setAction(""); - intent.removeExtra(Intent.EXTRA_TEXT); - intent.removeExtra(Intent.EXTRA_STREAM); - } - } - - @Nullable - @Override - public Map getConstants() { - HashMap constants = new HashMap<>(1); - constants.put("cacheDirName", RealPathUtil.CACHE_DIR_NAME); - constants.put("isOpened", mApplication.getSharedExtensionIsOpened()); - return constants; - } - - @ReactMethod - public void close(ReadableMap data) { - this.clear(); - Activity currentActivity = getCurrentActivity(); - if (currentActivity == null || !this.getCurrentActivityName().equals("ShareActivity")) { - return; - } - - currentActivity.finishAndRemoveTask(); - if (data != null && data.hasKey("serverUrl")) { - ReadableArray files = data.getArray("files"); - String serverUrl = data.getString("serverUrl"); - final String token = Credentials.getCredentialsForServerSync(mReactContext, serverUrl); - JSONObject postData = buildPostObject(data); - - if (files != null && files.size() > 0) { - uploadFiles(serverUrl, token, files, postData); - } else { - try { - post(serverUrl, token, postData); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - mApplication.setSharedExtensionIsOpened(false); - RealPathUtil.deleteTempFiles(this.tempFolder); - } - - @ReactMethod - public void getSharedData(Promise promise) { - promise.resolve(processIntent()); - } - - public WritableArray processIntent() { - String type, action, extra; - WritableArray items = Arguments.createArray(); - Activity currentActivity = getCurrentActivity(); - - if (currentActivity != null) { - this.tempFolder = new File(currentActivity.getCacheDir(), RealPathUtil.CACHE_DIR_NAME); - Intent intent = currentActivity.getIntent(); - action = intent.getAction(); - type = intent.getType(); - extra = intent.getStringExtra(Intent.EXTRA_TEXT); - - if (Intent.ACTION_SEND.equals(action) && "text/plain".equals(type) && extra != null) { - items.pushMap(ShareUtils.getTextItem(extra)); - } else if (Intent.ACTION_SEND.equals(action)) { - if (extra != null) { - items.pushMap(ShareUtils.getTextItem(extra)); - } - Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); - if (uri != null) { - ReadableMap fileInfo = ShareUtils.getFileItem(currentActivity, uri); - if (fileInfo != null) { - items.pushMap(fileInfo); - } - } - } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - if (extra != null) { - items.pushMap(ShareUtils.getTextItem(extra)); - } - - ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); - for (Uri uri : uris) { - ReadableMap fileInfo = ShareUtils.getFileItem(currentActivity, uri); - if (fileInfo != null) { - items.pushMap(fileInfo); - } - } - } - } - - return items; - } - - private JSONObject buildPostObject(ReadableMap data) { - JSONObject json = new JSONObject(); - try { - json.put("user_id", data.getString("userId")); - if (data.hasKey("channelId")) { - json.put("channel_id", data.getString("channelId")); - } - if (data.hasKey("message")) { - json.put("message", data.getString("message")); - } - } catch (JSONException e) { - e.printStackTrace(); - } - return json; - } - - private void post(String serverUrl, String token, JSONObject postData) throws IOException { - RequestBody body = RequestBody.create(postData.toString(), JSON); - Request request = new Request.Builder() - .header("Authorization", "BEARER " + token) - .url(serverUrl + "/api/v4/posts") - .post(body) - .build(); - client.newCall(request).execute(); - } - - private void uploadFiles(String serverUrl, String token, ReadableArray files, JSONObject postData) { - try { - MultipartBody.Builder builder = new MultipartBody.Builder() - .setType(MultipartBody.FORM); - - for(int i = 0 ; i < files.size() ; i++) { - ReadableMap file = files.getMap(i); - String mime = file.getString("type"); - String fullPath = file.getString("value"); - if (fullPath != null) { - String filePath = fullPath.replaceFirst("file://", ""); - File fileInfo = new File(filePath); - if (fileInfo.exists() && mime != null) { - final MediaType MEDIA_TYPE = MediaType.parse(mime); - builder.addFormDataPart("files", file.getString("filename"), RequestBody.create(fileInfo, MEDIA_TYPE)); - } - } - } - - builder.addFormDataPart("channel_id", postData.getString("channel_id")); - RequestBody body = builder.build(); - Request request = new Request.Builder() - .header("Authorization", "BEARER " + token) - .url(serverUrl + "/api/v4/files") - .post(body) - .build(); - - try (Response response = client.newCall(request).execute()) { - if (response.isSuccessful()) { - String responseData = Objects.requireNonNull(response.body()).string(); - JSONObject responseJson = new JSONObject(responseData); - JSONArray fileInfoArray = responseJson.getJSONArray("file_infos"); - JSONArray file_ids = new JSONArray(); - for(int i = 0 ; i < fileInfoArray.length() ; i++) { - JSONObject fileInfo = fileInfoArray.getJSONObject(i); - file_ids.put(fileInfo.getString("id")); - } - postData.put("file_ids", file_ids); - post(serverUrl, token, postData); - } - } catch (IOException e) { - e.printStackTrace(); - } - - } catch (JSONException e) { - e.printStackTrace(); - } - } -} diff --git a/android/app/src/main/java/com/mattermost/share/ShareUtils.java b/android/app/src/main/java/com/mattermost/share/ShareUtils.java deleted file mode 100644 index 143326edf..000000000 --- a/android/app/src/main/java/com/mattermost/share/ShareUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.mattermost.share; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.webkit.URLUtil; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.mattermost.helpers.RealPathUtil; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.UUID; - -public class ShareUtils { - public static ReadableMap getTextItem(String text) { - WritableMap map = Arguments.createMap(); - map.putString("value", text); - map.putString("type", ""); - map.putBoolean("isString", true); - return map; - } - - public static ReadableMap getFileItem(Activity activity, Uri uri) { - WritableMap map = Arguments.createMap(); - String filePath = RealPathUtil.getRealPathFromURI(activity, uri); - if (filePath == null) { - return null; - } - - File file = new File(filePath); - String type = RealPathUtil.getMimeTypeFromUri(activity, uri); - if (type != null) { - if (type.startsWith("image/")) { - BitmapFactory.Options bitMapOption = ShareUtils.getImageDimensions(filePath); - map.putInt("height", bitMapOption.outHeight); - map.putInt("width", bitMapOption.outWidth); - - } else if (type.startsWith("video/")) { - File cacheDir = new File(activity.getCacheDir(), RealPathUtil.CACHE_DIR_NAME); - addVideoThumbnailToMap(cacheDir, activity.getApplicationContext(), map, "file://" + filePath); - } - } else { - type = "application/octet-stream"; - } - - map.putString("value", "file://" + filePath); - map.putDouble("size", (double) file.length()); - map.putString("filename", file.getName()); - map.putString("type", type); - map.putString("extension", RealPathUtil.getExtension(filePath).replaceFirst(".", "")); - map.putBoolean("isString", false); - return map; - } - - public static BitmapFactory.Options getImageDimensions(String filePath) { - BitmapFactory.Options bitMapOption = new BitmapFactory.Options(); - bitMapOption.inJustDecodeBounds=true; - BitmapFactory.decodeFile(filePath, bitMapOption); - return bitMapOption; - } - - private static void addVideoThumbnailToMap(File cacheDir, Context context, WritableMap map, String filePath) { - String fileName = ("thumb-" + UUID.randomUUID().toString()) + ".png"; - OutputStream fOut = null; - - try { - File file = new File(cacheDir, fileName); - Bitmap image = getBitmapAtTime(context, filePath, 1); - if (file.createNewFile()) { - fOut = new FileOutputStream(file); - image.compress(Bitmap.CompressFormat.PNG, 100, fOut); - fOut.flush(); - fOut.close(); - - map.putString("videoThumb", "file://" + file.getAbsolutePath()); - map.putInt("width", image.getWidth()); - map.putInt("height", image.getHeight()); - } - } catch (Exception ignored) { - } - } - - private static Bitmap getBitmapAtTime(Context context, String filePath, int time) { - try { - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - if (URLUtil.isFileUrl(filePath)) { - String decodedPath; - try { - decodedPath = URLDecoder.decode(filePath, "UTF-8"); - } catch (UnsupportedEncodingException e) { - decodedPath = filePath; - } - - retriever.setDataSource(decodedPath.replace("file://", "")); - } else if (filePath.contains("content://")) { - retriever.setDataSource(context, Uri.parse(filePath)); - } - - Bitmap image = retriever.getFrameAtTime(time * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); - retriever.release(); - return image; - } catch (Exception e) { - throw new IllegalStateException("File doesn't exist or not supported"); - } - } -} diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml index ab58c0f44..060f7ee17 100644 --- a/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -14,7 +14,8 @@ android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material" android:insetRight="@dimen/abc_edit_text_inset_horizontal_material" android:insetTop="@dimen/abc_edit_text_inset_top_material" - android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> + android:insetBottom="@dimen/abc_edit_text_inset_bottom_material" + >