├── android ├── app │ ├── src │ │ ├── main │ │ │ ├── assets │ │ │ │ └── .gitignore │ │ │ ├── res │ │ │ │ ├── .gitignore │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ ├── discordrn │ │ │ │ │ ├── models │ │ │ │ │ │ ├── Row.java │ │ │ │ │ │ ├── Message.java │ │ │ │ │ │ └── MessageContent.kt │ │ │ │ │ ├── modules │ │ │ │ │ │ ├── DCDIconManager.java │ │ │ │ │ │ ├── RNCPushNotificationIOS.java │ │ │ │ │ │ ├── DynamicLinkManager.java │ │ │ │ │ │ ├── DCDColor.java │ │ │ │ │ │ ├── UserSearchWorkerManager.java │ │ │ │ │ │ ├── DCDSKAdNetworkManager.java │ │ │ │ │ │ ├── ProximitySensorManager.java │ │ │ │ │ │ ├── DCDTheme.java │ │ │ │ │ │ ├── DCDNotificationCategoryUtils.java │ │ │ │ │ │ ├── KeyCommandsModule.java │ │ │ │ │ │ ├── BrowserManager.java │ │ │ │ │ │ ├── LocalizationManager.java │ │ │ │ │ │ ├── OnePasswordManager.java │ │ │ │ │ │ ├── ScreenshotHelper.java │ │ │ │ │ │ ├── DCDSafeAreaManager.java │ │ │ │ │ │ ├── MediaManager.java │ │ │ │ │ │ ├── DCDCrashlyticsCrashReports.java │ │ │ │ │ │ ├── CaptchaManager.java │ │ │ │ │ │ ├── DCDDeviceManager.java │ │ │ │ │ │ ├── InfoDictionaryManager.java │ │ │ │ │ │ ├── IntentsHandler.java │ │ │ │ │ │ ├── KeyboardManager.java │ │ │ │ │ │ ├── TTIManager.java │ │ │ │ │ │ ├── ExpoRandom.java │ │ │ │ │ │ ├── BundleUpdaterManager.java │ │ │ │ │ │ ├── ActionSheetAndroid.java │ │ │ │ │ │ ├── NativePermissionManager.java │ │ │ │ │ │ ├── MMKVManager.java │ │ │ │ │ │ ├── DCDFastConnectManager.java │ │ │ │ │ │ ├── PushNotificationAndroid.java │ │ │ │ │ │ ├── ImageManager.java │ │ │ │ │ │ ├── DCDChatInputManager.java │ │ │ │ │ │ ├── VoiceEngine.java │ │ │ │ │ │ ├── TimersModule.java │ │ │ │ │ │ ├── DCDChatManager.java │ │ │ │ │ │ ├── DCDCompressionManager.java │ │ │ │ │ │ └── RNSentryModule.java │ │ │ │ │ ├── views │ │ │ │ │ │ ├── DCDChat.java │ │ │ │ │ │ ├── ViewStub.java │ │ │ │ │ │ ├── NativeLottieNode.java │ │ │ │ │ │ ├── DCDChatInput.java │ │ │ │ │ │ ├── DCDSegmentedControl.java │ │ │ │ │ │ └── DCDChatList.kt │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ ├── MainAppPackage.java │ │ │ │ │ └── MainApplication.java │ │ │ │ │ └── facebook │ │ │ │ │ └── react │ │ │ │ │ └── CustomReactInstanceManager.java │ │ │ └── AndroidManifest.xml │ │ └── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── discordrn │ │ │ └── ReactNativeFlipper.java │ ├── debug.keystore │ └── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle ├── gradle.properties ├── build.gradle ├── gradlew.bat └── gradlew ├── app.json ├── README.md ├── preload ├── rollup.config.js ├── package.json ├── src │ ├── index.js │ └── driver.js └── pnpm-lock.yaml ├── .gitignore ├── .github └── workflows │ └── main.yml └── package.json /android/app/src/main/assets/.gitignore: -------------------------------------------------------------------------------- 1 | discord.android.bundle -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Discord RN", 3 | "displayName": "Discord RN" 4 | } -------------------------------------------------------------------------------- /android/app/src/main/res/.gitignore: -------------------------------------------------------------------------------- 1 | !values/colors.xml 2 | !values/styles.xml 3 | * -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aliucord/DiscordRN/HEAD/android/app/debug.keystore -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aliucord/DiscordRN/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Discord released React Native version for Android on alpha update channel. This repo is obsolote now. 2 | 3 | # DiscordRN 4 | Android version of Discord React Native reconstructed from https://discord.com/android/90.0/manifest.json 5 | 6 | ## Running 7 | 1. `pnpm i` 8 | 2. `pnpm android` 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /preload/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "rollup"; 2 | import esbuild from "rollup-plugin-esbuild"; 3 | import { string } from "rollup-plugin-string"; 4 | 5 | export default defineConfig({ 6 | input: "src/index.js", 7 | output: [ 8 | { file: "../android/app/src/main/assets/preload.js", format: "cjs" } 9 | ], 10 | plugins: [ 11 | string({ 12 | include: "**/*.txt" 13 | }), 14 | esbuild({ target: "es2015" }) 15 | ] 16 | }); 17 | -------------------------------------------------------------------------------- /preload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preload", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rollup -c --configPlugin esbuild" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "esbuild": "^0.14.10", 13 | "rollup": "^2.63.0", 14 | "rollup-plugin-esbuild": "^4.8.2", 15 | "rollup-plugin-string": "^3.0.0" 16 | }, 17 | "dependencies": { 18 | "react-devtools-core": "^4.22.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/models/Row.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.models; 2 | 3 | public final class Row { 4 | public String addNewReactionAccessibilityLabel; 5 | public boolean animated; 6 | public boolean canAddNewReactions; 7 | public int changeType; 8 | public boolean highlightJumpedOnceOnly; 9 | public int index; 10 | public boolean jumped; 11 | public Message message; 12 | public boolean scrollTo; 13 | public boolean separatorBefore; 14 | public int type; 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDIconManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.*; 6 | 7 | public class DCDIconManager extends ReactContextBaseJavaModule { 8 | public DCDIconManager(ReactApplicationContext context) { 9 | super(context); 10 | } 11 | 12 | @NonNull 13 | @Override 14 | public String getName() { 15 | return "DCDIconManager"; 16 | } 17 | 18 | @ReactMethod 19 | public void setHosts(String cdn, String api) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/RNCPushNotificationIOS.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | 8 | public class RNCPushNotificationIOS extends ReactContextBaseJavaModule { 9 | public RNCPushNotificationIOS(ReactApplicationContext context) { 10 | super(context); 11 | } 12 | 13 | @NonNull 14 | @Override 15 | public String getName() { 16 | return "RNCPushNotificationIOS"; 17 | } 18 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DynamicLinkManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.*; 6 | 7 | public class DynamicLinkManager extends ReactContextBaseJavaModule { 8 | public DynamicLinkManager(ReactApplicationContext context) { 9 | super(context); 10 | } 11 | 12 | @NonNull 13 | @Override 14 | public String getName() { 15 | return "DynamicLinkManager"; 16 | } 17 | 18 | @ReactMethod 19 | public void getInitialURL(Promise promise) { 20 | promise.resolve(null); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDColor.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | public class DCDColor extends ReactContextBaseJavaModule { 10 | public DCDColor(ReactApplicationContext context) { 11 | super(context); 12 | } 13 | 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "DCDColor"; 18 | } 19 | 20 | @ReactMethod 21 | public void setColors(String colors, String themeColorMap) { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/UserSearchWorkerManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | public class UserSearchWorkerManager extends ReactContextBaseJavaModule { 10 | public UserSearchWorkerManager(ReactApplicationContext context) { 11 | super(context); 12 | } 13 | 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "UserSearchWorkerManager"; 18 | } 19 | 20 | @ReactMethod 21 | public void onmessage(String data) { 22 | } 23 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDSKAdNetworkManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | public class DCDSKAdNetworkManager extends ReactContextBaseJavaModule { 10 | public DCDSKAdNetworkManager(ReactApplicationContext context) { 11 | super(context); 12 | } 13 | 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "DCDSKAdNetworkManager"; 18 | } 19 | 20 | @ReactMethod 21 | public void updateConversionValue(int value) { 22 | } 23 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/ProximitySensorManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | public class ProximitySensorManager extends ReactContextBaseJavaModule { 10 | public ProximitySensorManager(ReactApplicationContext context) { 11 | super(context); 12 | } 13 | 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "ProximitySensorManager"; 18 | } 19 | 20 | @ReactMethod 21 | public void setProximityMonitoringEnabled(boolean enabled) { 22 | } 23 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/DCDChat.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.views; 2 | 3 | import android.widget.LinearLayout; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.uimanager.ThemedReactContext; 8 | import com.facebook.react.uimanager.ViewGroupManager; 9 | 10 | public class DCDChat extends ViewGroupManager { 11 | @NonNull 12 | @Override 13 | public String getName() { 14 | return "DCDChat"; 15 | } 16 | 17 | @NonNull 18 | @Override 19 | protected LinearLayout createViewInstance(@NonNull ThemedReactContext context) { 20 | LinearLayout layout = new LinearLayout(context); 21 | layout.setOrientation(LinearLayout.VERTICAL); 22 | return layout; 23 | } 24 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'DiscordRN' 2 | include ':@react-native-async-storage_async-storage' 3 | project(':@react-native-async-storage_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/.pnpm/@react-native-async-storage+async-storage@1.15.14_react-native@0.64.3/node_modules/@react-native-async-storage/async-storage/android') 4 | include ':react-native-orientation' 5 | project(':react-native-orientation').projectDir = new File(rootProject.projectDir, '../node_modules/.pnpm/react-native-orientation@3.1.3_react-native@0.64.3/node_modules/react-native-orientation/android') 6 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 7 | include ':app' 8 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDTheme.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | 11 | public class DCDTheme extends ReactContextBaseJavaModule { 12 | public DCDTheme(ReactApplicationContext context) { 13 | super(context); 14 | } 15 | 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "DCDTheme"; 20 | } 21 | 22 | @ReactMethod 23 | public void updateTheme(String theme) { 24 | Log.i("DCDTheme", "updateTheme: " + theme); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/ViewStub.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.views; 2 | 3 | import android.widget.FrameLayout; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.uimanager.ThemedReactContext; 8 | import com.facebook.react.uimanager.ViewGroupManager; 9 | 10 | public class ViewStub extends ViewGroupManager { 11 | private final String name; 12 | 13 | public ViewStub(String name) { 14 | this.name = name; 15 | } 16 | 17 | @NonNull 18 | @Override 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public FrameLayout createViewInstance(@NonNull ThemedReactContext context) { 26 | return new FrameLayout(context); 27 | } 28 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDNotificationCategoryUtils.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | import com.facebook.react.bridge.ReadableMap; 9 | 10 | public class DCDNotificationCategoryUtils extends ReactContextBaseJavaModule { 11 | public DCDNotificationCategoryUtils(ReactApplicationContext context) { 12 | super(context); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getName() { 18 | return "DCDNotificationCategoryUtils"; 19 | } 20 | 21 | @ReactMethod 22 | public void registerNotificationCategories(ReadableMap idk) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/KeyCommandsModule.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.*; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class KeyCommandsModule extends ReactContextBaseJavaModule { 11 | public KeyCommandsModule(ReactApplicationContext context) { 12 | super(context); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getName() { 18 | return "KeyCommandsView"; 19 | } 20 | 21 | @Override 22 | public Map getConstants() { 23 | final Map constants = new HashMap<>(); 24 | final WritableMap modifiers = Arguments.createMap(); 25 | constants.put("keyModifierCommand", modifiers); 26 | return constants; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.discordrn; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | 7 | import com.facebook.react.ReactActivity; 8 | 9 | public class MainActivity extends ReactActivity { 10 | @Override 11 | public void onConfigurationChanged(Configuration newConfig) { 12 | super.onConfigurationChanged(newConfig); 13 | Intent intent = new Intent("onConfigurationChanged"); 14 | intent.putExtra("newConfig", newConfig); 15 | this.sendBroadcast(intent); 16 | } 17 | 18 | @Override 19 | protected String getMainComponentName() { 20 | return "Discord"; 21 | } 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(null); // https://github.com/software-mansion/react-native-screens#android 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/BrowserManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class BrowserManager extends ReactContextBaseJavaModule { 12 | public BrowserManager(ReactApplicationContext context) { 13 | super(context); 14 | } 15 | 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "BrowserManager"; 20 | } 21 | 22 | @Override 23 | public Map getConstants() { 24 | final Map constants = new HashMap<>(); 25 | constants.put("selectedBrowser", "SAFARI"); 26 | constants.put("isChromeInstalled", false); 27 | return constants; 28 | } 29 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/LocalizationManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | 8 | import java.util.HashMap; 9 | import java.util.Locale; 10 | import java.util.Map; 11 | 12 | public class LocalizationManager extends ReactContextBaseJavaModule { 13 | public LocalizationManager(ReactApplicationContext context) { 14 | super(context); 15 | } 16 | 17 | @NonNull 18 | @Override 19 | public String getName() { 20 | return "LocalizationManager"; 21 | } 22 | 23 | @Override 24 | public Map getConstants() { 25 | final Map constants = new HashMap<>(); 26 | constants.put("Language", Locale.getDefault().toLanguageTag()); 27 | return constants; 28 | } 29 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/OnePasswordManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | 10 | public class OnePasswordManager extends ReactContextBaseJavaModule { 11 | public OnePasswordManager(ReactApplicationContext context) { 12 | super(context); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getName() { 18 | return "OnePasswordManager"; 19 | } 20 | 21 | @ReactMethod 22 | public void isSupported(Promise promise) { 23 | promise.resolve(false); 24 | } 25 | 26 | @ReactMethod 27 | public void findLogin(String a, String b, Promise promise) { 28 | promise.resolve(null); 29 | } 30 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/ScreenshotHelper.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | 9 | public class ScreenshotHelper extends ReactContextBaseJavaModule { 10 | public ScreenshotHelper(ReactApplicationContext context) { 11 | super(context); 12 | } 13 | 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "ScreenshotHelper"; 18 | } 19 | 20 | @ReactMethod 21 | public void appStateChanged(Boolean isActive) { 22 | } 23 | 24 | @ReactMethod 25 | public void addListener(String name) { 26 | } 27 | 28 | @ReactMethod 29 | public void removeListener(String name) { 30 | } 31 | 32 | @ReactMethod 33 | public void removeEventListener(String name) { 34 | } 35 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/NativeLottieNode.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.views; 2 | 3 | 4 | import android.widget.FrameLayout; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.facebook.react.common.MapBuilder; 10 | import com.facebook.react.uimanager.ThemedReactContext; 11 | import com.facebook.react.uimanager.ViewGroupManager; 12 | 13 | import java.util.Map; 14 | 15 | public class NativeLottieNode extends ViewGroupManager { 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "NativeLottieNode"; 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public FrameLayout createViewInstance(@NonNull ThemedReactContext context) { 25 | return new FrameLayout(context); 26 | } 27 | 28 | @Nullable 29 | @Override 30 | public Map getCommandsMap() { 31 | return MapBuilder.of( 32 | "create", 0, 33 | "setup", 1 34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDSafeAreaManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class DCDSafeAreaManager extends ReactContextBaseJavaModule { 13 | public DCDSafeAreaManager(ReactApplicationContext context) { 14 | super(context); 15 | } 16 | 17 | @NonNull 18 | @Override 19 | public String getName() { 20 | return "DCDSafeAreaManager"; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public Map getConstants() { 26 | final Map constants = new HashMap<>(); 27 | constants.put("top", 0); 28 | constants.put("bottom", 0); 29 | constants.put("left", 0); 30 | constants.put("right", 0); 31 | return constants; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/models/Message.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.models; 2 | 3 | import java.util.List; 4 | 5 | public final class Message { 6 | // attachments 7 | public String authorId; 8 | public String avatarURL; 9 | public String channelId; 10 | public int colorString; 11 | public double constrainedWidth; 12 | public List content; 13 | public String edited; 14 | public int editedColor; 15 | // embeds 16 | public int feedbackColor; 17 | public int flags; 18 | public boolean gifAutoPlay; 19 | public int highlightColor; 20 | public String id; 21 | // invite 22 | public boolean isEditing; 23 | public boolean mentioned; 24 | // nonce 25 | // public List<> reactions; 26 | public String state; 27 | // stickers 28 | public String tagText; 29 | public boolean tagVerified; 30 | public String timestamp; 31 | // either a boolean or an int 32 | public Object timestampColor; 33 | public int type; 34 | public String username; 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/MediaManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class MediaManager extends ReactContextBaseJavaModule { 14 | public MediaManager(ReactApplicationContext context) { 15 | super(context); 16 | } 17 | 18 | @NonNull 19 | @Override 20 | public String getName() { 21 | return "MediaManager"; 22 | } 23 | 24 | @ReactMethod 25 | public void downloadMediaAsset(String a, String imageType, Promise promise) { 26 | promise.resolve(null); 27 | } 28 | 29 | @Override 30 | public Map getConstants() { 31 | final Map constants = new HashMap<>(); 32 | return constants; 33 | } 34 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDCrashlyticsCrashReports.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Callback; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | import com.facebook.react.bridge.ReadableMap; 10 | 11 | public class DCDCrashlyticsCrashReports extends ReactContextBaseJavaModule { 12 | public DCDCrashlyticsCrashReports(ReactApplicationContext context) { 13 | super(context); 14 | } 15 | 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "CrashlyticsManager"; 20 | } 21 | 22 | @ReactMethod 23 | public void initializeManager() { 24 | } 25 | 26 | @ReactMethod 27 | public void getDidCrashDuringPreviousExecution(Callback callback) { 28 | callback.invoke(false); 29 | } 30 | 31 | @ReactMethod 32 | public void setUser(ReadableMap user) { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/CaptchaManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | 10 | public class CaptchaManager extends ReactContextBaseJavaModule { 11 | public CaptchaManager(ReactApplicationContext context) { 12 | super(context); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getName() { 18 | return "CaptchaManager"; 19 | } 20 | 21 | @ReactMethod 22 | public void showCaptcha(Promise promise) { 23 | promise.resolve(null); 24 | } 25 | 26 | @ReactMethod 27 | public void closeCaptcha(Promise promise) { 28 | promise.resolve(null); 29 | } 30 | 31 | @ReactMethod 32 | public void addListener(String eventName) { 33 | } 34 | 35 | @ReactMethod 36 | public void removeListeners(double count) { 37 | } 38 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDDeviceManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.os.Build; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class DCDDeviceManager extends ReactContextBaseJavaModule { 14 | public DCDDeviceManager(ReactApplicationContext context) { 15 | super(context); 16 | } 17 | 18 | @NonNull 19 | @Override 20 | public String getName() { 21 | return "DCDDeviceManager"; 22 | } 23 | 24 | @Override 25 | public Map getConstants() { 26 | final Map constants = new HashMap<>(); 27 | constants.put("name", Build.MODEL); 28 | constants.put("systemVersion", Build.VERSION.RELEASE); 29 | constants.put("device", Build.MODEL + ", " + Build.PRODUCT); 30 | constants.put("model", "Phone"); 31 | return constants; 32 | } 33 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | yarn-error.log 37 | .pnpm-debug.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | 62 | android/app/src/main/assets/preload.js 63 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/DCDChatInput.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.views; 2 | 3 | import android.text.InputType; 4 | import android.widget.EditText; 5 | import android.widget.LinearLayout; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.appcompat.app.ActionBar; 9 | 10 | import com.facebook.react.uimanager.ThemedReactContext; 11 | import com.facebook.react.uimanager.ViewGroupManager; 12 | 13 | public class DCDChatInput extends ViewGroupManager { 14 | @NonNull 15 | @Override 16 | public String getName() { 17 | return "DCDChatInput"; 18 | } 19 | 20 | @NonNull 21 | @Override 22 | protected LinearLayout createViewInstance(@NonNull ThemedReactContext context) { 23 | LinearLayout layout = new LinearLayout(context); 24 | layout.setOrientation(LinearLayout.HORIZONTAL); 25 | EditText editor = new EditText(context); 26 | editor.setInputType(InputType.TYPE_CLASS_TEXT); 27 | layout.addView(editor, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 28 | return layout; 29 | } 30 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/InfoDictionaryManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class InfoDictionaryManager extends ReactContextBaseJavaModule { 12 | public InfoDictionaryManager(ReactApplicationContext context) { 13 | super(context); 14 | } 15 | 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "InfoDictionaryManager"; 20 | } 21 | 22 | @Override 23 | public Map getConstants() { 24 | final Map constants = new HashMap<>(); 25 | constants.put("SentryDsn", ""); 26 | constants.put("UserSettings", "{\"systemTheme\": \"DARK\", \"useSystemTheme\": true}"); 27 | constants.put("Manifest", "[]"); 28 | 29 | constants.put("Version", "90.0"); 30 | constants.put("Build", "90000"); 31 | constants.put("ReleaseChannel", "stable"); 32 | return constants; 33 | } 34 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/IntentsHandler.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.Promise; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.facebook.react.bridge.WritableMap; 11 | 12 | public class IntentsHandler extends ReactContextBaseJavaModule { 13 | public IntentsHandler(ReactApplicationContext context) { 14 | super(context); 15 | } 16 | 17 | @NonNull 18 | @Override 19 | public String getName() { 20 | return "IntentsHandler"; 21 | } 22 | 23 | @ReactMethod 24 | public void getLaunchSendMessageIntent(Promise p) { 25 | final WritableMap intent = Arguments.createMap(); 26 | intent.putString("channelId", null); 27 | intent.putString("guildId", null); 28 | p.resolve(intent); 29 | } 30 | 31 | @ReactMethod 32 | public void getConversationSuggestionsEnabled(Promise p) { 33 | p.resolve(false); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Cache pnpm modules 12 | uses: actions/cache@v2 13 | with: 14 | path: ~/.pnpm-store 15 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 16 | restore-keys: | 17 | ${{ runner.os }}- 18 | 19 | - name: Cache discord assets 20 | uses: actions/cache@v2 21 | with: 22 | path: | 23 | android/app/src/main/assets/discord.android.bundle 24 | android/app/src/main/res 25 | !android/app/src/main/res/values/colors.xml 26 | !android/app/src/main/res/values/styles.xml 27 | key: ${{ runner.os }}-assets 28 | 29 | - uses: pnpm/action-setup@v2 30 | with: 31 | version: latest 32 | 33 | - run: pnpm i --unsafe-perm && pnpm preload && cd android && chmod +x gradlew && ./gradlew assembleDebug --stacktrace --no-daemon 34 | 35 | - uses: actions/upload-artifact@v2 36 | with: 37 | name: apk 38 | path: android/app/build/outputs/apk/debug/app-debug.apk 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/KeyboardManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.View; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 12 | import com.facebook.react.bridge.ReactMethod; 13 | 14 | import java.util.Objects; 15 | 16 | public class KeyboardManager extends ReactContextBaseJavaModule { 17 | public KeyboardManager(ReactApplicationContext context) { 18 | super(context); 19 | } 20 | 21 | @NonNull 22 | @Override 23 | public String getName() { 24 | return "KeyboardManager"; 25 | } 26 | 27 | @ReactMethod 28 | public void dismissGlobalKeyboard() { 29 | Activity activity = Objects.requireNonNull(getCurrentActivity()); 30 | 31 | View view = activity.getCurrentFocus(); 32 | if (view == null) { 33 | view = new View(activity); 34 | } 35 | 36 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 37 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 38 | } 39 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.75.1 29 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/TTIManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.Promise; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.facebook.react.bridge.WritableMap; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class TTIManager extends ReactContextBaseJavaModule { 16 | private final long timestamp = System.currentTimeMillis(); 17 | 18 | public TTIManager(ReactApplicationContext context) { 19 | super(context); 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public String getName() { 25 | return "TTIManager"; 26 | } 27 | 28 | @ReactMethod 29 | public void getJSBundleTimestamps(Promise promise) { 30 | WritableMap data = Arguments.createMap(); 31 | data.putInt("JSBundleLoadedTimestamp", 0); 32 | data.putInt("JSBundleParsedTimestamp", 0); 33 | promise.resolve(data); 34 | } 35 | 36 | @Override 37 | public Map getConstants() { 38 | final Map constants = new HashMap<>(); 39 | constants.put("AppOpenedTimestamp", timestamp); 40 | return constants; 41 | } 42 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/ExpoRandom.java: -------------------------------------------------------------------------------- 1 | // manually converted to java from https://github.com/expo/expo/blob/master/packages/expo-random/android/src/main/java/expo/modules/random/RandomModule.kt 2 | package com.discordrn.modules; 3 | 4 | import android.util.Base64; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.facebook.react.bridge.Promise; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 11 | import com.facebook.react.bridge.ReactMethod; 12 | 13 | import java.security.SecureRandom; 14 | 15 | public class ExpoRandom extends ReactContextBaseJavaModule { 16 | private final SecureRandom secureRandom = new SecureRandom(); 17 | 18 | public ExpoRandom(ReactApplicationContext context) { 19 | super(context); 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public String getName() { 25 | return "ExpoRandom"; 26 | } 27 | 28 | @ReactMethod 29 | public void getRandomBase64StringAsync(int randomByteCount, Promise promise) { 30 | promise.resolve(getRandomBase64String(randomByteCount)); 31 | } 32 | 33 | @ReactMethod(isBlockingSynchronousMethod = true) 34 | public String getRandomBase64String(int randomByteCount) { 35 | byte[] output = new byte[randomByteCount]; 36 | secureRandom.nextBytes(output); 37 | return Base64.encodeToString(output, Base64.NO_WRAP); 38 | } 39 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/BundleUpdaterManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.facebook.react.bridge.Promise; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 12 | import com.facebook.react.bridge.ReactMethod; 13 | 14 | public class BundleUpdaterManager extends ReactContextBaseJavaModule { 15 | public BundleUpdaterManager(ReactApplicationContext context) { 16 | super(context); 17 | } 18 | 19 | @NonNull 20 | @Override 21 | public String getName() { 22 | return "BundleUpdaterManager"; 23 | } 24 | 25 | @ReactMethod 26 | public void getInitialBundleDownloaded(Promise promise) { 27 | promise.resolve(null); 28 | } 29 | 30 | @ReactMethod 31 | public void reload() { 32 | ReactApplicationContext context = getReactApplicationContext(); 33 | PackageManager packageManager = context.getPackageManager(); 34 | Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName()); 35 | ComponentName componentName = intent.getComponent(); 36 | Intent mainIntent = Intent.makeRestartActivityTask(componentName); 37 | context.startActivity(mainIntent); 38 | Runtime.getRuntime().exit(0); 39 | } 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discordrn", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "preinstall": "pnpm --prefix preload i", 7 | "preload": "pnpm --prefix preload build", 8 | "android": "pnpm run preload && react-native run-android --no-jetifier --no-packager", 9 | "android-release": "pnpm run android -- --variant=release", 10 | "start": "react-native start" 11 | }, 12 | "dependencies": { 13 | "@react-native-async-storage/async-storage": "^1.15.14", 14 | "@react-native-clipboard/clipboard": "^1.8.5", 15 | "@react-native-community/cli-platform-android": "^6.3.0", 16 | "@react-native-community/netinfo": "Aliucord/react-native-netinfo#5.1.0", 17 | "@react-native-community/slider": "^4.1.12", 18 | "react": "17.0.1", 19 | "react-native": "0.64.3", 20 | "react-native-adjust": "^4.29.5", 21 | "react-native-gesture-handler": "^1.10.3", 22 | "react-native-linear-gradient": "^2.5.6", 23 | "react-native-orientation": "The1Central/react-native-orientation#feature/upgrade-android-version-fix-compile-obsolete", 24 | "react-native-reanimated": "2.2.4", 25 | "react-native-safe-area-context": "^3.3.2", 26 | "react-native-screens": "^3.10.1", 27 | "react-native-svg": "^12.1.1", 28 | "react-native-video": "^5.2.0", 29 | "react-native-webview": "^11.15.1" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.16.7", 33 | "@babel/runtime": "^7.16.7", 34 | "eslint": "7.14.0", 35 | "hermes-engine": "0.7.2", 36 | "metro-react-native-babel-preset": "^0.64.0", 37 | "react-test-renderer": "17.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/ActionSheetAndroid.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | import com.facebook.react.bridge.ReadableArray; 10 | import com.khoiron.actionsheets.ActionSheet; 11 | 12 | import java.util.ArrayList; 13 | 14 | public class ActionSheetAndroid extends ReactContextBaseJavaModule { 15 | public ActionSheetAndroid(ReactApplicationContext context) { 16 | super(context); 17 | } 18 | 19 | @NonNull 20 | @Override 21 | public String getName() { 22 | return "ActionSheetAndroid"; 23 | } 24 | 25 | @ReactMethod 26 | public void options(String title, String message, String cancel, String primaryBold, ReadableArray options, String primary, String backgroundPrimary, int f, String textDanger, String textNormal, String backgroundSecondary, Promise promise) { 27 | ArrayList data = new ArrayList<>(); 28 | for (int i = 0; i < options.size(); i++) { 29 | data.add(options.getString(i)); 30 | } 31 | 32 | ActionSheet actionSheet = new ActionSheet(getCurrentActivity(), data) 33 | .setCancelTitle(cancel); 34 | 35 | if (title == null) { 36 | actionSheet.hideTitle(); 37 | } else { 38 | actionSheet.setTitle(title); 39 | } 40 | 41 | actionSheet.create((option, position) -> promise.resolve(position)); 42 | } 43 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/models/MessageContent.kt: -------------------------------------------------------------------------------- 1 | package com.discordrn.models 2 | 3 | sealed class MessageContent { 4 | data class Text(val content: String) : MessageContent() 5 | data class Link(val target: String, val content: List) : MessageContent() 6 | data class CodeBlock(val lang: String, val inQuote: Boolean, val content: String) : MessageContent() 7 | data class Mention(val userId: String, val channelId: String, val roleId: String, val color: Int, val colorString: String, val content: List) : MessageContent() 8 | data class Channel(val channelId: String, val guildId: String, val content: List) : MessageContent() 9 | data class Emoji(val content: String, val surrogate: String) : MessageContent() 10 | data class CustomEmoji(val id: String, val alt: String, val src: String, val frozenSrc: String, val jumboable: Boolean) : MessageContent() 11 | data class Strong(val content: List) : MessageContent() 12 | data class Emphasis(val content: List) : MessageContent() 13 | data class Stroke(val content: List) : MessageContent() 14 | data class Underlined(val content: List) : MessageContent() 15 | data class InlineCode(val content: String) : MessageContent() 16 | data class Spoiler(val channelId: String) : MessageContent() 17 | data class Timestamp(val timestamp: String, val full: String, val formatted: String) : MessageContent() 18 | data class BlockQuote(val content: List) : MessageContent() 19 | data class Paragraph(val content: List) : MessageContent() 20 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/NativePermissionManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | 10 | public class NativePermissionManager extends ReactContextBaseJavaModule { 11 | public NativePermissionManager(ReactApplicationContext context) { 12 | super(context); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getName() { 18 | return "NativePermissionManager"; 19 | } 20 | 21 | @ReactMethod 22 | public boolean requestCameraAuthorization() { 23 | return true; 24 | } 25 | 26 | @ReactMethod 27 | public boolean requestMicrophoneAuthorization() { 28 | return true; 29 | } 30 | 31 | @ReactMethod 32 | public boolean requestPhotoAuthorization() { 33 | return true; 34 | } 35 | 36 | @ReactMethod 37 | public boolean requestContactsAuthorization() { 38 | return false; 39 | } 40 | 41 | @ReactMethod 42 | public boolean requestPermissionLookup() { 43 | return true; 44 | } 45 | 46 | @ReactMethod 47 | public boolean hasCameraAuthorization() { 48 | return true; 49 | } 50 | 51 | @ReactMethod 52 | public boolean hasMicrophoneAuthorization() { 53 | return true; 54 | } 55 | 56 | @ReactMethod 57 | public boolean hasPhotoAuthorization() { 58 | return true; 59 | } 60 | 61 | @ReactMethod 62 | public boolean hasPermissionLookup() { 63 | return true; 64 | } 65 | 66 | @ReactMethod 67 | public void getNotificationAuthorizationStatus(Promise promise) { 68 | promise.resolve(false); 69 | } 70 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | minSdkVersion = 24 6 | compileSdkVersion = 31 7 | targetSdkVersion = 31 8 | androidXCore = "1.3.0" 9 | ndkVersion = "20.1.5948944" 10 | kotlin_version = "1.6.10" 11 | compose_version = "1.1.0-rc02" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath("com.android.tools.build:gradle:7.0.4") 19 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url("$rootDir/../node_modules/react-native/android") 28 | } 29 | 30 | google() 31 | mavenCentral() 32 | maven { url "https://jitpack.io" } 33 | } 34 | 35 | // Use mavenCentral versions of jcenter libraries 36 | configurations.all { 37 | resolutionStrategy.eachDependency { details -> 38 | if (details.requested.group == "com.facebook.yoga" && details.requested.name == "proguard-annotations") { 39 | details.useVersion "1.19.0" 40 | } 41 | 42 | if (details.requested.group == "com.facebook.fbjni" && details.requested.name == "fbjni-java-only") { 43 | details.useVersion "0.0.4" 44 | } 45 | 46 | if (details.requested.group == "com.facebook.fresco") { 47 | details.useVersion "2.3.0" 48 | } 49 | 50 | // https://github.com/yqritc/Android-ScalableVideoView/issues/52 51 | if (details.requested.group == "com.yqritc" && details.requested.name == "android-scalablevideoview") { 52 | details.useTarget group: "com.github.MatrixFrog", name: "Android-ScalableVideoView", version: "v1.0.4-jitpack" 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/MMKVManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.facebook.react.bridge.*; 10 | 11 | public class MMKVManager extends ReactContextBaseJavaModule { 12 | private final String TAG = "MMKVManager"; 13 | private final SharedPreferences prefs; 14 | private final String name; 15 | 16 | public MMKVManager(ReactApplicationContext reactContext, String name) { 17 | super(reactContext); 18 | prefs = reactContext.getSharedPreferences(name, Context.MODE_PRIVATE); 19 | this.name = name; 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | @ReactMethod 29 | public void setItem(String key, String value, Promise promise) { 30 | Log.i(TAG, String.format("setItem: %s = %s", key, value)); 31 | prefs.edit().putString(key, value).apply(); 32 | promise.resolve(true); 33 | } 34 | 35 | @ReactMethod 36 | public void getItem(String key, Promise promise) { 37 | Log.i(TAG, String.format("getItem: %s", key)); 38 | promise.resolve(prefs.getString(key, null)); 39 | } 40 | 41 | @ReactMethod 42 | public void removeItem(String key, Promise promise) { 43 | Log.i(TAG, String.format("removeItem: %s", key)); 44 | prefs.edit().remove(key).apply(); 45 | promise.resolve(true); 46 | } 47 | 48 | @ReactMethod 49 | public void clear(ReadableArray keys, Promise promise) { 50 | Log.i(TAG, "clear"); 51 | SharedPreferences.Editor editor = prefs.edit(); 52 | for (int i = 0, j = keys.size(); i < j; i++) { 53 | editor.remove(keys.getString(i)); 54 | } 55 | editor.apply(); 56 | promise.resolve(null); 57 | } 58 | 59 | @ReactMethod 60 | public void refresh(ReadableArray keys, Promise promise) { 61 | promise.resolve(null); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDFastConnectManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 11 | import com.facebook.react.bridge.ReactMethod; 12 | import com.facebook.react.module.annotations.ReactModule; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @ReactModule(name = "DCDFastConnectManager") 18 | public class DCDFastConnectManager extends ReactContextBaseJavaModule { 19 | private static final String TAG = "FastConnectManager"; 20 | private final SharedPreferences prefs; 21 | 22 | private final Map identities = new HashMap<>(); 23 | 24 | public DCDFastConnectManager(ReactApplicationContext context) { 25 | super(context); 26 | prefs = context.getSharedPreferences("MMKVManager", Context.MODE_PRIVATE); 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public String getName() { 32 | return "DCDFastConnectManager"; 33 | } 34 | 35 | @ReactMethod 36 | public void setClientState(String state) { 37 | Log.i(TAG, "setClientState: " + state); 38 | prefs.edit().putString("clientState", state).apply(); 39 | } 40 | 41 | @ReactMethod 42 | public void prepareIdentify(String json, int socketId) { 43 | Log.i(TAG, "prepareIdentify: " + json + ", " + socketId); 44 | identities.put(socketId, json); 45 | } 46 | 47 | public Map getIdentities() { 48 | return identities; 49 | } 50 | 51 | @Override 52 | public Map getConstants() { 53 | final Map constants = new HashMap<>(); 54 | String token = prefs.getString("token", null); 55 | constants.put("token", token == null ? null : token.substring(1, token.length() - 1)); 56 | constants.put("clientState", prefs.getString("clientState", null)); 57 | return constants; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/PushNotificationAndroid.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Callback; 6 | import com.facebook.react.bridge.Promise; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | 11 | public class PushNotificationAndroid extends ReactContextBaseJavaModule { 12 | public PushNotificationAndroid(ReactApplicationContext context) { 13 | super(context); 14 | } 15 | 16 | @NonNull 17 | @Override 18 | public String getName() { 19 | return "PushNotificationAndroid"; 20 | } 21 | 22 | @ReactMethod 23 | public void getInitialNotification(Promise promise) { 24 | promise.resolve(null); 25 | } 26 | 27 | @ReactMethod 28 | public void setApplicationIconBadgeNumber(int i) { 29 | } 30 | 31 | @ReactMethod 32 | public void clearAllNotifications() { 33 | } 34 | 35 | @ReactMethod 36 | public void presentLocalNotification() { 37 | } 38 | 39 | @ReactMethod 40 | public void scheduleLocalNotification() { 41 | } 42 | 43 | @ReactMethod 44 | public void getScheduledLocalNotifications() { 45 | } 46 | 47 | @ReactMethod 48 | public void cancelLocalNotifications() { 49 | } 50 | 51 | @ReactMethod 52 | public void cancelAllLocalNotifications() { 53 | } 54 | 55 | @ReactMethod 56 | public boolean checkPermissions() { 57 | return true; 58 | } 59 | 60 | @ReactMethod 61 | public void requestPermissions(Promise p) { 62 | p.resolve(true); 63 | } 64 | 65 | @ReactMethod 66 | public void openNotificationSettings() { 67 | } 68 | 69 | @ReactMethod 70 | public void addNotificationEventListener(Callback cb) { 71 | } 72 | 73 | @ReactMethod 74 | public void addRegisterEventListener(Callback cb) { 75 | } 76 | 77 | @ReactMethod 78 | public void addListener(Callback cb) { 79 | } 80 | 81 | @ReactMethod 82 | public void addEventListener(Callback cb) { 83 | } 84 | 85 | @ReactMethod 86 | public void registerEventListener(String what) { 87 | } 88 | 89 | @ReactMethod 90 | public void appStateChanged(String appState) { 91 | } 92 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/ImageManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Color; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.palette.graphics.Palette; 10 | 11 | import com.facebook.react.bridge.Promise; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.ReadableMap; 16 | import com.facebook.react.bridge.WritableNativeArray; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | 21 | public class ImageManager extends ReactContextBaseJavaModule { 22 | public ImageManager(ReactApplicationContext context) { 23 | super(context); 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public String getName() { 29 | return "ImageManager"; 30 | } 31 | 32 | @ReactMethod 33 | public void getDominantColor(ReadableMap image, Promise promise) { 34 | try { 35 | URL url = new URL(image.getString("uri")); 36 | Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream()); 37 | 38 | getDominantColor(bitmap, promise); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | 44 | @ReactMethod 45 | public void getDominantColorLocalAsset(ReadableMap image, Promise promise) { 46 | ReactApplicationContext context = getReactApplicationContext(); 47 | Resources resources = context.getResources(); 48 | int resourceId = resources.getIdentifier(image.getString("uri"), "drawable", context.getPackageName()); 49 | Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId); 50 | 51 | getDominantColor(bitmap, promise); 52 | } 53 | 54 | private void getDominantColor(Bitmap bitmap, Promise promise) { 55 | new Palette.Builder(bitmap).generate(palette -> { 56 | int color = palette == null ? Color.BLACK : palette.getDominantColor(Color.BLACK); 57 | int r = (color >> 16) & 0xFF; 58 | int g = (color >> 8) & 0xFF; 59 | int b = (color) & 0xFF; 60 | 61 | WritableNativeArray colorArray = new WritableNativeArray(); 62 | colorArray.pushInt(r); 63 | colorArray.pushInt(g); 64 | colorArray.pushInt(b); 65 | promise.resolve(colorArray); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDChatInputManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.view.KeyEvent; 4 | import android.widget.EditText; 5 | import android.widget.LinearLayout; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 12 | import com.facebook.react.bridge.ReactMethod; 13 | import com.facebook.react.uimanager.UIManagerModule; 14 | 15 | import java.util.Objects; 16 | 17 | public class DCDChatInputManager extends ReactContextBaseJavaModule { 18 | public DCDChatInputManager(ReactApplicationContext context) { 19 | super(context); 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public String getName() { 25 | return "DCDChatInputManager"; 26 | } 27 | 28 | @ReactMethod 29 | public void setText(int id, String text) { 30 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> { 31 | UIManagerModule managerModule = getReactApplicationContext().getNativeModule(UIManagerModule.class); 32 | assert managerModule != null; 33 | LinearLayout layout = (LinearLayout) managerModule.resolveView(id); 34 | EditText editor = (EditText) layout.getChildAt(0); 35 | editor.setText(text); 36 | }); 37 | } 38 | 39 | @ReactMethod 40 | public void refreshFirstResponder(int id) { 41 | } 42 | 43 | @ReactMethod 44 | public void backspace(int id) { 45 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> { 46 | UIManagerModule managerModule = getReactApplicationContext().getNativeModule(UIManagerModule.class); 47 | assert managerModule != null; 48 | LinearLayout layout = (LinearLayout) managerModule.resolveView(id); 49 | EditText editor = (EditText) layout.getChildAt(0); 50 | editor.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); 51 | editor.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 52 | }); 53 | } 54 | 55 | @ReactMethod 56 | public void blur(int id) { 57 | } 58 | 59 | @ReactMethod 60 | public void focus(int id) { 61 | } 62 | 63 | @ReactMethod 64 | public void closeCustomKeyboard(int id) { 65 | } 66 | 67 | @ReactMethod 68 | public void openSystemKeyboard(int id) { 69 | } 70 | 71 | @ReactMethod 72 | public void openCustomKeyboard(int id) { 73 | } 74 | 75 | @ReactMethod 76 | public void setSelectedRange(int id, int start, int end) { 77 | } 78 | 79 | @ReactMethod 80 | public void replaceRange(int id, String text, int start, int end) { 81 | } 82 | 83 | @ReactMethod 84 | public void updateTextBlocks(int id, int t, int n) { 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/VoiceEngine.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.Callback; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.facebook.react.bridge.ReadableMap; 11 | import com.facebook.react.bridge.WritableMap; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | public class VoiceEngine extends ReactContextBaseJavaModule { 17 | public VoiceEngine(ReactApplicationContext context) { 18 | super(context); 19 | } 20 | 21 | @NonNull 22 | @Override 23 | public String getName() { 24 | return "VoiceEngine"; 25 | } 26 | 27 | @ReactMethod 28 | public void setTransportOptions(ReadableMap obj) { 29 | } 30 | 31 | @ReactMethod 32 | public boolean getUseLegacyAudioDevice() { 33 | return false; 34 | } 35 | 36 | @ReactMethod 37 | public void pingVoiceThread(Callback callback) { 38 | callback.invoke(); 39 | } 40 | 41 | @ReactMethod 42 | public void getAudioSubsystem(Callback callback) { 43 | callback.invoke("standard", ""); 44 | } 45 | 46 | @ReactMethod 47 | public boolean getDebugLogging() { 48 | return true; 49 | } 50 | 51 | @ReactMethod 52 | public void init() { 53 | } 54 | 55 | // @ReactMethod 56 | // public void createVoiceConnection() { 57 | // } 58 | 59 | // @ReactMethod 60 | // public void createOwnStreamConnection() { 61 | // } 62 | 63 | @ReactMethod 64 | public void getInputDevices(Callback cb) { 65 | cb.invoke(Arguments.createArray()); 66 | } 67 | 68 | @ReactMethod 69 | public void getOutputDevices(Callback cb) { 70 | cb.invoke(Arguments.createArray()); 71 | } 72 | 73 | @ReactMethod 74 | public void getVideoInputDevices(Callback cb) { 75 | cb.invoke(Arguments.createArray()); 76 | } 77 | 78 | @ReactMethod 79 | public void setInputDevice(String index) { 80 | } 81 | 82 | @ReactMethod 83 | public void setOutputDevice(String index) { 84 | } 85 | 86 | @ReactMethod 87 | public void setInputVolume(int volume) { 88 | } 89 | 90 | @ReactMethod 91 | public void setOutputVolume(int volume) { 92 | } 93 | 94 | @ReactMethod 95 | public void setEmitVADLevel(boolean a, boolean b, ReadableMap c) { 96 | } 97 | 98 | @Override 99 | public Map getConstants() { 100 | final Map constants = new HashMap<>(); 101 | final WritableMap modes = Arguments.createMap(); 102 | modes.putInt("VOICE", 0); 103 | modes.putInt("LISTEN", 1); 104 | modes.putInt("VIDEO", 2); 105 | constants.put("AVAudioSessionMode", modes); 106 | return constants; 107 | } 108 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/TimersModule.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.os.Handler; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.facebook.react.bridge.Arguments; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 11 | import com.facebook.react.bridge.ReactMethod; 12 | import com.facebook.react.bridge.WritableMap; 13 | import com.facebook.react.modules.core.DeviceEventManagerModule; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Timer; 18 | import java.util.TimerTask; 19 | 20 | public class TimersModule extends ReactContextBaseJavaModule { 21 | private static final String TAG = "TimersModule"; 22 | 23 | private Handler handler; 24 | private Timer timer; 25 | 26 | private final Map timeouts = new HashMap<>(); 27 | private final Map intervals = new HashMap<>(); 28 | 29 | public TimersModule(ReactApplicationContext context) { 30 | super(context); 31 | } 32 | 33 | @NonNull 34 | @Override 35 | public String getName() { 36 | return "TimersModule"; 37 | } 38 | 39 | private void sendEvent(String eventName, WritableMap params) { 40 | getReactApplicationContext() 41 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 42 | .emit(eventName, params); 43 | } 44 | 45 | @Override 46 | public void initialize() { 47 | super.initialize(); 48 | 49 | handler = new Handler(); 50 | timer = new Timer(); 51 | } 52 | 53 | @ReactMethod 54 | public void setTimeout(int id, int delay) { 55 | Log.i(TAG, "setTimeout: " + delay); 56 | 57 | timeouts.put(id, new Object()); 58 | handler.postDelayed(() -> { 59 | if (!timeouts.containsKey(id)) 60 | return; 61 | 62 | WritableMap data = Arguments.createMap(); 63 | data.putInt("id", id); 64 | sendEvent("timer", data); 65 | }, delay); 66 | } 67 | 68 | @ReactMethod 69 | public void clearTimeout(int id) { 70 | Log.i(TAG, "clearTimeout: " + id); 71 | 72 | Object token = timeouts.remove(id); 73 | if (token != null) { 74 | handler.removeCallbacksAndMessages(token); 75 | } 76 | } 77 | 78 | @ReactMethod 79 | public void setInterval(int id, int period) { 80 | Log.i(TAG, "setInterval: " + period); 81 | 82 | TimerTask timerTask = new TimerTask() { 83 | @Override 84 | public void run() { 85 | if (!intervals.containsKey(id)) { 86 | return; 87 | } 88 | 89 | WritableMap data = Arguments.createMap(); 90 | data.putInt("id", id); 91 | sendEvent("interval", data); 92 | } 93 | }; 94 | intervals.put(id, timerTask); 95 | timer.scheduleAtFixedRate(timerTask, 0, period); 96 | } 97 | 98 | @ReactMethod 99 | public void clearInterval(int id) { 100 | Log.i(TAG, "clearInterval: " + id); 101 | 102 | TimerTask task = intervals.remove(id); 103 | if (task != null) { 104 | task.cancel(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /android/app/src/debug/java/com/discordrn/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.discordrn; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/MainAppPackage.java: -------------------------------------------------------------------------------- 1 | package com.discordrn; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.discordrn.modules.*; 6 | import com.discordrn.views.*; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MainAppPackage implements ReactPackage { 16 | @SuppressWarnings("rawtypes") 17 | @NonNull 18 | @Override 19 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 20 | List views = new ArrayList<>(); 21 | 22 | views.add(new DCDSegmentedControl()); 23 | views.add(new ViewStub("DCDSafeArea")); 24 | // views.add(new ViewStub("DCDChatList")); 25 | views.add(new DCDChatList()); 26 | views.add(new DCDChat()); 27 | views.add(new DCDChatInput()); 28 | views.add(new ViewStub("ChannelSpine")); 29 | views.add(new ViewStub("KeyCommandsView")); // https://github.com/envoy/react-native-key-commands, ios only 30 | views.add(new NativeLottieNode()); 31 | 32 | return views; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) { 38 | List modules = new ArrayList<>(); 39 | 40 | modules.add(new ActionSheetAndroid(reactContext)); 41 | modules.add(new BrowserManager(reactContext)); 42 | modules.add(new BundleUpdaterManager(reactContext)); 43 | modules.add(new CaptchaManager(reactContext)); 44 | modules.add(new DCDChatInputManager(reactContext)); 45 | modules.add(new DCDChatManager(reactContext)); 46 | modules.add(new DCDColor(reactContext)); 47 | modules.add(new DCDCompressionManager(reactContext)); 48 | modules.add(new DCDCrashlyticsCrashReports(reactContext)); 49 | modules.add(new DCDDeviceManager(reactContext)); 50 | modules.add(new DCDFastConnectManager(reactContext)); 51 | modules.add(new DCDIconManager(reactContext)); 52 | modules.add(new DCDNotificationCategoryUtils(reactContext)); 53 | modules.add(new DCDSafeAreaManager(reactContext)); 54 | modules.add(new DCDSKAdNetworkManager(reactContext)); 55 | modules.add(new DCDTheme(reactContext)); 56 | modules.add(new DynamicLinkManager(reactContext)); 57 | modules.add(new ExpoRandom(reactContext)); 58 | modules.add(new ImageManager(reactContext)); 59 | modules.add(new InfoDictionaryManager(reactContext)); 60 | modules.add(new IntentsHandler(reactContext)); 61 | modules.add(new KeyboardManager(reactContext)); 62 | modules.add(new KeyCommandsModule(reactContext)); 63 | modules.add(new LocalizationManager(reactContext)); 64 | modules.add(new MediaManager(reactContext)); 65 | modules.add(new MMKVManager(reactContext, "DCDStrongboxManager")); 66 | modules.add(new MMKVManager(reactContext, "MMKVManager")); 67 | modules.add(new NativePermissionManager(reactContext)); 68 | modules.add(new OnePasswordManager(reactContext)); 69 | modules.add(new ProximitySensorManager(reactContext)); 70 | modules.add(new PushNotificationAndroid(reactContext)); 71 | modules.add(new RNCPushNotificationIOS(reactContext)); 72 | modules.add(new RNSentryModule(reactContext)); 73 | modules.add(new ScreenshotHelper(reactContext)); 74 | modules.add(new TimersModule(reactContext)); 75 | modules.add(new TTIManager(reactContext)); 76 | modules.add(new UserSearchWorkerManager(reactContext)); 77 | modules.add(new VoiceEngine(reactContext)); 78 | 79 | return modules; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDChatManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.view.animation.AlphaAnimation; 4 | import android.view.animation.Animation; 5 | import android.widget.LinearLayout; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.discordrn.models.MessageContent; 10 | import com.discordrn.models.Row; 11 | import com.discordrn.views.DCDChatList; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.uimanager.UIManagerModule; 16 | import com.google.gson.Gson; 17 | import com.google.gson.GsonBuilder; 18 | import com.google.gson.reflect.TypeToken; 19 | import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; 20 | 21 | import java.lang.reflect.Type; 22 | import java.util.List; 23 | import java.util.Objects; 24 | 25 | public class DCDChatManager extends ReactContextBaseJavaModule { 26 | private static final Gson gson; 27 | private static final Type rowsType = TypeToken.getParameterized(List.class, Row.class).getType(); 28 | 29 | static { 30 | RuntimeTypeAdapterFactory contentFactory = RuntimeTypeAdapterFactory.of(MessageContent.class, "type"); 31 | contentFactory.registerSubtype(MessageContent.Text.class, "text"); 32 | contentFactory.registerSubtype(MessageContent.Link.class, "link"); 33 | contentFactory.registerSubtype(MessageContent.CodeBlock.class, "codeBlock"); 34 | contentFactory.registerSubtype(MessageContent.Mention.class, "mention"); 35 | contentFactory.registerSubtype(MessageContent.Channel.class, "channel"); 36 | contentFactory.registerSubtype(MessageContent.Emoji.class, "emoji"); 37 | contentFactory.registerSubtype(MessageContent.CustomEmoji.class, "customEmoji"); 38 | contentFactory.registerSubtype(MessageContent.Strong.class, "strong"); 39 | contentFactory.registerSubtype(MessageContent.Emphasis.class, "em"); 40 | contentFactory.registerSubtype(MessageContent.Stroke.class, "s"); 41 | contentFactory.registerSubtype(MessageContent.Underlined.class, "u"); 42 | contentFactory.registerSubtype(MessageContent.InlineCode.class, "inlineCode"); 43 | contentFactory.registerSubtype(MessageContent.Spoiler.class, "spoiler"); 44 | contentFactory.registerSubtype(MessageContent.Timestamp.class, "timestamp"); 45 | contentFactory.registerSubtype(MessageContent.BlockQuote.class, "blockQuote"); 46 | contentFactory.registerSubtype(MessageContent.Paragraph.class, "paragraph"); 47 | 48 | gson = new GsonBuilder() 49 | .registerTypeAdapterFactory(contentFactory) 50 | .create(); 51 | } 52 | 53 | public DCDChatManager(ReactApplicationContext context) { 54 | super(context); 55 | } 56 | 57 | @NonNull 58 | @Override 59 | public String getName() { 60 | return "DCDChatManager"; 61 | } 62 | 63 | @ReactMethod 64 | public void updateRows(int id, String json, boolean b) { 65 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> { 66 | List rows = gson.fromJson(json, rowsType); 67 | for (Row row : rows) { 68 | if (row.message != null && row.message.content != null) 69 | DCDChatList.addMessage(row.message); 70 | } 71 | }); 72 | } 73 | 74 | @ReactMethod 75 | public void clearRows(int id) { 76 | DCDChatList.clearMessages(); 77 | } 78 | 79 | @ReactMethod 80 | public void scrollTo(int id, int message, boolean instant) { 81 | } 82 | 83 | @ReactMethod 84 | public void fadeIn(int id) { 85 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> { 86 | UIManagerModule managerModule = getReactApplicationContext().getNativeModule(UIManagerModule.class); 87 | assert managerModule != null; 88 | LinearLayout layout = (LinearLayout) managerModule.resolveView(id); 89 | AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); 90 | anim.setDuration(300); 91 | anim.setRepeatCount(1); 92 | anim.setRepeatMode(Animation.REVERSE); 93 | layout.startAnimation(anim); 94 | }); 95 | } 96 | 97 | @ReactMethod 98 | public void scrollToBottom(int id, boolean b) { 99 | } 100 | 101 | @ReactMethod 102 | public void scrollToTop(int id, boolean b) { 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/DCDCompressionManager.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.facebook.react.bridge.WritableMap; 11 | import com.facebook.react.modules.websocket.WebSocketModule; 12 | 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.io.EOFException; 17 | import java.io.IOException; 18 | import java.util.HashSet; 19 | import java.util.Map; 20 | import java.util.Objects; 21 | import java.util.Set; 22 | import java.util.zip.Inflater; 23 | 24 | import okio.Buffer; 25 | import okio.ByteString; 26 | import okio.InflaterSource; 27 | 28 | public class DCDCompressionManager extends ReactContextBaseJavaModule { 29 | private static final String TAG = "CompressionManager"; 30 | 31 | public DCDCompressionManager(ReactApplicationContext context) { 32 | super(context); 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public String getName() { 38 | return "DCDCompressionManager"; 39 | } 40 | 41 | private WebSocketModule getSocketModule() { 42 | return getReactApplicationContext().getNativeModule(WebSocketModule.class); 43 | } 44 | 45 | private static final ByteString FLUSH = ByteString.of((byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF); 46 | 47 | private Set enabled = new HashSet<>(); 48 | 49 | @ReactMethod 50 | public void enableZlibStreamSupport(int socketId) { 51 | Log.i(TAG, "enableZlibStreamSupport: " + socketId); 52 | 53 | if (enabled.contains(socketId)) 54 | return; 55 | 56 | enabled.add(socketId); 57 | 58 | Buffer buffer = new Buffer(); 59 | Buffer result = new Buffer(); 60 | Inflater inflater = new Inflater(); 61 | 62 | WebSocketModule webSocketModule = getSocketModule(); 63 | webSocketModule.setContentHandler(socketId, new WebSocketModule.ContentHandler() { 64 | @Override 65 | public void onMessage(String text, WritableMap params) { 66 | params.putString("data", text); 67 | } 68 | 69 | @Override 70 | public void onMessage(ByteString byteString, WritableMap params) { 71 | buffer.write(byteString); 72 | 73 | long size = buffer.size(); 74 | if (size < 4 || !buffer.rangeEquals(size - 4, FLUSH)) 75 | return; 76 | 77 | InflaterSource source = new InflaterSource(buffer, inflater); 78 | result.clear(); 79 | 80 | try { 81 | try { 82 | while (source.read(result, Long.MAX_VALUE) != -1) { 83 | } 84 | } catch (EOFException ignored) { 85 | } 86 | } catch (IOException e) { 87 | Log.e(TAG, "inflating failed: " + e); 88 | } 89 | 90 | buffer.clear(); 91 | 92 | String json = result.snapshot().utf8(); 93 | 94 | Log.d(TAG, "received: " + json); 95 | params.putString("type", "text"); 96 | params.putString("data", json); 97 | 98 | try { 99 | int op = new JSONObject(json).getInt("op"); 100 | if (op == 10) { 101 | Map identities = Objects.requireNonNull(getReactApplicationContext().getNativeModule(DCDFastConnectManager.class)).getIdentities(); 102 | 103 | if (identities.containsKey(socketId)) { 104 | String identity = identities.get(socketId); 105 | Log.d(TAG, "sending: " + identity); 106 | webSocketModule.send(identity, socketId); 107 | } 108 | } 109 | } catch (JSONException e) { 110 | Log.e(TAG, "failed to parse json: " + e); 111 | } 112 | } 113 | }); 114 | } 115 | 116 | @ReactMethod 117 | public void disableZlibStreamSupport(int socketId) { 118 | Log.i(TAG, "disableZlibStreamSupport: " + socketId); 119 | 120 | if (!enabled.contains(socketId)) 121 | return; 122 | 123 | enabled.remove(socketId); 124 | WebSocketModule webSocketModule = getSocketModule(); 125 | webSocketModule.setContentHandler(socketId, null); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/DCDSegmentedControl.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.view.Gravity; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import com.alanvan.segmented_control.SegmentedControlButton; 12 | import com.alanvan.segmented_control.SegmentedControlGroup; 13 | import com.facebook.react.bridge.*; 14 | import com.facebook.react.common.MapBuilder; 15 | import com.facebook.react.uimanager.SimpleViewManager; 16 | import com.facebook.react.uimanager.ThemedReactContext; 17 | import com.facebook.react.uimanager.annotations.ReactProp; 18 | import com.facebook.react.uimanager.events.RCTEventEmitter; 19 | 20 | import java.lang.reflect.Field; 21 | import java.util.Map; 22 | 23 | import kotlin.Unit; 24 | 25 | public class DCDSegmentedControl extends SimpleViewManager { 26 | private int textColor; 27 | private int selectedTextColor; 28 | private int selected; 29 | 30 | @NonNull 31 | @Override 32 | public String getName() { 33 | return "DCDSegmentedControl"; 34 | } 35 | 36 | @NonNull 37 | @Override 38 | protected SegmentedControlGroup createViewInstance(@NonNull ThemedReactContext reactContext) { 39 | SegmentedControlGroup group = new SegmentedControlGroup(reactContext); 40 | group.addView(createButton(reactContext)); 41 | group.setOnSelectedOptionChangeCallback((i) -> { 42 | selected = i; 43 | ((SegmentedControlButton) group.getChildAt(i)).setTextColor(selectedTextColor); 44 | for (int index = 0, count = group.getChildCount(); index < count; index++) 45 | if (index != i) ((SegmentedControlButton) group.getChildAt(index)).setTextColor(textColor); 46 | 47 | WritableMap data = Arguments.createMap(); 48 | data.putInt("selectedSegmentIndex", i); 49 | ((ReactContext) group.getContext()).getJSModule(RCTEventEmitter.class).receiveEvent(group.getId(), "onValueChange", data); 50 | return Unit.INSTANCE; 51 | }); 52 | return group; 53 | } 54 | 55 | @ReactProp(name = "values") 56 | public void setValues(SegmentedControlGroup group, ReadableArray values) { 57 | int size = values.size(); 58 | if (size == 0) return; 59 | else ((SegmentedControlButton) group.getChildAt(0)).setText(values.getString(0)); 60 | for (int i = 1; i < size; i++) { 61 | SegmentedControlButton btn = createButton(group.getContext()); 62 | btn.setText(values.getString(i)); 63 | group.addView(btn); 64 | } 65 | } 66 | 67 | @ReactProp(name = "selectedSegmentIndex") 68 | public void setSelectedSegmentIndex(SegmentedControlGroup group, int i) { 69 | selected = i; 70 | group.setSelectedIndex(i, false); 71 | } 72 | 73 | @ReactProp(name = "backgroundColor") 74 | public void setBackgroundColor(SegmentedControlGroup group, String color) { 75 | group.setBackgroundColor(Color.parseColor(color)); 76 | } 77 | 78 | @ReactProp(name = "titleAttributes") 79 | public void setTitleAttributes(SegmentedControlGroup group, ReadableMap map) { 80 | textColor = map.getInt("textColor"); 81 | for (int i = 0, j = group.getChildCount(); i < j; i++) 82 | ((SegmentedControlButton) group.getChildAt(i)).setTextColor(textColor); 83 | } 84 | 85 | @ReactProp(name = "selectedTitleAttributes") 86 | public void setSelectedTitleAttributes(SegmentedControlGroup group, ReadableMap map) { 87 | selectedTextColor = map.getInt("textColor"); 88 | ((SegmentedControlButton) group.getChildAt(selected)).setTextColor(selectedTextColor); 89 | } 90 | 91 | @ReactProp(name = "customSelectedTintColor") 92 | public void setCustomSelectedTintColor(SegmentedControlGroup group, String color) throws Throwable { 93 | Field field = group.getClass().getDeclaredField("sliderPaint"); 94 | field.setAccessible(true); 95 | Paint paint = (Paint) field.get(group); 96 | if (paint != null) { 97 | int c = Color.parseColor(color); 98 | paint.setColor(c); 99 | } 100 | } 101 | 102 | @Nullable 103 | @Override 104 | public Map getExportedCustomDirectEventTypeConstants() { 105 | return MapBuilder.of("onValueChange", MapBuilder.of("registrationName", "onValueChange")); 106 | } 107 | 108 | private SegmentedControlButton createButton(Context context) { 109 | SegmentedControlButton btn = new SegmentedControlButton(context); 110 | btn.setGravity(Gravity.CENTER); 111 | if (textColor != 0) btn.setTextColor(textColor); 112 | return btn; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.discordrn; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.res.AssetManager; 6 | import android.util.Log; 7 | 8 | import com.facebook.common.logging.FLog; 9 | import com.facebook.debug.debugoverlay.model.DebugOverlayTag; 10 | import com.facebook.debug.holder.Printer; 11 | import com.facebook.debug.holder.PrinterHolder; 12 | import com.facebook.hermes.reactexecutor.HermesExecutorFactory; 13 | import com.facebook.react.CustomReactInstanceManager; 14 | import com.facebook.react.PackageList; 15 | import com.facebook.react.ReactApplication; 16 | import com.facebook.react.ReactInstanceManager; 17 | import com.facebook.react.ReactNativeHost; 18 | import com.facebook.react.ReactPackage; 19 | import com.facebook.react.bridge.JSBundleLoader; 20 | import com.facebook.react.bridge.JSBundleLoaderDelegate; 21 | import com.facebook.react.bridge.JSIModulePackage; 22 | import com.facebook.react.common.LifecycleState; 23 | import com.facebook.soloader.SoLoader; 24 | import com.swmansion.reanimated.ReanimatedJSIModulePackage; 25 | 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.util.List; 28 | 29 | public class MainApplication extends Application implements ReactApplication { 30 | private final ReactNativeHost mReactNativeHost = 31 | new ReactNativeHost(this) { 32 | @Override 33 | public boolean getUseDeveloperSupport() { 34 | return BuildConfig.DEBUG; 35 | } 36 | 37 | @Override 38 | protected List getPackages() { 39 | List packages = new PackageList(this).getPackages(); 40 | packages.add(new MainAppPackage()); 41 | return packages; 42 | } 43 | 44 | @Override 45 | protected JSIModulePackage getJSIModulePackage() { 46 | return new ReanimatedJSIModulePackage(); 47 | } 48 | 49 | @Override 50 | protected String getJSMainModuleName() { 51 | return "index"; 52 | } 53 | 54 | @Override 55 | protected String getBundleAssetName() { 56 | return "discord.android.bundle"; 57 | } 58 | 59 | @Override 60 | protected ReactInstanceManager createReactInstanceManager() { 61 | return new CustomReactInstanceManager( 62 | getApplication(), 63 | null, 64 | null, 65 | new HermesExecutorFactory(), 66 | new JSBundleLoader() { 67 | @Override 68 | public String loadScript(JSBundleLoaderDelegate delegate) { 69 | AssetManager assetManager = getApplicationContext().getAssets(); 70 | 71 | delegate.loadScriptFromAssets(assetManager, "assets://preload.js", true); 72 | 73 | String assetUrl = "assets://" + getBundleAssetName(); 74 | delegate.loadScriptFromAssets(assetManager, assetUrl, false); 75 | return assetUrl; 76 | } 77 | }, 78 | getJSMainModuleName(), 79 | getPackages(), 80 | getUseDeveloperSupport(), 81 | null, 82 | LifecycleState.BEFORE_CREATE, 83 | getUIImplementationProvider(), 84 | null, 85 | null, 86 | false, 87 | null, 88 | 1, 89 | -1, 90 | getJSIModulePackage(), 91 | null 92 | ); 93 | } 94 | }; 95 | 96 | @Override 97 | public ReactNativeHost getReactNativeHost() { 98 | return mReactNativeHost; 99 | } 100 | 101 | @Override 102 | public void onCreate() { 103 | super.onCreate(); 104 | FLog.setMinimumLoggingLevel(Log.VERBOSE); 105 | SoLoader.init(this, false); 106 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 107 | if (BuildConfig.PRINTER_LOGS) PrinterHolder.setPrinter(new Printer() { 108 | private void log(String message) { 109 | if (message.contains("ReanimatedModule") || message.contains("NativeAnimatedModule")) 110 | return; 111 | 112 | Log.v("Printer", message); 113 | } 114 | 115 | @Override 116 | public void logMessage(DebugOverlayTag tag, String message, Object... args) { 117 | log(String.format("[%s] %s", tag.name, String.format(message, args))); 118 | } 119 | 120 | @Override 121 | public void logMessage(DebugOverlayTag tag, String message) { 122 | log(String.format("[%s] %s", tag.name, message)); 123 | } 124 | 125 | @Override 126 | public boolean shouldDisplayLogMessage(DebugOverlayTag tag) { 127 | return true; 128 | } 129 | }); 130 | } 131 | 132 | private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 133 | if (BuildConfig.DEBUG) { 134 | try { 135 | Class.forName("com.discordrn.ReactNativeFlipper") 136 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 137 | .invoke(null, context, reactInstanceManager); 138 | } catch (ClassNotFoundException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { 139 | e.printStackTrace(); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /preload/src/index.js: -------------------------------------------------------------------------------- 1 | import * as driver from "./driver"; 2 | import reactDevToolsCode from "./react-devtools-core.txt"; 3 | 4 | const loggerId = 13; 5 | const promiseId = 111; 6 | const nativeModulesId = 41; 7 | const localForageId = 892; 8 | const setUpBatchedBridgeId = 152; 9 | const exceptionsManagerId = 99; 10 | 11 | const appStateId = 415; 12 | const viewConfigId = 183; 13 | const flattenStyleId = 164; 14 | 15 | globalThis = new Proxy( 16 | globalThis, 17 | { 18 | set: function (obj, prop, value) { 19 | if (prop === "__d") { 20 | obj[prop] = function (factory, moduleId, dependencyMap) { 21 | if (moduleId === loggerId || moduleId == promiseId || moduleId == localForageId || moduleId == setUpBatchedBridgeId || moduleId == exceptionsManagerId) { 22 | const _factory = factory; 23 | factory = function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) { 24 | const ret = _factory(global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap); 25 | 26 | if (moduleId === loggerId) { 27 | const _logger = module.exports.default; 28 | module.exports.default = function (name) { 29 | const logger = new _logger(name); 30 | const prefix = `[${name}] `; 31 | logger.log = (s) => console.log(prefix + s); 32 | logger.warn = (s) => console.warn(prefix + s); 33 | logger.error = (s) => console.error(prefix + (s && s.stack ? s.stack : s)); 34 | logger.trace = (s) => console.log(prefix + s); 35 | logger.verbose = (s) => console.log(prefix + s); 36 | return logger; 37 | } 38 | } else if (moduleId == promiseId) { 39 | module.exports = new Proxy(module.exports, { 40 | construct: function (target, argumentsList, newTarget) { 41 | const promise = new target(...argumentsList); 42 | 43 | if (argumentsList.length > 0 && argumentsList[0] !== Promise._0) { 44 | const stack = new Error("Promise not resolved after 10 seconds").stack; 45 | setTimeout(() => { 46 | if (promise._V === 0) { 47 | console.warn(stack); 48 | } 49 | }, 10000); 50 | } 51 | 52 | return promise; 53 | } 54 | }); 55 | 56 | module.exports._Z = (promise, error) => { 57 | console[error instanceof Error ? "error" : "warn"](`Promise rejection (_deferredState: ${promise._U})\n` + (error instanceof Error ? error.stack : new Error(typeof error === "object" ? JSON.stringify(error) : error).stack)); 58 | }; 59 | } else if (moduleId == localForageId) { 60 | driver.AsyncStorage = _$$_REQUIRE(nativeModulesId).RNC_AsyncSQLiteDBStorage; 61 | const _driver = driver.driverWithoutSerialization(); 62 | module.exports.defineDriver(_driver).then(() => module.exports.setDriver(_driver._driver)); 63 | 64 | module.exports.__proto__.getDriver = function getDriver(t, n, o) { 65 | const d = Promise.resolve(_driver); 66 | typeof n == "function" && d.then(n); 67 | typeof o == "function" && d.catch(o); 68 | return d; 69 | }; 70 | } else if (moduleId == setUpBatchedBridgeId) { 71 | (0, eval)(reactDevToolsCode); 72 | const reactDevTools = require_react_devtools_core(); 73 | 74 | function setUpReactDevTools() { 75 | const AppState = _$$_REQUIRE(appStateId); 76 | const isAppActive = () => AppState.currentState !== "background"; 77 | 78 | const ws = new WebSocket("ws://localhost:8097"); 79 | ws.addEventListener("close", function (event) { 80 | console.log("Devtools connection closed: " + event.message); 81 | }); 82 | 83 | const viewConfig = _$$_REQUIRE(viewConfigId); 84 | const { flattenStyle } = _$$_REQUIRE(flattenStyleId); 85 | console.log("Connecting to devtools"); 86 | reactDevTools.connectToDevTools({ 87 | isAppActive, 88 | resolveRNStyle: flattenStyle, 89 | nativeStyleEditorValidAttributes: viewConfig.validAttributes.style ? Object.keys( 90 | viewConfig.validAttributes.style, 91 | ) : undefined, 92 | websocket: ws 93 | }); 94 | } 95 | 96 | setUpReactDevTools(); 97 | 98 | const { NativeAppEventEmitter } = _$$_REQUIRE(17); 99 | NativeAppEventEmitter.addListener("RCTDevMenuShown", setUpReactDevTools); 100 | } else if (moduleId == exceptionsManagerId) { 101 | module.exports.handleException = (e, isFatal) => { 102 | if (e instanceof Error) { 103 | console.error(e.stack); 104 | } else { 105 | console.error(e); 106 | } 107 | }; 108 | } 109 | 110 | return ret; 111 | }; 112 | } 113 | 114 | return value(factory, moduleId, dependencyMap); 115 | } 116 | } else { 117 | obj[prop] = value; 118 | } 119 | 120 | return true; 121 | }, 122 | }, 123 | ); 124 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonSlurper 2 | import java.security.* 3 | 4 | apply plugin: "com.android.application" 5 | apply plugin: "kotlin-android" 6 | 7 | project.ext.react = [ 8 | enableHermes : true, 9 | bundleInRelease: false, 10 | ] 11 | 12 | apply from: "../../node_modules/react-native/react.gradle" 13 | 14 | /** 15 | * Set this to true to create two separate APKs instead of one: 16 | * - An APK that only works on ARM devices 17 | * - An APK that only works on x86 devices 18 | * The advantage is the size of the APK is reduced by about 4MB. 19 | * Upload all the APKs to the Play Store and people will download 20 | * the correct one based on the CPU architecture of their device. 21 | */ 22 | def enableSeparateBuildPerCPUArchitecture = false 23 | 24 | android { 25 | ndkVersion rootProject.ext.ndkVersion 26 | 27 | compileSdkVersion rootProject.ext.compileSdkVersion 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_11 31 | targetCompatibility JavaVersion.VERSION_11 32 | } 33 | 34 | defaultConfig { 35 | applicationId "com.discordrn" 36 | minSdkVersion rootProject.ext.minSdkVersion 37 | targetSdkVersion rootProject.ext.targetSdkVersion 38 | versionCode 1 39 | versionName "1.0" 40 | 41 | buildConfigField "boolean", "PRINTER_LOGS", "false" 42 | } 43 | 44 | buildFeatures { 45 | compose true 46 | } 47 | 48 | composeOptions { 49 | kotlinCompilerExtensionVersion rootProject.ext.compose_version 50 | } 51 | 52 | kotlinOptions { 53 | jvmTarget = "11" 54 | } 55 | 56 | splits { 57 | abi { 58 | reset() 59 | enable enableSeparateBuildPerCPUArchitecture 60 | universalApk false // If true, also generate a universal APK 61 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 62 | } 63 | } 64 | signingConfigs { 65 | debug { 66 | storeFile file('debug.keystore') 67 | storePassword 'android' 68 | keyAlias 'androiddebugkey' 69 | keyPassword 'android' 70 | } 71 | } 72 | buildTypes { 73 | debug { 74 | signingConfig signingConfigs.debug 75 | } 76 | release { 77 | // Caution! In production, you need to generate your own keystore file. 78 | // see https://reactnative.dev/docs/signed-apk-android. 79 | signingConfig signingConfigs.debug 80 | minifyEnabled false 81 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 82 | } 83 | } 84 | 85 | // applicationVariants are e.g. debug, release 86 | applicationVariants.all { variant -> 87 | variant.outputs.each { output -> 88 | // For each separate APK per architecture, set a unique version code as described here: 89 | // https://developer.android.com/studio/build/configure-apk-splits.html 90 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. 91 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 92 | def abi = output.getFilter(com.android.build.OutputFile.ABI) 93 | if (abi != null) { // null for the universal-debug, universal-release variants 94 | output.versionCodeOverride = 95 | defaultConfig.versionCode * 1000 + versionCodes.get(abi) 96 | } 97 | 98 | } 99 | } 100 | } 101 | 102 | dependencies { 103 | implementation fileTree(dir: "libs", include: ["*.jar"]) 104 | //noinspection GradleDynamicVersion 105 | implementation "com.facebook.react:react-native:+" // From node_modules 106 | 107 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 108 | 109 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 110 | exclude group: 'com.facebook.fbjni' 111 | } 112 | 113 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 114 | exclude group: 'com.facebook.flipper' 115 | exclude group: 'com.squareup.okhttp3', module: 'okhttp' 116 | } 117 | 118 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { 119 | exclude group: 'com.facebook.flipper' 120 | } 121 | 122 | def hermesPath = "../../node_modules/hermes-engine/android/"; 123 | debugImplementation files(hermesPath + "hermes-debug.aar") 124 | releaseImplementation files(hermesPath + "hermes-release.aar") 125 | 126 | implementation 'com.github.alanvan0502:segmented-control-group:v1.0' 127 | implementation 'androidx.palette:palette-ktx:1.0.0' 128 | implementation 'com.github.khoyron:Actionsheet-android:4' 129 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 130 | implementation 'com.google.code.gson:gson:2.8.9' 131 | implementation 'org.danilopianini:gson-extras:0.4.0' 132 | 133 | implementation "androidx.appcompat:appcompat:1.3.1" 134 | implementation "androidx.core:core-ktx:$androidXCore" 135 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 136 | implementation "androidx.compose.compiler:compiler:$compose_version" 137 | implementation "androidx.compose.runtime:runtime:1.1.0-rc01" 138 | implementation "androidx.compose.ui:ui:1.0.5" 139 | implementation 'androidx.compose.material:material:1.0.5' 140 | 141 | implementation('com.google.accompanist:accompanist-appcompat-theme:0.22.0-rc') { 142 | exclude group: 'androidx.appcompat', module: 'appcompat' 143 | } 144 | 145 | implementation "com.github.skydoves:landscapist-fresco:1.4.5" 146 | } 147 | 148 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 149 | 150 | static def generateMD5(File file) { 151 | file.withInputStream { 152 | new DigestInputStream(it, MessageDigest.getInstance('MD5')).withStream { 153 | it.eachByte {} 154 | it.messageDigest.digest().encodeHex() as String 155 | } 156 | } 157 | } 158 | 159 | task downloadAssets { 160 | doLast { 161 | buildDir.mkdirs() 162 | File manifestFile = new File(buildDir, "manifest.json") 163 | if (!manifestFile.exists()) { 164 | new URL("https://discord.com/android/90.0/manifest.json").withInputStream { i -> manifestFile.withOutputStream { it << i } } 165 | } 166 | assert generateMD5(manifestFile) == "7aa00a9db0b647f6076e464401c05977" 167 | 168 | def json = new JsonSlurper().parse(manifestFile) 169 | def commit = json.metadata.commit 170 | 171 | json.hashes.entrySet().parallelStream().forEach { entry -> 172 | def path = entry.key 173 | def hash = entry.value 174 | def file = new File(projectDir, path.substring(4).replace("index.android.bundle", "discord.android.bundle")) 175 | 176 | if (!file.exists()) { 177 | file.parentFile.mkdirs() 178 | new URL("https://discord.com/assets/android/$commit/$path").withInputStream { i -> file.withOutputStream { it << i } } 179 | } 180 | 181 | assert generateMD5(file) == hash 182 | } 183 | } 184 | } 185 | tasks.named("preBuild") { dependsOn("downloadAssets") } 186 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/views/DCDChatList.kt: -------------------------------------------------------------------------------- 1 | package com.discordrn.views 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.lazy.LazyColumn 6 | import androidx.compose.foundation.lazy.items 7 | import androidx.compose.foundation.shape.CircleShape 8 | import androidx.compose.material.Colors 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.mutableStateListOf 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.clip 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.platform.ComposeView 18 | import androidx.compose.ui.platform.LocalContext 19 | import androidx.compose.ui.text.* 20 | import androidx.compose.ui.text.font.FontStyle 21 | import androidx.compose.ui.text.font.FontWeight 22 | import androidx.compose.ui.text.style.TextDecoration 23 | import androidx.compose.ui.unit.dp 24 | import androidx.compose.ui.unit.sp 25 | import com.discordrn.BuildConfig 26 | import com.discordrn.R 27 | import com.discordrn.models.Message 28 | import com.discordrn.models.MessageContent 29 | import com.facebook.react.uimanager.SimpleViewManager 30 | import com.facebook.react.uimanager.ThemedReactContext 31 | import com.google.accompanist.appcompattheme.AppCompatTheme 32 | import com.skydoves.landscapist.fresco.FrescoImage 33 | 34 | class DCDChatList : SimpleViewManager() { 35 | 36 | companion object { 37 | private val messages = mutableStateListOf() 38 | 39 | @JvmStatic 40 | fun addMessage(message: Message) { 41 | messages.add(message) 42 | } 43 | 44 | @JvmStatic 45 | fun clearMessages() { 46 | messages.clear() 47 | } 48 | 49 | fun getDefaultAvatarUri(name: String) = "res:/" + when (name) { 50 | "images_ios_avatars_default_avatar_1" -> R.drawable.images_ios_avatars_default_avatar_1 51 | "images_ios_avatars_default_avatar_2" -> R.drawable.images_ios_avatars_default_avatar_2 52 | "images_ios_avatars_default_avatar_3" -> R.drawable.images_ios_avatars_default_avatar_3 53 | "images_ios_avatars_default_avatar_4" -> R.drawable.images_ios_avatars_default_avatar_4 54 | else -> R.drawable.images_ios_avatars_default_avatar_0 55 | } 56 | } 57 | 58 | override fun getName() = "DCDChatList" 59 | 60 | override fun createViewInstance(reactContext: ThemedReactContext) = 61 | ComposeView(reactContext).apply { 62 | setContent { 63 | AppCompatTheme(context = reactContext) { 64 | LazyColumn( 65 | modifier = Modifier 66 | .fillMaxWidth() 67 | .wrapContentHeight(), 68 | reverseLayout = true 69 | ) { 70 | items(messages) { message -> 71 | WidgetChatMessage( 72 | modifier = Modifier 73 | .fillMaxWidth() 74 | .padding(6.dp), 75 | message = message, 76 | ) 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | @Composable 84 | private fun WidgetChatMessage( 85 | message: Message, 86 | modifier: Modifier = Modifier 87 | ) { 88 | val messageContentString = parseMessageContentToSpan(message.content) 89 | 90 | Row( 91 | modifier = modifier, 92 | horizontalArrangement = Arrangement.spacedBy(8.dp), 93 | verticalAlignment = Alignment.Top 94 | ) { 95 | if (message.avatarURL != null) { 96 | FrescoImage( 97 | imageUrl = message.avatarURL.run { 98 | if (startsWith("images_")) getDefaultAvatarUri(this) else this }, 99 | modifier = Modifier 100 | .size(40.dp) 101 | .clip(CircleShape), 102 | ) 103 | Column( 104 | modifier = Modifier 105 | .weight(1f) 106 | .heightIn(min = 40.dp), 107 | verticalArrangement = Arrangement.SpaceEvenly, 108 | ) { 109 | Row { 110 | Text( 111 | text = message.username, 112 | fontSize = 14.sp, 113 | fontWeight = FontWeight.SemiBold, 114 | color = Color(message.colorString) 115 | ) 116 | Text( 117 | text = message.timestamp, 118 | fontSize = 12.sp, 119 | color = Color((message.timestampColor as Double).toInt()), 120 | modifier = Modifier.padding(start = 4.dp) 121 | ) 122 | } 123 | Text( 124 | text = messageContentString, 125 | style = MaterialTheme.typography.body2, 126 | color = Color.White 127 | ) 128 | } 129 | } else { 130 | Text( 131 | text = messageContentString, 132 | style = MaterialTheme.typography.body2, 133 | color = Color.White 134 | ) 135 | } 136 | } 137 | } 138 | 139 | @Composable 140 | private fun parseMessageContentToSpan( 141 | contentList: List 142 | ): AnnotatedString = buildAnnotatedString { 143 | for (content in contentList) { 144 | when (content) { 145 | is MessageContent.Text -> append(content.content) 146 | is MessageContent.Link -> { 147 | withStyle(SpanStyle(color = MaterialTheme.colors.blurple)) { 148 | append(content.target) 149 | } 150 | } 151 | is MessageContent.CodeBlock -> { 152 | withStyle( 153 | SpanStyle( 154 | color = Color.LightGray, 155 | background = Color.DarkGray 156 | ) 157 | ) { 158 | append(content.content) 159 | } 160 | } 161 | is MessageContent.Mention -> { 162 | withStyle( 163 | SpanStyle( 164 | color = MaterialTheme.colors.blurple, 165 | background = MaterialTheme.colors.blurpleBackground 166 | ) 167 | ) { 168 | append('@') 169 | append(content.userId) 170 | } 171 | } 172 | is MessageContent.Emoji -> append(content.content) 173 | is MessageContent.CustomEmoji -> { 174 | append(':') 175 | append(content.alt) 176 | append(':') 177 | } 178 | is MessageContent.Channel -> { 179 | withStyle( 180 | SpanStyle( 181 | color = MaterialTheme.colors.blurple, 182 | background = MaterialTheme.colors.blurpleBackground 183 | ) 184 | ) { 185 | append('#') 186 | append(content.channelId) 187 | } 188 | } 189 | is MessageContent.Strong -> { 190 | withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { 191 | append(parseMessageContentToSpan(content.content)) 192 | } 193 | } 194 | is MessageContent.Emphasis -> { 195 | withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { 196 | append(parseMessageContentToSpan(content.content)) 197 | } 198 | } 199 | is MessageContent.Stroke -> { 200 | withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) { 201 | append(parseMessageContentToSpan(content.content)) 202 | } 203 | } 204 | is MessageContent.Underlined -> { 205 | withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) { 206 | append(parseMessageContentToSpan(content.content)) 207 | } 208 | } 209 | is MessageContent.InlineCode -> { 210 | withStyle( 211 | SpanStyle( 212 | color = Color.LightGray, 213 | background = Color.DarkGray 214 | ) 215 | ) { 216 | append(content.content) 217 | } 218 | } 219 | else -> { 220 | //TODO 221 | } 222 | } 223 | } 224 | } 225 | 226 | private val Colors.blurple 227 | get() = 228 | Color(88, 101, 242) 229 | 230 | private val Colors.blurpleBackground 231 | get() = 232 | blurple.copy(alpha = 0.3f) 233 | 234 | } 235 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/facebook/react/CustomReactInstanceManager.java: -------------------------------------------------------------------------------- 1 | package com.facebook.react; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.util.Log; 6 | import android.view.View; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import com.facebook.react.bridge.JSBundleLoader; 11 | import com.facebook.react.bridge.JSIModulePackage; 12 | import com.facebook.react.bridge.JavaScriptExecutorFactory; 13 | import com.facebook.react.bridge.NativeModuleCallExceptionHandler; 14 | import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; 15 | import com.facebook.react.bridge.ReactContext; 16 | import com.facebook.react.bridge.ReadableArray; 17 | import com.facebook.react.common.LifecycleState; 18 | import com.facebook.react.devsupport.RedBoxHandler; 19 | import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; 20 | import com.facebook.react.devsupport.interfaces.DevOptionHandler; 21 | import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; 22 | import com.facebook.react.devsupport.interfaces.DevSupportManager; 23 | import com.facebook.react.devsupport.interfaces.ErrorCustomizer; 24 | import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; 25 | import com.facebook.react.devsupport.interfaces.StackFrame; 26 | import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; 27 | import com.facebook.react.modules.debug.interfaces.DeveloperSettings; 28 | import com.facebook.react.packagerconnection.RequestHandler; 29 | import com.facebook.react.uimanager.UIImplementationProvider; 30 | 31 | import java.io.File; 32 | import java.lang.reflect.Field; 33 | import java.lang.reflect.InvocationTargetException; 34 | import java.lang.reflect.Method; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | public class CustomReactInstanceManager extends ReactInstanceManager { 39 | public CustomReactInstanceManager(Context applicationContext, @Nullable Activity currentActivity, @Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler, JavaScriptExecutorFactory javaScriptExecutorFactory, @Nullable JSBundleLoader bundleLoader, @Nullable String jsMainModulePath, List packages, boolean useDeveloperSupport, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, @Nullable UIImplementationProvider mUIImplementationProvider, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler, @Nullable RedBoxHandler redBoxHandler, boolean lazyViewManagersEnabled, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes, int minTimeLeftInFrameForNonBatchedOperationMs, @Nullable JSIModulePackage jsiModulePackage, @Nullable Map customPackagerCommandHandlers) { 40 | super(applicationContext, currentActivity, defaultHardwareBackBtnHandler, javaScriptExecutorFactory, bundleLoader, jsMainModulePath, packages, useDeveloperSupport, bridgeIdleDebugListener, initialLifecycleState, mUIImplementationProvider, nativeModuleCallExceptionHandler, redBoxHandler, lazyViewManagersEnabled, devBundleDownloadListener, minNumShakes, minTimeLeftInFrameForNonBatchedOperationMs, jsiModulePackage, customPackagerCommandHandlers); 41 | 42 | try { 43 | Field field = ReactInstanceManager.class.getDeclaredField("mDevSupportManager"); 44 | field.setAccessible(true); 45 | field.set(this, new DevSupportManagerWrapper(getDevSupportManager())); 46 | } catch (NoSuchFieldException | IllegalAccessException e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | 51 | @Override 52 | public void createReactContextInBackground() { 53 | if (!hasStartedCreatingInitialContext()) { 54 | try { 55 | Field field = ReactInstanceManager.class.getDeclaredField("mHasStartedCreatingInitialContext"); 56 | field.setAccessible(true); 57 | field.set(this, true); 58 | } catch (NoSuchFieldException | IllegalAccessException e) { 59 | throw new RuntimeException(e); 60 | } 61 | 62 | this.recreateReactContextInBackground(); 63 | } 64 | } 65 | 66 | @Override 67 | public void recreateReactContextInBackground() { 68 | try { 69 | Method method = ReactInstanceManager.class.getDeclaredMethod("recreateReactContextInBackgroundFromBundleLoader"); 70 | method.setAccessible(true); 71 | method.invoke(this); 72 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 73 | throw new RuntimeException(e); 74 | } 75 | } 76 | 77 | public static class DevSupportManagerWrapper implements DevSupportManager { 78 | private static final String TAG = "DevSupportManager"; 79 | 80 | private final DevSupportManager wrapped; 81 | 82 | public DevSupportManagerWrapper(DevSupportManager wrapped) { 83 | this.wrapped = wrapped; 84 | } 85 | 86 | @Override 87 | public void showNewJavaError(String message, Throwable e) { 88 | wrapped.showNewJavaError(message, e); 89 | } 90 | 91 | @Override 92 | public void addCustomDevOption(String optionName, DevOptionHandler optionHandler) { 93 | wrapped.addCustomDevOption(optionName, optionHandler); 94 | } 95 | 96 | @Nullable 97 | @Override 98 | public View createRootView(String appKey) { 99 | return wrapped.createRootView(appKey); 100 | } 101 | 102 | @Override 103 | public void destroyRootView(View rootView) { 104 | wrapped.destroyRootView(rootView); 105 | } 106 | 107 | @Override 108 | public void showNewJSError(String message, ReadableArray details, int errorCookie) { 109 | wrapped.showNewJSError(message, details, errorCookie); 110 | } 111 | 112 | @Override 113 | public void updateJSError(String message, ReadableArray details, int errorCookie) { 114 | wrapped.updateJSError(message, details, errorCookie); 115 | } 116 | 117 | @Override 118 | public void hideRedboxDialog() { 119 | wrapped.hideRedboxDialog(); 120 | } 121 | 122 | @Override 123 | public void showDevOptionsDialog() { 124 | wrapped.showDevOptionsDialog(); 125 | } 126 | 127 | @Override 128 | public void setDevSupportEnabled(boolean isDevSupportEnabled) { 129 | wrapped.setDevSupportEnabled(isDevSupportEnabled); 130 | } 131 | 132 | @Override 133 | public void startInspector() { 134 | wrapped.startInspector(); 135 | } 136 | 137 | @Override 138 | public void stopInspector() { 139 | wrapped.stopInspector(); 140 | } 141 | 142 | @Override 143 | public boolean getDevSupportEnabled() { 144 | return wrapped.getDevSupportEnabled(); 145 | } 146 | 147 | @Override 148 | public DeveloperSettings getDevSettings() { 149 | return wrapped.getDevSettings(); 150 | } 151 | 152 | @Override 153 | public void onNewReactContextCreated(ReactContext reactContext) { 154 | wrapped.onNewReactContextCreated(reactContext); 155 | } 156 | 157 | @Override 158 | public void onReactInstanceDestroyed(ReactContext reactContext) { 159 | wrapped.onReactInstanceDestroyed(reactContext); 160 | } 161 | 162 | @Override 163 | public String getSourceMapUrl() { 164 | return wrapped.getSourceMapUrl(); 165 | } 166 | 167 | @Override 168 | public String getSourceUrl() { 169 | return wrapped.getSourceUrl(); 170 | } 171 | 172 | @Override 173 | public String getJSBundleURLForRemoteDebugging() { 174 | return wrapped.getJSBundleURLForRemoteDebugging(); 175 | } 176 | 177 | @Override 178 | public String getDownloadedJSBundleFile() { 179 | return wrapped.getDownloadedJSBundleFile(); 180 | } 181 | 182 | @Override 183 | public boolean hasUpToDateJSBundleInCache() { 184 | return wrapped.hasUpToDateJSBundleInCache(); 185 | } 186 | 187 | @Override 188 | public void reloadSettings() { 189 | wrapped.reloadSettings(); 190 | } 191 | 192 | @Override 193 | public void handleReloadJS() { 194 | Log.i(TAG, "ignored handleReloadJS"); 195 | // wrapped.handleReloadJS(); 196 | } 197 | 198 | @Override 199 | public void reloadJSFromServer(String bundleURL) { 200 | Log.i(TAG, "ignored reloadJSFromServer"); 201 | // wrapped.reloadJSFromServer(bundleURL); 202 | } 203 | 204 | @Override 205 | public void loadSplitBundleFromServer(String bundlePath, DevSplitBundleCallback callback) { 206 | wrapped.loadSplitBundleFromServer(bundlePath, callback); 207 | } 208 | 209 | @Override 210 | public void isPackagerRunning(PackagerStatusCallback callback) { 211 | wrapped.isPackagerRunning(callback); 212 | } 213 | 214 | @Override 215 | public void setHotModuleReplacementEnabled(boolean isHotModuleReplacementEnabled) { 216 | wrapped.setHotModuleReplacementEnabled(isHotModuleReplacementEnabled); 217 | } 218 | 219 | @Override 220 | public void setRemoteJSDebugEnabled(boolean isRemoteJSDebugEnabled) { 221 | wrapped.setRemoteJSDebugEnabled(isRemoteJSDebugEnabled); 222 | } 223 | 224 | @Override 225 | public void setFpsDebugEnabled(boolean isFpsDebugEnabled) { 226 | wrapped.setRemoteJSDebugEnabled(isFpsDebugEnabled); 227 | } 228 | 229 | @Override 230 | public void toggleElementInspector() { 231 | wrapped.toggleElementInspector(); 232 | } 233 | 234 | @Nullable 235 | @Override 236 | public File downloadBundleResourceFromUrlSync(String resourceURL, File outputFile) { 237 | return wrapped.downloadBundleResourceFromUrlSync(resourceURL, outputFile); 238 | } 239 | 240 | @Nullable 241 | @Override 242 | public String getLastErrorTitle() { 243 | return wrapped.getLastErrorTitle(); 244 | } 245 | 246 | @Nullable 247 | @Override 248 | public StackFrame[] getLastErrorStack() { 249 | return wrapped.getLastErrorStack(); 250 | } 251 | 252 | @Override 253 | public void registerErrorCustomizer(ErrorCustomizer errorCustomizer) { 254 | wrapped.registerErrorCustomizer(errorCustomizer); 255 | } 256 | 257 | @Override 258 | public void setPackagerLocationCustomizer(PackagerLocationCustomizer packagerLocationCustomizer) { 259 | wrapped.setPackagerLocationCustomizer(packagerLocationCustomizer); 260 | } 261 | 262 | @Override 263 | public void handleException(Exception e) { 264 | wrapped.handleException(e); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /preload/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | esbuild: ^0.14.10 5 | react-devtools-core: ^4.22.1 6 | rollup: ^2.63.0 7 | rollup-plugin-esbuild: ^4.8.2 8 | rollup-plugin-string: ^3.0.0 9 | 10 | dependencies: 11 | react-devtools-core: 4.22.1 12 | 13 | devDependencies: 14 | esbuild: 0.14.11 15 | rollup: 2.64.0 16 | rollup-plugin-esbuild: 4.8.2_esbuild@0.14.11+rollup@2.64.0 17 | rollup-plugin-string: 3.0.0 18 | 19 | packages: 20 | 21 | /@rollup/pluginutils/4.1.2: 22 | resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==} 23 | engines: {node: '>= 8.0.0'} 24 | dependencies: 25 | estree-walker: 2.0.2 26 | picomatch: 2.3.1 27 | dev: true 28 | 29 | /debug/4.3.3: 30 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} 31 | engines: {node: '>=6.0'} 32 | peerDependencies: 33 | supports-color: '*' 34 | peerDependenciesMeta: 35 | supports-color: 36 | optional: true 37 | dependencies: 38 | ms: 2.1.2 39 | dev: true 40 | 41 | /es-module-lexer/0.9.3: 42 | resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} 43 | dev: true 44 | 45 | /esbuild-android-arm64/0.14.11: 46 | resolution: {integrity: sha512-6iHjgvMnC/SzDH8TefL+/3lgCjYWwAd1LixYfmz/TBPbDQlxcuSkX0yiQgcJB9k+ibZ54yjVXziIwGdlc+6WNw==} 47 | cpu: [arm64] 48 | os: [android] 49 | requiresBuild: true 50 | dev: true 51 | optional: true 52 | 53 | /esbuild-darwin-64/0.14.11: 54 | resolution: {integrity: sha512-olq84ikh6TiBcrs3FnM4eR5VPPlcJcdW8BnUz/lNoEWYifYQ+Po5DuYV1oz1CTFMw4k6bQIZl8T3yxL+ZT2uvQ==} 55 | cpu: [x64] 56 | os: [darwin] 57 | requiresBuild: true 58 | dev: true 59 | optional: true 60 | 61 | /esbuild-darwin-arm64/0.14.11: 62 | resolution: {integrity: sha512-Jj0ieWLREPBYr/TZJrb2GFH8PVzDqiQWavo1pOFFShrcmHWDBDrlDxPzEZ67NF/Un3t6sNNmeI1TUS/fe1xARg==} 63 | cpu: [arm64] 64 | os: [darwin] 65 | requiresBuild: true 66 | dev: true 67 | optional: true 68 | 69 | /esbuild-freebsd-64/0.14.11: 70 | resolution: {integrity: sha512-C5sT3/XIztxxz/zwDjPRHyzj/NJFOnakAanXuyfLDwhwupKPd76/PPHHyJx6Po6NI6PomgVp/zi6GRB8PfrOTA==} 71 | cpu: [x64] 72 | os: [freebsd] 73 | requiresBuild: true 74 | dev: true 75 | optional: true 76 | 77 | /esbuild-freebsd-arm64/0.14.11: 78 | resolution: {integrity: sha512-y3Llu4wbs0bk4cwjsdAtVOesXb6JkdfZDLKMt+v1U3tOEPBdSu6w8796VTksJgPfqvpX22JmPLClls0h5p+L9w==} 79 | cpu: [arm64] 80 | os: [freebsd] 81 | requiresBuild: true 82 | dev: true 83 | optional: true 84 | 85 | /esbuild-linux-32/0.14.11: 86 | resolution: {integrity: sha512-Cg3nVsxArjyLke9EuwictFF3Sva+UlDTwHIuIyx8qpxRYAOUTmxr2LzYrhHyTcGOleLGXUXYsnUVwKqnKAgkcg==} 87 | cpu: [ia32] 88 | os: [linux] 89 | requiresBuild: true 90 | dev: true 91 | optional: true 92 | 93 | /esbuild-linux-64/0.14.11: 94 | resolution: {integrity: sha512-oeR6dIrrojr8DKVrxtH3xl4eencmjsgI6kPkDCRIIFwv4p+K7ySviM85K66BN01oLjzthpUMvBVfWSJkBLeRbg==} 95 | cpu: [x64] 96 | os: [linux] 97 | requiresBuild: true 98 | dev: true 99 | optional: true 100 | 101 | /esbuild-linux-arm/0.14.11: 102 | resolution: {integrity: sha512-vcwskfD9g0tojux/ZaTJptJQU3a7YgTYsptK1y6LQ/rJmw7U5QJvboNawqM98Ca3ToYEucfCRGbl66OTNtp6KQ==} 103 | cpu: [arm] 104 | os: [linux] 105 | requiresBuild: true 106 | dev: true 107 | optional: true 108 | 109 | /esbuild-linux-arm64/0.14.11: 110 | resolution: {integrity: sha512-+e6ZCgTFQYZlmg2OqLkg1jHLYtkNDksxWDBWNtI4XG4WxuOCUErLqfEt9qWjvzK3XBcCzHImrajkUjO+rRkbMg==} 111 | cpu: [arm64] 112 | os: [linux] 113 | requiresBuild: true 114 | dev: true 115 | optional: true 116 | 117 | /esbuild-linux-mips64le/0.14.11: 118 | resolution: {integrity: sha512-Rrs99L+p54vepmXIb87xTG6ukrQv+CzrM8eoeR+r/OFL2Rg8RlyEtCeshXJ2+Q66MXZOgPJaokXJZb9snq28bw==} 119 | cpu: [mips64el] 120 | os: [linux] 121 | requiresBuild: true 122 | dev: true 123 | optional: true 124 | 125 | /esbuild-linux-ppc64le/0.14.11: 126 | resolution: {integrity: sha512-JyzziGAI0D30Vyzt0HDihp4s1IUtJ3ssV2zx9O/c+U/dhUHVP2TmlYjzCfCr2Q6mwXTeloDcLS4qkyvJtYptdQ==} 127 | cpu: [ppc64] 128 | os: [linux] 129 | requiresBuild: true 130 | dev: true 131 | optional: true 132 | 133 | /esbuild-linux-s390x/0.14.11: 134 | resolution: {integrity: sha512-DoThrkzunZ1nfRGoDN6REwmo8ZZWHd2ztniPVIR5RMw/Il9wiWEYBahb8jnMzQaSOxBsGp0PbyJeVLTUatnlcw==} 135 | cpu: [s390x] 136 | os: [linux] 137 | requiresBuild: true 138 | dev: true 139 | optional: true 140 | 141 | /esbuild-netbsd-64/0.14.11: 142 | resolution: {integrity: sha512-12luoRQz+6eihKYh1zjrw0CBa2aw3twIiHV/FAfjh2NEBDgJQOY4WCEUEN+Rgon7xmLh4XUxCQjnwrvf8zhACw==} 143 | cpu: [x64] 144 | os: [netbsd] 145 | requiresBuild: true 146 | dev: true 147 | optional: true 148 | 149 | /esbuild-openbsd-64/0.14.11: 150 | resolution: {integrity: sha512-l18TZDjmvwW6cDeR4fmizNoxndyDHamGOOAenwI4SOJbzlJmwfr0jUgjbaXCUuYVOA964siw+Ix+A+bhALWg8Q==} 151 | cpu: [x64] 152 | os: [openbsd] 153 | requiresBuild: true 154 | dev: true 155 | optional: true 156 | 157 | /esbuild-sunos-64/0.14.11: 158 | resolution: {integrity: sha512-bmYzDtwASBB8c+0/HVOAiE9diR7+8zLm/i3kEojUH2z0aIs6x/S4KiTuT5/0VKJ4zk69kXel1cNWlHBMkmavQg==} 159 | cpu: [x64] 160 | os: [sunos] 161 | requiresBuild: true 162 | dev: true 163 | optional: true 164 | 165 | /esbuild-windows-32/0.14.11: 166 | resolution: {integrity: sha512-J1Ys5hMid8QgdY00OBvIolXgCQn1ARhYtxPnG6ESWNTty3ashtc4+As5nTrsErnv8ZGUcWZe4WzTP/DmEVX1UQ==} 167 | cpu: [ia32] 168 | os: [win32] 169 | requiresBuild: true 170 | dev: true 171 | optional: true 172 | 173 | /esbuild-windows-64/0.14.11: 174 | resolution: {integrity: sha512-h9FmMskMuGeN/9G9+LlHPAoiQk9jlKDUn9yA0MpiGzwLa82E7r1b1u+h2a+InprbSnSLxDq/7p5YGtYVO85Mlg==} 175 | cpu: [x64] 176 | os: [win32] 177 | requiresBuild: true 178 | dev: true 179 | optional: true 180 | 181 | /esbuild-windows-arm64/0.14.11: 182 | resolution: {integrity: sha512-dZp7Krv13KpwKklt9/1vBFBMqxEQIO6ri7Azf8C+ob4zOegpJmha2XY9VVWP/OyQ0OWk6cEeIzMJwInRZrzBUQ==} 183 | cpu: [arm64] 184 | os: [win32] 185 | requiresBuild: true 186 | dev: true 187 | optional: true 188 | 189 | /esbuild/0.14.11: 190 | resolution: {integrity: sha512-xZvPtVj6yecnDeFb3KjjCM6i7B5TCAQZT77kkW/CpXTMnd6VLnRPKrUB1XHI1pSq6a4Zcy3BGueQ8VljqjDGCg==} 191 | hasBin: true 192 | requiresBuild: true 193 | optionalDependencies: 194 | esbuild-android-arm64: 0.14.11 195 | esbuild-darwin-64: 0.14.11 196 | esbuild-darwin-arm64: 0.14.11 197 | esbuild-freebsd-64: 0.14.11 198 | esbuild-freebsd-arm64: 0.14.11 199 | esbuild-linux-32: 0.14.11 200 | esbuild-linux-64: 0.14.11 201 | esbuild-linux-arm: 0.14.11 202 | esbuild-linux-arm64: 0.14.11 203 | esbuild-linux-mips64le: 0.14.11 204 | esbuild-linux-ppc64le: 0.14.11 205 | esbuild-linux-s390x: 0.14.11 206 | esbuild-netbsd-64: 0.14.11 207 | esbuild-openbsd-64: 0.14.11 208 | esbuild-sunos-64: 0.14.11 209 | esbuild-windows-32: 0.14.11 210 | esbuild-windows-64: 0.14.11 211 | esbuild-windows-arm64: 0.14.11 212 | dev: true 213 | 214 | /estree-walker/0.6.1: 215 | resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} 216 | dev: true 217 | 218 | /estree-walker/2.0.2: 219 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 220 | dev: true 221 | 222 | /fsevents/2.3.2: 223 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 224 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 225 | os: [darwin] 226 | requiresBuild: true 227 | dev: true 228 | optional: true 229 | 230 | /joycon/3.1.1: 231 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 232 | engines: {node: '>=10'} 233 | dev: true 234 | 235 | /jsonc-parser/3.0.0: 236 | resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==} 237 | dev: true 238 | 239 | /ms/2.1.2: 240 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 241 | dev: true 242 | 243 | /picomatch/2.3.1: 244 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 245 | engines: {node: '>=8.6'} 246 | dev: true 247 | 248 | /react-devtools-core/4.22.1: 249 | resolution: {integrity: sha512-pvpNDHE7p0FtcCmIWGazoY8LLVfBI9sw0Kf10kdHhPI9Tzt3OG/qEt16GrAbE0keuna5WzX3r1qPKVjqOqsuUg==} 250 | dependencies: 251 | shell-quote: 1.7.3 252 | ws: 7.5.6 253 | transitivePeerDependencies: 254 | - bufferutil 255 | - utf-8-validate 256 | dev: false 257 | 258 | /rollup-plugin-esbuild/4.8.2_esbuild@0.14.11+rollup@2.64.0: 259 | resolution: {integrity: sha512-wsaYNOjzTb6dN1qCIZsMZ7Q0LWiPJklYs2TDI8vJA2LUbvtPUY+17TC8C0vSat3jPMInfR9XWKdA7ttuwkjsGQ==} 260 | engines: {node: '>=12'} 261 | peerDependencies: 262 | esbuild: '>=0.10.1' 263 | rollup: ^1.20.0 || ^2.0.0 264 | dependencies: 265 | '@rollup/pluginutils': 4.1.2 266 | debug: 4.3.3 267 | es-module-lexer: 0.9.3 268 | esbuild: 0.14.11 269 | joycon: 3.1.1 270 | jsonc-parser: 3.0.0 271 | rollup: 2.64.0 272 | transitivePeerDependencies: 273 | - supports-color 274 | dev: true 275 | 276 | /rollup-plugin-string/3.0.0: 277 | resolution: {integrity: sha512-vqyzgn9QefAgeKi+Y4A7jETeIAU1zQmS6VotH6bzm/zmUQEnYkpIGRaOBPY41oiWYV4JyBoGAaBjYMYuv+6wVw==} 278 | dependencies: 279 | rollup-pluginutils: 2.8.2 280 | dev: true 281 | 282 | /rollup-pluginutils/2.8.2: 283 | resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} 284 | dependencies: 285 | estree-walker: 0.6.1 286 | dev: true 287 | 288 | /rollup/2.64.0: 289 | resolution: {integrity: sha512-+c+lbw1lexBKSMb1yxGDVfJ+vchJH3qLbmavR+awDinTDA2C5Ug9u7lkOzj62SCu0PKUExsW36tpgW7Fmpn3yQ==} 290 | engines: {node: '>=10.0.0'} 291 | hasBin: true 292 | optionalDependencies: 293 | fsevents: 2.3.2 294 | dev: true 295 | 296 | /shell-quote/1.7.3: 297 | resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} 298 | dev: false 299 | 300 | /ws/7.5.6: 301 | resolution: {integrity: sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==} 302 | engines: {node: '>=8.3.0'} 303 | peerDependencies: 304 | bufferutil: ^4.0.1 305 | utf-8-validate: ^5.0.2 306 | peerDependenciesMeta: 307 | bufferutil: 308 | optional: true 309 | utf-8-validate: 310 | optional: true 311 | dev: false 312 | -------------------------------------------------------------------------------- /preload/src/driver.js: -------------------------------------------------------------------------------- 1 | // https://github.com/aveq-research/localforage-asyncstorage-driver/blob/master/src/main.js 2 | 3 | /** 4 | * This implementation adds localforage support for react-native async storage, 5 | * as documented in: https://facebook.github.io/react-native/docs/asyncstorage 6 | * 7 | * The implementation itself is based on the already existing localstorage 8 | * support implementation. A future work could try to unite similar code 9 | * structures of both implementations. 10 | */ 11 | 12 | export let AsyncStorage; 13 | 14 | async function _withCallback(callback, promise) { 15 | try { 16 | const ret = await promise(); 17 | 18 | if (callback) { 19 | callback(null, ret); 20 | } 21 | 22 | return ret; 23 | } 24 | catch (e) { 25 | if (callback) { 26 | callback(e); 27 | } 28 | 29 | throw e; 30 | } 31 | } 32 | 33 | function _getKeyPrefix(options, defaultConfig) { 34 | let keyPrefix = `${options.name}/`; 35 | if (options.storeName && options.storeName !== defaultConfig.storeName) { 36 | keyPrefix += `${options.storeName}/`; 37 | } 38 | 39 | return keyPrefix; 40 | } 41 | 42 | async function _iterate(dbInfo, keys, iterator, iterationNumber) { 43 | const key = keys.shift(); 44 | if (key === undefined) { 45 | return; 46 | } 47 | 48 | const keyPrefix = dbInfo.keyPrefix; 49 | if (key.indexOf(keyPrefix) === 0) { 50 | const serializedValue = await AsyncStorage.getItem(key); 51 | 52 | // If a result was found, parse it from the serialized 53 | // string into a JS object. If result isn't truthy, the 54 | // key is likely undefined and we'll pass it straight 55 | // to the iterator. 56 | const value = serializedValue && dbInfo.serializer 57 | ? dbInfo.serializer.deserialize(serializedValue) 58 | : serializedValue; 59 | 60 | const itVal = iterator( 61 | value, 62 | key.substring(keyPrefix.length), 63 | iterationNumber++ 64 | ); 65 | 66 | if (itVal !== undefined) { 67 | return itVal; 68 | } 69 | } 70 | 71 | return _iterate(dbInfo, keys, iterator, iterationNumber); 72 | } 73 | 74 | const defaultDriver = { 75 | 76 | /** 77 | * The name of this driver! 78 | */ 79 | _driver: 'rnAsyncStorageWrapper', 80 | 81 | /** 82 | * Returns true if the driver can support the React Native Async Storage on the executed platform, false otherwise. 83 | * @returns {boolean} 84 | * @private 85 | */ 86 | // eslint-disable-next-line require-await 87 | _support: async function () { 88 | try { 89 | return ( 90 | typeof AsyncStorage !== 'undefined' && 91 | 'setItem' in AsyncStorage && 92 | !!AsyncStorage.setItem 93 | ); 94 | } 95 | catch (e) { 96 | return false; 97 | } 98 | }, 99 | 100 | /** 101 | * Config the localStorage backend, using options set in the config. 102 | * @param serializer 103 | * @param options 104 | * @returns {Promise} 105 | * @private 106 | */ 107 | _initStorage: async function (serializer, options = {}) { 108 | const dbInfo = Object.assign({}, options); 109 | dbInfo.keyPrefix = _getKeyPrefix(options, this._defaultConfig); 110 | 111 | const localStorageTestKey = '_localforage_support_test'; 112 | await AsyncStorage.setItem(localStorageTestKey, 'it-works'); 113 | await AsyncStorage.removeItem(localStorageTestKey); 114 | // if these operations throw, the storage initialization fails, because something is wrong with the storage! 115 | 116 | if (serializer === undefined) { 117 | serializer = await this.getSerializer(); 118 | } 119 | 120 | if (serializer) { 121 | dbInfo.serializer = serializer; 122 | } 123 | 124 | this._dbInfo = dbInfo; 125 | return Promise.resolve(); 126 | }, 127 | 128 | /** 129 | * Iterate over all items in the store. 130 | * 131 | * @param iterator the method to execute on each item individually! 132 | * @param callback 133 | * @returns {Promise} 134 | */ 135 | iterate: function (iterator, callback) { 136 | return _withCallback(callback, async () => { 137 | await this.ready(); 138 | 139 | const allKeys = await AsyncStorage.getAllKeys(); 140 | return _iterate(this._dbInfo, allKeys, iterator, 0); 141 | }); 142 | }, 143 | 144 | /** 145 | * Retrieve an item from the store. Unlike the original async_storage 146 | * library in Gaia, we don't modify return values at all. If a key's value 147 | * is `undefined`, we pass that value to the callback function. 148 | * 149 | * @param key 150 | * @param callback 151 | * @returns {Promise} 152 | */ 153 | getItem: function (key, callback) { 154 | return _withCallback(callback, async () => { 155 | key = String(key); 156 | await this.ready(); 157 | const dbInfo = this._dbInfo; 158 | const item = await AsyncStorage.getItem(`${dbInfo.keyPrefix}${key}`); 159 | 160 | return (dbInfo.serializer && item) ? dbInfo.serializer.deserialize(item) : item; 161 | }); 162 | }, 163 | 164 | /** 165 | * Set a key's value and run an optional callback once the value is set. 166 | * Unlike Gaia's implementation, the callback function is passed the value, 167 | * in case you want to operate on that value only after you're sure it 168 | * saved, or something like that. 169 | * 170 | * @param key 171 | * @param value 172 | * @param callback 173 | * @returns {Promise} 174 | */ 175 | setItem: function (key, value, callback) { 176 | return _withCallback(callback, async () => { 177 | key = String(key); 178 | await this.ready(); 179 | // Convert undefined values to null. 180 | // https://github.com/mozilla/localForage/pull/42 181 | if (value === undefined) { 182 | value = null; 183 | } 184 | 185 | // Save the original value to pass to the callback. 186 | const originalValue = value; //_copyValue(value); 187 | const dbInfo = this._dbInfo; 188 | 189 | return new Promise((resolve, reject) => { 190 | async function writeToStorage(valueToWrite) { 191 | await AsyncStorage.setItem(`${dbInfo.keyPrefix}${key}`, valueToWrite); 192 | return originalValue; 193 | } 194 | 195 | if (dbInfo.serializer) { 196 | dbInfo.serializer.serialize(value, (serializedValue, error) => { 197 | if (error) { 198 | reject(error); 199 | return; 200 | } 201 | 202 | writeToStorage(serializedValue) 203 | .then(resolve) 204 | .catch(reject); 205 | }); 206 | } 207 | else { 208 | writeToStorage(value) 209 | .then(resolve) 210 | .catch(reject); 211 | } 212 | }); 213 | }); 214 | }, 215 | 216 | /** 217 | * Remove an item from the store, nice and simple. 218 | * @param key 219 | * @param callback 220 | * @returns {Promise} 221 | */ 222 | removeItem: function (key, callback) { 223 | return _withCallback(callback, async () => { 224 | key = String(key); 225 | await this.ready(); 226 | await AsyncStorage.removeItem(`${this._dbInfo.keyPrefix}${key}`); 227 | }); 228 | }, 229 | 230 | /** 231 | * Remove all keys from the datastore, effectively destroying all data in 232 | * the app's key/value store! 233 | * 234 | * @param callback 235 | * @returns {Promise} 236 | */ 237 | clear: function (callback) { 238 | return _withCallback(callback, async () => { 239 | await this.ready(); 240 | const keyPrefix = this._dbInfo.keyPrefix; 241 | const keysToDelete = []; 242 | const allKeys = await AsyncStorage.getAllKeys(); 243 | 244 | for (const key of allKeys) { 245 | if (key.indexOf(keyPrefix) === 0) { 246 | keysToDelete.push(key); 247 | } 248 | } 249 | 250 | await AsyncStorage.multiRemove(keysToDelete); 251 | }); 252 | }, 253 | 254 | /** 255 | * Supply the number of keys in the datastore to the callback function. 256 | * 257 | * @param callback 258 | * @returns {PromiseLike | Promise} 259 | */ 260 | length: function (callback) { 261 | return _withCallback(callback, async () => { 262 | const keys = await this.keys(); 263 | return keys.length; 264 | }); 265 | }, 266 | 267 | /** 268 | * Same as localStorage's key() method, except takes a callback. 269 | * @returns {Promise<*>} 270 | */ 271 | key: function (n, callback) { 272 | return _withCallback(callback, async () => { 273 | await this.ready(); 274 | const dbInfo = this._dbInfo; 275 | const allKeys = await AsyncStorage.getAllKeys(); 276 | const key = allKeys[n]; 277 | 278 | return key ? key.substring(dbInfo.keyPrefix.length) : null; 279 | }); 280 | }, 281 | 282 | keys: function (callback) { 283 | return _withCallback(callback, async () => { 284 | await this.ready(); 285 | 286 | const dbInfo = this._dbInfo; 287 | const allKeys = await AsyncStorage.getAllKeys(); 288 | const driverKeys = []; 289 | 290 | for (const key of allKeys) { 291 | if (key.indexOf(dbInfo.keyPrefix) === 0) { 292 | driverKeys.push(key.substring(dbInfo.keyPrefix.length)); 293 | } 294 | } 295 | 296 | return driverKeys; 297 | }); 298 | }, 299 | 300 | dropInstance: function (options = {}, callback) { 301 | return _withCallback(callback, async () => { 302 | const currentConfig = this.config(); 303 | options.name = options.name || currentConfig.name; 304 | options.storeName = options.storeName || currentConfig.storeName; 305 | 306 | if (options.name === undefined) { 307 | throw Error('Invalid arguments'); 308 | } 309 | 310 | const keyPrefix = _getKeyPrefix(options, this._defaultConfig); 311 | const keys = await this.keys(); 312 | const keysToDelete = keys.map(k => `${keyPrefix}${k}`); 313 | 314 | return AsyncStorage.multiRemove(keysToDelete); 315 | }); 316 | } 317 | 318 | }; 319 | 320 | export function driverWithSerialization(serializer) { 321 | const driver = Object.assign({}, defaultDriver, { 322 | _driver: `${defaultDriver._driver}-with${serializer === undefined ? 'DefaultSerializer' : (serializer === null ? 'outSerializer' : 'Serializer')}`, 323 | }); 324 | 325 | const orgInitFunctionBody = driver._initStorage; 326 | driver._initStorage = function (options) { 327 | return orgInitFunctionBody.apply(this, [serializer, options]); 328 | }; 329 | 330 | return driver; 331 | } 332 | 333 | export function driverWithoutSerialization() { 334 | return driverWithSerialization(null); 335 | } 336 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/discordrn/modules/RNSentryModule.java: -------------------------------------------------------------------------------- 1 | package com.discordrn.modules; 2 | import android.content.Context; 3 | import android.content.pm.PackageInfo; 4 | import com.facebook.react.bridge.Arguments; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 7 | import com.facebook.react.bridge.ReactMethod; 8 | import com.facebook.react.bridge.WritableMap; 9 | import com.facebook.react.bridge.ReadableMap; 10 | import com.facebook.react.bridge.Promise; 11 | import com.facebook.react.module.annotations.ReactModule; 12 | import java.util.Map; 13 | import java.util.HashMap; 14 | import java.util.logging.Logger; 15 | import java.io.UnsupportedEncodingException; 16 | 17 | @ReactModule(name = RNSentryModule.NAME) 18 | public class RNSentryModule extends ReactContextBaseJavaModule { 19 | 20 | public static final String NAME = "RNSentry"; 21 | 22 | private static final Logger logger = Logger.getLogger("react-native-sentry"); 23 | 24 | private PackageInfo packageInfo = null; 25 | private boolean didFetchAppStart; 26 | private Object frameMetricsAggregator = null; 27 | private boolean androidXAvailable = true; 28 | 29 | // 700ms to constitute frozen frames. 30 | private static final int FROZEN_FRAME_THRESHOLD = 700; 31 | // 16ms (slower than 60fps) to constitute slow frames. 32 | private static final int SLOW_FRAME_THRESHOLD = 16; 33 | 34 | public RNSentryModule(ReactApplicationContext reactContext) { 35 | super(reactContext); 36 | packageInfo = getPackageInfo(reactContext); 37 | } 38 | 39 | @Override 40 | public String getName() { 41 | return NAME; 42 | } 43 | 44 | @Override 45 | public Map getConstants() { 46 | final Map constants = new HashMap<>(); 47 | constants.put("nativeClientAvailable", true); 48 | constants.put("nativeTransport", true); 49 | return constants; 50 | } 51 | 52 | @ReactMethod 53 | public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { 54 | // SentryAndroid.init(this.getReactApplicationContext(), options -> { 55 | // if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { 56 | // options.setDebug(true); 57 | // logger.setLevel(Level.INFO); 58 | // } 59 | // if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) { 60 | // String dsn = rnOptions.getString("dsn"); 61 | // logger.info(String.format("Starting with DSN: '%s'", dsn)); 62 | // options.setDsn(dsn); 63 | // } else { 64 | // // SentryAndroid needs an empty string fallback for the dsn. 65 | // options.setDsn(""); 66 | // } 67 | // if (rnOptions.hasKey("maxBreadcrumbs")) { 68 | // options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs")); 69 | // } 70 | // if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) { 71 | // options.setEnvironment(rnOptions.getString("environment")); 72 | // } 73 | // if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) { 74 | // options.setRelease(rnOptions.getString("release")); 75 | // } 76 | // if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) { 77 | // options.setDist(rnOptions.getString("dist")); 78 | // } 79 | // if (rnOptions.hasKey("enableAutoSessionTracking")) { 80 | // options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking")); 81 | // } 82 | // if (rnOptions.hasKey("sessionTrackingIntervalMillis")) { 83 | // options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis")); 84 | // } 85 | // if (rnOptions.hasKey("enableNdkScopeSync")) { 86 | // options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync")); 87 | // } 88 | // if (rnOptions.hasKey("attachStacktrace")) { 89 | // options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace")); 90 | // } 91 | // if (rnOptions.hasKey("attachThreads")) { 92 | // // JS use top level stacktraces and android attaches Threads which hides them so 93 | // // by default we hide. 94 | // options.setAttachThreads(rnOptions.getBoolean("attachThreads")); 95 | // } 96 | // if (rnOptions.hasKey("sendDefaultPii")) { 97 | // options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii")); 98 | // } 99 | // if (rnOptions.hasKey("enableAutoPerformanceTracking") 100 | // && rnOptions.getBoolean("enableAutoPerformanceTracking")) { 101 | // androidXAvailable = checkAndroidXAvailability(); 102 | 103 | // if (androidXAvailable) { 104 | // frameMetricsAggregator = new FrameMetricsAggregator(); 105 | // final Activity currentActivity = getCurrentActivity(); 106 | 107 | // if (frameMetricsAggregator != null && currentActivity != null) { 108 | // try { 109 | // frameMetricsAggregator.add(currentActivity); 110 | // } catch (Throwable ignored) { 111 | // // throws ConcurrentModification when calling addOnFrameMetricsAvailableListener 112 | // // this is a best effort since we can't reproduce it 113 | // logger.warning("Error adding Activity to frameMetricsAggregator."); 114 | // } 115 | // } 116 | // } else { 117 | // logger.warning("androidx.core' isn't available as a dependency."); 118 | // } 119 | // } else { 120 | // this.disableNativeFramesTracking(); 121 | // } 122 | 123 | // options.setBeforeSend((event, hint) -> { 124 | // // React native internally throws a JavascriptException 125 | // // Since we catch it before that, we don't want to send this one 126 | // // because we would send it twice 127 | // try { 128 | // SentryException ex = event.getExceptions().get(0); 129 | // if (null != ex && ex.getType().contains("JavascriptException")) { 130 | // return null; 131 | // } 132 | // } catch (Throwable ignored) { 133 | // // We do nothing 134 | // } 135 | 136 | // setEventOriginTag(event); 137 | // addPackages(event, options.getSdkVersion()); 138 | 139 | // return event; 140 | // }); 141 | 142 | // if (rnOptions.hasKey("enableNativeCrashHandling") && !rnOptions.getBoolean("enableNativeCrashHandling")) { 143 | // final List integrations = options.getIntegrations(); 144 | // for (final Integration integration : integrations) { 145 | // if (integration instanceof UncaughtExceptionHandlerIntegration 146 | // || integration instanceof AnrIntegration || integration instanceof NdkIntegration) { 147 | // integrations.remove(integration); 148 | // } 149 | // } 150 | // } 151 | 152 | // logger.info(String.format("Native Integrations '%s'", options.getIntegrations().toString())); 153 | // }); 154 | 155 | promise.resolve(true); 156 | } 157 | 158 | @ReactMethod 159 | public void crash() { 160 | throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)"); 161 | } 162 | 163 | @ReactMethod 164 | public void fetchNativeRelease(Promise promise) { 165 | WritableMap release = Arguments.createMap(); 166 | // release.putString("id", packageInfo.packageName); 167 | // release.putString("version", packageInfo.versionName); 168 | // release.putString("build", String.valueOf(packageInfo.versionCode)); 169 | release.putString("id", "com.hammerandchisel.discord"); 170 | release.putString("version", ""); 171 | release.putString("build", ""); 172 | promise.resolve(release); 173 | } 174 | 175 | @ReactMethod 176 | public void fetchNativeAppStart(Promise promise) { 177 | // final AppStartState appStartInstance = AppStartState.getInstance(); 178 | // final Date appStartTime = appStartInstance.getAppStartTime(); 179 | // final Boolean isColdStart = appStartInstance.isColdStart(); 180 | 181 | // if (appStartTime == null) { 182 | // logger.warning("App start won't be sent due to missing appStartTime."); 183 | // promise.resolve(null); 184 | // } else if (isColdStart == null) { 185 | // logger.warning("App start won't be sent due to missing isColdStart."); 186 | // promise.resolve(null); 187 | // } else { 188 | // final double appStartTimestamp = (double) appStartTime.getTime(); 189 | 190 | // WritableMap appStart = Arguments.createMap(); 191 | 192 | // appStart.putDouble("appStartTime", appStartTimestamp); 193 | // appStart.putBoolean("isColdStart", isColdStart); 194 | // appStart.putBoolean("didFetchAppStart", didFetchAppStart); 195 | 196 | // promise.resolve(appStart); 197 | // } 198 | // // This is always set to true, as we would only allow an app start fetch to only 199 | // // happen once in the case of a JS bundle reload, we do not want it to be 200 | // // instrumented again. 201 | didFetchAppStart = true; 202 | WritableMap appStart = Arguments.createMap(); 203 | 204 | appStart.putDouble("appStartTime", 0.0); 205 | appStart.putBoolean("isColdStart", true); 206 | appStart.putBoolean("didFetchAppStart", true); 207 | 208 | promise.resolve(appStart); 209 | } 210 | 211 | /** 212 | * Returns frames metrics at the current point in time. 213 | */ 214 | @ReactMethod 215 | public void fetchNativeFrames(Promise promise) { 216 | promise.resolve(null); 217 | // if (!isFrameMetricsAggregatorAvailable()) { 218 | // promise.resolve(null); 219 | // } else { 220 | // try { 221 | // int totalFrames = 0; 222 | // int slowFrames = 0; 223 | // int frozenFrames = 0; 224 | 225 | // final SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics(); 226 | 227 | // if (framesRates != null) { 228 | // final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX]; 229 | // if (totalIndexArray != null) { 230 | // for (int i = 0; i < totalIndexArray.size(); i++) { 231 | // int frameTime = totalIndexArray.keyAt(i); 232 | // int numFrames = totalIndexArray.valueAt(i); 233 | // totalFrames += numFrames; 234 | // // hard coded values, its also in the official android docs and frame metrics 235 | // // API 236 | // if (frameTime > FROZEN_FRAME_THRESHOLD) { 237 | // // frozen frames, threshold is 700ms 238 | // frozenFrames += numFrames; 239 | // } else if (frameTime > SLOW_FRAME_THRESHOLD) { 240 | // // slow frames, above 16ms, 60 frames/second 241 | // slowFrames += numFrames; 242 | // } 243 | // } 244 | // } 245 | // } 246 | 247 | // WritableMap map = Arguments.createMap(); 248 | // map.putInt("totalFrames", totalFrames); 249 | // map.putInt("slowFrames", slowFrames); 250 | // map.putInt("frozenFrames", frozenFrames); 251 | 252 | // promise.resolve(map); 253 | // } catch (Throwable ignored) { 254 | // logger.warning("Error fetching native frames."); 255 | // promise.resolve(null); 256 | // } 257 | // } 258 | } 259 | 260 | @ReactMethod 261 | public void captureEnvelope(String envelope, Promise promise) { 262 | // try { 263 | // final String outboxPath = HubAdapter.getInstance().getOptions().getOutboxPath(); 264 | 265 | // if (outboxPath == null) { 266 | // logger.severe( 267 | // "Error retrieving outboxPath. Envelope will not be sent. Is the Android SDK initialized?"); 268 | // } else { 269 | // File installation = new File(outboxPath, UUID.randomUUID().toString()); 270 | // try (FileOutputStream out = new FileOutputStream(installation)) { 271 | // out.write(envelope.getBytes(Charset.forName("UTF-8"))); 272 | // } 273 | // } 274 | // } catch (Throwable ignored) { 275 | // logger.severe("Error reading envelope"); 276 | // } 277 | promise.resolve(true); 278 | } 279 | 280 | @ReactMethod 281 | public void getStringBytesLength(String payload, Promise promise) { 282 | try { 283 | promise.resolve(payload.getBytes("UTF-8").length); 284 | } catch (UnsupportedEncodingException e) { 285 | promise.reject(e); 286 | } 287 | } 288 | 289 | private static PackageInfo getPackageInfo(Context ctx) { 290 | // try { 291 | // return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); 292 | // } catch (PackageManager.NameNotFoundException e) { 293 | // logger.warning("Error getting package info."); 294 | // return null; 295 | // } 296 | return null; 297 | } 298 | 299 | @ReactMethod 300 | public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) { 301 | // Sentry.configureScope(scope -> { 302 | // if (user == null && otherUserKeys == null) { 303 | // scope.setUser(null); 304 | // } else { 305 | // User userInstance = new User(); 306 | 307 | // if (user != null) { 308 | // if (user.hasKey("email")) { 309 | // userInstance.setEmail(user.getString("email")); 310 | // } 311 | 312 | // if (user.hasKey("id")) { 313 | // userInstance.setId(user.getString("id")); 314 | // } 315 | 316 | // if (user.hasKey("username")) { 317 | // userInstance.setUsername(user.getString("username")); 318 | // } 319 | 320 | // if (user.hasKey("ip_address")) { 321 | // userInstance.setIpAddress(user.getString("ip_address")); 322 | // } 323 | // } 324 | 325 | // if (otherUserKeys != null) { 326 | // HashMap otherUserKeysMap = new HashMap(); 327 | // ReadableMapKeySetIterator it = otherUserKeys.keySetIterator(); 328 | // while (it.hasNextKey()) { 329 | // String key = it.nextKey(); 330 | // String value = otherUserKeys.getString(key); 331 | 332 | // otherUserKeysMap.put(key, value); 333 | // } 334 | 335 | // userInstance.setOthers(otherUserKeysMap); 336 | // } 337 | 338 | // scope.setUser(userInstance); 339 | // } 340 | // }); 341 | } 342 | 343 | @ReactMethod 344 | public void addBreadcrumb(final ReadableMap breadcrumb) { 345 | // Sentry.configureScope(scope -> { 346 | // Breadcrumb breadcrumbInstance = new Breadcrumb(); 347 | 348 | // if (breadcrumb.hasKey("message")) { 349 | // breadcrumbInstance.setMessage(breadcrumb.getString("message")); 350 | // } 351 | 352 | // if (breadcrumb.hasKey("type")) { 353 | // breadcrumbInstance.setType(breadcrumb.getString("type")); 354 | // } 355 | 356 | // if (breadcrumb.hasKey("category")) { 357 | // breadcrumbInstance.setCategory(breadcrumb.getString("category")); 358 | // } 359 | 360 | // if (breadcrumb.hasKey("level")) { 361 | // switch (breadcrumb.getString("level")) { 362 | // case "fatal": 363 | // breadcrumbInstance.setLevel(SentryLevel.FATAL); 364 | // break; 365 | // case "warning": 366 | // breadcrumbInstance.setLevel(SentryLevel.WARNING); 367 | // break; 368 | // case "info": 369 | // breadcrumbInstance.setLevel(SentryLevel.INFO); 370 | // break; 371 | // case "debug": 372 | // breadcrumbInstance.setLevel(SentryLevel.DEBUG); 373 | // break; 374 | // case "error": 375 | // breadcrumbInstance.setLevel(SentryLevel.ERROR); 376 | // break; 377 | // default: 378 | // breadcrumbInstance.setLevel(SentryLevel.ERROR); 379 | // break; 380 | // } 381 | // } 382 | 383 | // if (breadcrumb.hasKey("data")) { 384 | // ReadableMap data = breadcrumb.getMap("data"); 385 | // ReadableMapKeySetIterator it = data.keySetIterator(); 386 | // while (it.hasNextKey()) { 387 | // String key = it.nextKey(); 388 | // String value = data.getString(key); 389 | 390 | // breadcrumbInstance.setData(key, value); 391 | // } 392 | // } 393 | 394 | // scope.addBreadcrumb(breadcrumbInstance); 395 | // }); 396 | } 397 | 398 | @ReactMethod 399 | public void clearBreadcrumbs() { 400 | // Sentry.configureScope(scope -> { 401 | // scope.clearBreadcrumbs(); 402 | // }); 403 | } 404 | 405 | @ReactMethod 406 | public void setExtra(String key, String extra) { 407 | // Sentry.configureScope(scope -> { 408 | // scope.setExtra(key, extra); 409 | // }); 410 | } 411 | 412 | @ReactMethod 413 | public void setTag(String key, String value) { 414 | // Sentry.configureScope(scope -> { 415 | // scope.setTag(key, value); 416 | // }); 417 | } 418 | 419 | @ReactMethod 420 | public void closeNativeSdk(Promise promise) { 421 | // Sentry.close(); 422 | 423 | // disableNativeFramesTracking(); 424 | 425 | promise.resolve(true); 426 | } 427 | 428 | @ReactMethod 429 | public void disableNativeFramesTracking() { 430 | // if (isFrameMetricsAggregatorAvailable()) { 431 | // frameMetricsAggregator.stop(); 432 | // frameMetricsAggregator = null; 433 | // } 434 | } 435 | 436 | // private void setEventOriginTag(SentryEvent event) { 437 | // SdkVersion sdk = event.getSdk(); 438 | // if (sdk != null) { 439 | // switch (sdk.getName()) { 440 | // // If the event is from capacitor js, it gets set there and we do not handle it 441 | // // here. 442 | // case "sentry.native": 443 | // setEventEnvironmentTag(event, "android", "native"); 444 | // break; 445 | // case "sentry.java.android": 446 | // setEventEnvironmentTag(event, "android", "java"); 447 | // break; 448 | // default: 449 | // break; 450 | // } 451 | // } 452 | // } 453 | 454 | // private void setEventEnvironmentTag(SentryEvent event, String origin, String environment) { 455 | // event.setTag("event.origin", origin); 456 | // event.setTag("event.environment", environment); 457 | // } 458 | 459 | // private void addPackages(SentryEvent event, SdkVersion sdk) { 460 | // SdkVersion eventSdk = event.getSdk(); 461 | // if (eventSdk != null && eventSdk.getName().equals("sentry.javascript.react-native") && sdk != null) { 462 | // List sentryPackages = sdk.getPackages(); 463 | // if (sentryPackages != null) { 464 | // for (SentryPackage sentryPackage : sentryPackages) { 465 | // eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion()); 466 | // } 467 | // } 468 | 469 | // List integrations = sdk.getIntegrations(); 470 | // if (integrations != null) { 471 | // for (String integration : integrations) { 472 | // eventSdk.addIntegration(integration); 473 | // } 474 | // } 475 | 476 | // event.setSdk(eventSdk); 477 | // } 478 | // } 479 | 480 | private boolean checkAndroidXAvailability() { 481 | try { 482 | Class.forName("androidx.core.app.FrameMetricsAggregator"); 483 | return true; 484 | } catch (ClassNotFoundException ignored) { 485 | // androidx.core isn't available. 486 | return false; 487 | } 488 | } 489 | 490 | private boolean isFrameMetricsAggregatorAvailable() { 491 | return androidXAvailable && frameMetricsAggregator != null; 492 | } 493 | } --------------------------------------------------------------------------------