├── .babelrc
├── .editorconfig
├── .eslintrc.js
├── .gitattributes
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── App.tsx
├── LICENSE
├── README.md
├── __tests__
└── App-test.tsx
├── android
├── app
│ ├── build.gradle
│ ├── debug.keystore
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── site
│ │ │ │ └── endl
│ │ │ │ └── taiku
│ │ │ │ └── rvmob
│ │ │ │ └── ReactNativeFlipper.java
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ └── ic_launcher_monochrome.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ └── strings.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── java
│ │ │ └── site
│ │ │ │ └── endl
│ │ │ │ └── taiku
│ │ │ │ └── rvmob
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ └── ic_launcher_monochrome.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── release
│ │ └── site
│ │ └── endl
│ │ └── taiku
│ │ └── rvmob
│ │ └── ReactNativeFlipper.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── app.json
├── assets
├── fonts
│ ├── Inter
│ │ ├── Inter.ttf
│ │ ├── Inter_Black.ttf
│ │ ├── Inter_Bold.ttf
│ │ ├── Inter_ExtraBold.ttf
│ │ ├── Inter_ExtraLight.ttf
│ │ ├── Inter_Light.ttf
│ │ ├── Inter_Medium.ttf
│ │ ├── Inter_SemiBold.ttf
│ │ └── Inter_Thin.ttf
│ └── Open Sans
│ │ ├── Open Sans.ttf
│ │ ├── Open Sans_bold.ttf
│ │ ├── Open Sans_bold_italic.ttf
│ │ └── Open Sans_italic.ttf
└── images
│ ├── icon_debug.png
│ └── icon_release.png
├── babel.config.js
├── index.js
├── ios
├── Podfile
├── Revvy.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Revvy.xcscheme
├── Revvy
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ └── main.m
├── RevvyTests
│ ├── Info.plist
│ └── RevvyTests.m
└── link-assets-manifest.json
├── metro.config.js
├── package.json
├── react-native.config.js
├── rvmob-full.png
├── screenshots
├── 1.png
├── 2.png
├── 3.png
├── 4.png
└── 5.png
├── shim.js
├── src
├── Generic.tsx
├── MessageBox.tsx
├── MessageView.tsx
├── Modals.tsx
├── Profile.tsx
├── SideMenus.tsx
├── Theme.tsx
├── components
│ ├── NetworkIndicator.tsx
│ ├── Notification.tsx
│ ├── common
│ │ ├── MarkdownView.tsx
│ │ ├── atoms
│ │ │ ├── Button.tsx
│ │ │ ├── Checkbox.tsx
│ │ │ ├── ContextButton.tsx
│ │ │ ├── CopyIDButton.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── Text.tsx
│ │ │ └── index.tsx
│ │ └── messaging
│ │ │ ├── InviteEmbed.tsx
│ │ │ ├── Message.tsx
│ │ │ ├── MessageEmbed.tsx
│ │ │ ├── ReplyMessage.tsx
│ │ │ └── index.ts
│ ├── navigation
│ │ ├── ChannelHeader.tsx
│ │ ├── ChannelList.tsx
│ │ ├── ServerList.tsx
│ │ └── UserList.tsx
│ ├── pages
│ │ ├── FriendsPage.tsx
│ │ ├── HomePage.tsx
│ │ ├── LoginSettingsPage.tsx
│ │ └── VoiceChannel.tsx
│ ├── sheets
│ │ ├── ChannelInfoSheet.tsx
│ │ ├── MemberListSheet.tsx
│ │ ├── MessageMenuSheet.tsx
│ │ ├── ProfileSheet.tsx
│ │ ├── ReportSheet.tsx
│ │ ├── ServerInfoSheet.tsx
│ │ ├── SettingsSheet.tsx
│ │ ├── UserMenuSheet.tsx
│ │ └── index.tsx
│ └── views
│ │ └── ChannelView.tsx
└── lib
│ ├── consts.ts
│ └── utils.ts
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "@babel/plugin-proposal-decorators", { "legacy": true }
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Windows files
2 | [*.bat]
3 | end_of_line = crlf
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native-community',
4 | parser: '@typescript-eslint/parser',
5 | plugins: ['@typescript-eslint'],
6 | rules: {
7 | 'react-native/no-inline-styles': 0, // TODO: turn this back on when the codebase is less error-filled
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Windows files should use crlf line endings
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | *.bat text eol=crlf
4 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build RVMob (non-release)
2 |
3 | on:
4 | push:
5 |
6 | jobs:
7 | # build-win:
8 | # runs-on: windows-latest
9 | # steps:
10 | # - name: Checkout Repository
11 | # uses: actions/checkout@v3
12 | # - name: Set up Node
13 | # uses: actions/setup-node@v3
14 | # with:
15 | # node-version: 18
16 | # cache: "yarn"
17 | # - name: Set up Java
18 | # uses: actions/setup-java@v3
19 | # with:
20 | # java-version: '11'
21 | # distribution: 'microsoft'
22 | # - name: Install and prepare dependencies
23 | # run: yarn install && npx rn-nodeify -e
24 | # - name: Build the app
25 | # run: npx react-native build-android --mode debug
26 | # - name: Upload debug APK
27 | # uses: actions/upload-artifact@v3
28 | # with:
29 | # path: D:\a\rvmob\rvmob\android\app\build\outputs\apk\debug\app-debug.apk
30 | # name: RVMob-Debug-${{ github.sha }}.apk
31 | build-linux:
32 | runs-on: ubuntu-latest
33 | steps:
34 | - name: Checkout Repository
35 | uses: actions/checkout@v3
36 | - name: Set up Node
37 | uses: actions/setup-node@v3
38 | with:
39 | node-version: 18
40 | cache: 'yarn'
41 | - name: Set up Java
42 | uses: actions/setup-java@v3
43 | with:
44 | java-version: '11'
45 | distribution: 'microsoft'
46 | - name: Install and prepare dependencies
47 | run: yarn install && npx rn-nodeify -e && npx react-native-asset
48 | - name: Bundle the JS
49 | run: npx react-native bundle --platform android --entry-file ./index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
50 | - name: Build the app
51 | run: npx react-native build-android --mode debug
52 | - name: Prepare debug APK
53 | run: mv app-debug.apk RVMob-Debug-${{ github.sha }}.apk
54 | working-directory: /home/runner/work/dvmob/dvmob/android/app/build/outputs/apk/debug/
55 | - name: Upload debug APK
56 | uses: actions/upload-artifact@v3
57 | with:
58 | path: /home/runner/work/dvmob/dvmob/android/app/build/outputs/apk/debug/RVMob-Debug-${{ github.sha }}.apk
59 |
--------------------------------------------------------------------------------
/.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 | # Keys
25 | *.keystore
26 | !debug.keystore
27 |
28 | # Android/IntelliJ
29 | #
30 | build/
31 | .idea
32 | .gradle
33 | local.properties
34 | *.iml
35 | index.android.bundle
36 | .cxx
37 | link-assets-manifest.json
38 | android/app/src/main/assets/fonts
39 |
40 | # node.js
41 | #
42 | node_modules/
43 | npm-debug.log
44 | yarn-error.log
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/
52 |
53 | */fastlane/report.xml
54 | */fastlane/Preview.html
55 | */fastlane/screenshots
56 |
57 | # Bundle artifact
58 | *.jsbundle
59 |
60 | # CocoaPods
61 | /ios/Pods/
62 |
63 | # Temporary files created by Metro to check the health of the file watcher
64 | .metro-health-check*
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true, // TODO: switch this to false at an opportune moment
5 | trailingComma: 'all',
6 | arrowParens: 'avoid',
7 | endOfLine: 'auto',
8 | };
9 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notice
2 | This fork has now been archived as RVMob now supports instances, simply connect with `https://api.divolt.xyz` and restart. Any issues with instance switching should be opened there.
3 |
4 | ---
5 |
6 | # DVMob
7 |
8 | **DVMob** is a mobile Divolt client made in React Native.
9 |
10 | DVMob is currently in beta, exclusive to Android and pretty unoptimized - use at your own discretion.
11 |
12 | ## Building
13 |
14 | Right now, if you want to install DVMob you will have to build it yourself. You'll need [Node](https://nodejs.org/en/), [Yarn Classic](https://classic.yarnpkg.com) and [npx](https://www.npmjs.com/package/npx). Then run the following:
15 |
16 | ```sh
17 | yarn install
18 | npx rn-nodeify -e
19 | yarn start
20 | ```
21 |
22 | CLI commands:
23 |
24 | | Command | Description |
25 | | -------------- | -------------------------------------- |
26 | | `yarn start` | Starts the application. |
27 | | `yarn test` | Tests to see if everything is working. |
28 | | `yarn android` | Runs an Android version. |
29 | | `yarn ios` | Runs an iOS version (requires a Mac). |
30 | | `yarn lint` | Checks the code syntax using ESLint. |
31 |
32 | For more see a list of `react-native`'s commands [here](https://github.com/react-native-community/cli/blob/master/docs/commands.md). You can access them by running `npx react-native`.
33 |
34 | ## Troubleshooting
35 |
36 | If you encounter bugs, first check if you're able to [open Divolt in your browser](https://divolt.xyz). Also check if you have any firewall settings that may block the Revolt API.
37 |
38 | If you're still experiencing issues, and there aren't any open issues for the bug(s) you're facing, please [open an issue](https://github.com/fmhy/dvmob/issues).
39 |
40 | ## License
41 |
42 | DVMob is licensed under the [GNU Affero General Public License v3.0](https://github.com/revoltchat/rvmob/blob/master/LICENSE).
43 |
--------------------------------------------------------------------------------
/__tests__/App-test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native';
6 | import React from 'react';
7 | import App from '../App';
8 |
9 | // Note: test renderer must be required after react-native.
10 | import renderer from 'react-test-renderer';
11 |
12 | it('renders correctly', () => {
13 | renderer.create();
14 | });
15 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "com.facebook.react"
3 |
4 | import com.android.build.OutputFile
5 |
6 | /**
7 | * This is the configuration block to customize your React Native Android app.
8 | * By default you don't need to apply any configuration, just uncomment the lines you need.
9 | */
10 | react {
11 | /* Folders */
12 | // The root of your project, i.e. where "package.json" lives. Default is '..'
13 | // root = file("../")
14 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native
15 | // reactNativeDir = file("../node-modules/react-native")
16 | // The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
17 | // codegenDir = file("../node-modules/react-native-codegen")
18 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
19 | // cliFile = file("../node_modules/react-native/cli.js")
20 | /* Variants */
21 | // The list of variants to that are debuggable. For those we're going to
22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
24 | // debuggableVariants = ["liteDebug", "prodDebug"]
25 | /* Bundling */
26 | // A list containing the node command and its flags. Default is just 'node'.
27 | // nodeExecutableAndArgs = ["node"]
28 | //
29 | // The command to run when bundling. By default is 'bundle'
30 | // bundleCommand = "ram-bundle"
31 | //
32 | // The path to the CLI configuration file. Default is empty.
33 | // bundleConfig = file(../rn-cli.config.js)
34 | //
35 | // The name of the generated asset file containing your JS bundle
36 | // bundleAssetName = "MyApplication.android.bundle"
37 | //
38 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
39 | // entryFile = file("../js/MyApplication.android.js")
40 | //
41 | // A list of extra flags to pass to the 'bundle' commands.
42 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
43 | // extraPackagerArgs = []
44 | /* Hermes Commands */
45 | // The hermes compiler command to run. By default it is 'hermesc'
46 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
47 | //
48 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
49 | // hermesFlags = ["-O", "-output-source-map"]
50 | }
51 |
52 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
53 |
54 | /**
55 | * Set this to true to create four separate APKs instead of one,
56 | * one for each native architecture. This is useful if you don't
57 | * use App Bundles (https://developer.android.com/guide/app-bundle/)
58 | * and want to have separate APKs to upload to the Play Store.
59 | */
60 | def enableSeparateBuildPerCPUArchitecture = false
61 |
62 | /**
63 | * Set this to true to run Proguard on Release builds to minify the Java bytecode.
64 | */
65 | def enableProguardInReleaseBuilds = false
66 |
67 | /**
68 | * The preferred build flavor of JavaScriptCore (JSC).
69 | *
70 | * For example, to use the international variant, you can use:
71 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
72 | *
73 | * The international variant includes ICU i18n library and necessary data
74 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
75 | * give correct results when using with locales other than en-US. Note that
76 | * this variant is about 6MiB larger per architecture than default.
77 | */
78 | def jscFlavor = 'org.webkit:android-jsc-intl:+'
79 |
80 | /**
81 | * Private function to get the list of Native Architectures you want to build.
82 | * This reads the value from reactNativeArchitectures in your gradle.properties
83 | * file and works together with the --active-arch-only flag of react-native run-android.
84 | */
85 | def reactNativeArchitectures() {
86 | def value = project.getProperties().get("reactNativeArchitectures")
87 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
88 | }
89 |
90 | android {
91 | ndkVersion rootProject.ext.ndkVersion
92 |
93 | compileSdkVersion rootProject.ext.compileSdkVersion
94 |
95 | namespace "site.endl.taiku.rvmob"
96 | defaultConfig {
97 | applicationId "site.endl.taiku.rvmob"
98 | minSdkVersion rootProject.ext.minSdkVersion
99 | targetSdkVersion rootProject.ext.targetSdkVersion
100 | versionCode 5
101 | versionName "0.4.0"
102 | }
103 | splits {
104 | abi {
105 | reset()
106 | enable enableSeparateBuildPerCPUArchitecture
107 | universalApk false // If true, also generate a universal APK
108 | include (*reactNativeArchitectures())
109 | }
110 | }
111 | signingConfigs {
112 | debug {
113 | storeFile file('debug.keystore')
114 | storePassword 'android'
115 | keyAlias 'androiddebugkey'
116 | keyPassword 'android'
117 | }
118 | release {
119 | storeFile file('.keystore')
120 | storePassword ''
121 | keyAlias ''
122 | keyPassword ''
123 | }
124 | }
125 | buildTypes {
126 | debug {
127 | signingConfig signingConfigs.debug
128 | applicationIdSuffix '.debug'
129 | }
130 | release {
131 | // Caution! In production, you need to generate your own keystore file.
132 | // see https://reactnative.dev/docs/signed-apk-android.
133 | signingConfig signingConfigs.debug
134 | minifyEnabled enableProguardInReleaseBuilds
135 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
136 | }
137 | }
138 |
139 | // applicationVariants are e.g. debug, release
140 | applicationVariants.all { variant ->
141 | variant.outputs.each { output ->
142 | // For each separate APK per architecture, set a unique version code as described here:
143 | // https://developer.android.com/studio/build/configure-apk-splits.html
144 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
145 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
146 | def abi = output.getFilter(OutputFile.ABI)
147 | if (abi != null) { // null for the universal-debug, universal-release variants
148 | output.versionCodeOverride =
149 | defaultConfig.versionCode * 1000 + versionCodes.get(abi)
150 | }
151 |
152 | }
153 | }
154 | packagingOptions {
155 | // Make sure libjsc.so does not packed in APK
156 | exclude "**/libjsc.so"
157 | }
158 | }
159 |
160 | dependencies {
161 | // The version of react-native is set by the React Native Gradle Plugin
162 | implementation("com.facebook.react:react-android")
163 |
164 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
165 |
166 | // implementation('com.ts:auth-control-sdk:5.1.1@aar') { transitive=true }
167 |
168 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
169 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
170 | exclude group:'com.squareup.okhttp3', module:'okhttp'
171 | }
172 |
173 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
174 |
175 | // if (hermesEnabled.toBoolean()) {
176 | implementation("com.facebook.react:hermes-android")
177 | // } else {
178 | // implementation jscFlavor
179 | // }
180 | }
181 |
182 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
183 |
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/debug.keystore
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 | -keep class com.facebook.hermes.unicode.** { *; }
12 | -keep class com.facebook.jni.** { *; }
13 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/app/src/debug/java/site/endl/taiku/rvmob/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 site.endl.taiku.rvmob;
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.sharedpreferences.SharedPreferencesFlipperPlugin;
21 | import com.facebook.react.ReactInstanceManager;
22 | import com.facebook.react.bridge.ReactContext;
23 | import com.facebook.react.modules.network.NetworkingModule;
24 | import okhttp3.OkHttpClient;
25 |
26 | public class ReactNativeFlipper {
27 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
28 | if (FlipperUtils.shouldEnableFlipper(context)) {
29 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
30 |
31 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
32 | client.addPlugin(new DatabasesFlipperPlugin(context));
33 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
34 | client.addPlugin(CrashReporterPlugin.getInstance());
35 |
36 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
37 | NetworkingModule.setCustomClientBuilder(
38 | new NetworkingModule.CustomClientBuilder() {
39 | @Override
40 | public void apply(OkHttpClient.Builder builder) {
41 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
42 | }
43 | });
44 | client.addPlugin(networkFlipperPlugin);
45 | client.start();
46 |
47 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
48 | // Hence we run if after all native modules have been initialized
49 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
50 | if (reactContext == null) {
51 | reactInstanceManager.addReactInstanceEventListener(
52 | new ReactInstanceManager.ReactInstanceEventListener() {
53 | @Override
54 | public void onReactContextInitialized(ReactContext reactContext) {
55 | reactInstanceManager.removeReactInstanceEventListener(this);
56 | reactContext.runOnNativeModulesQueueThread(
57 | new Runnable() {
58 | @Override
59 | public void run() {
60 | client.addPlugin(new FrescoFlipperPlugin());
61 | }
62 | });
63 | }
64 | });
65 | } else {
66 | client.addPlugin(new FrescoFlipperPlugin());
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RVMob Canary
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/assets/.gitkeep
--------------------------------------------------------------------------------
/android/app/src/main/java/site/endl/taiku/rvmob/MainActivity.java:
--------------------------------------------------------------------------------
1 | package site.endl.taiku.rvmob;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript. This is used to schedule
9 | * rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "Revvy";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/android/app/src/main/java/site/endl/taiku/rvmob/MainApplication.java:
--------------------------------------------------------------------------------
1 | package site.endl.taiku.rvmob;
2 |
3 | import android.app.Application;
4 | import com.facebook.react.PackageList;
5 | import com.facebook.react.ReactApplication;
6 | import io.invertase.notifee.NotifeePackage;
7 | import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
8 | import org.linusu.RNGetRandomValuesPackage;
9 | import com.bitgo.randombytes.RandomBytesPackage;
10 | import com.facebook.react.ReactNativeHost;
11 | import com.facebook.react.modules.systeminfo.AndroidInfoHelpers;
12 | import com.facebook.react.ReactPackage;
13 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
14 | import com.facebook.react.defaults.DefaultReactNativeHost;
15 | import com.facebook.soloader.SoLoader;
16 | import java.util.List;
17 |
18 | public class MainApplication extends Application implements ReactApplication {
19 |
20 | private final ReactNativeHost mReactNativeHost =
21 | new DefaultReactNativeHost(this) {
22 | @Override
23 | public boolean getUseDeveloperSupport() {
24 | return BuildConfig.DEBUG;
25 | }
26 |
27 | @Override
28 | protected List getPackages() {
29 | @SuppressWarnings("UnnecessaryLocalVariable")
30 | List packages = new PackageList(this).getPackages();
31 | // Packages that cannot be autolinked yet can be added manually here, for example:
32 | // packages.add(new MyReactNativePackage());
33 | return packages;
34 | }
35 |
36 | @Override
37 | protected String getJSMainModuleName() {
38 | return "index";
39 | }
40 |
41 | @Override
42 | protected boolean isNewArchEnabled() {
43 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
44 | }
45 |
46 | @Override
47 | protected Boolean isHermesEnabled() {
48 | return BuildConfig.IS_HERMES_ENABLED;
49 | }
50 | };
51 |
52 | @Override
53 | public ReactNativeHost getReactNativeHost() {
54 | return mReactNativeHost;
55 | }
56 |
57 | @Override
58 | public void onCreate() {
59 | super.onCreate();
60 | SoLoader.init(this, /* native exopackage */ false);
61 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
62 | // If you opted-in for the New Architecture, we load the native entry point for this app.
63 | DefaultNewArchitectureEntryPoint.load();
64 | }
65 | ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RVMob
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/release/site/endl/taiku/rvmob/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and 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.rndiffapp;
8 | import android.content.Context;
9 | import com.facebook.react.ReactInstanceManager;
10 | /**
11 | * Class responsible of loading Flipper inside your React Native application. This is the release
12 | * flavor of it so it's empty as we don't want to load Flipper.
13 | */
14 | public class ReactNativeFlipper {
15 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
16 | // Do nothing as we don't want to initialize Flipper on Release.
17 | }
18 | }
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | buildToolsVersion = "33.0.0"
5 | minSdkVersion = 21
6 | compileSdkVersion = 33
7 | targetSdkVersion = 33
8 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
9 | ndkVersion = "23.1.7779620"
10 | }
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | dependencies {
16 | classpath('com.android.tools.build:gradle:7.4.1')
17 | classpath("com.facebook.react:react-native-gradle-plugin")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
21 | # AndroidX package structure to make it clearer which packages are bundled with the
22 | # Android operating system, and which are packaged with your app's APK
23 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
24 | android.useAndroidX=true
25 | # Automatically convert third-party libraries to use AndroidX
26 | android.enableJetifier=true
27 |
28 | # Version of flipper SDK to use with React Native
29 | FLIPPER_VERSION=0.93.0
30 |
31 | # Use this property to enable or disable the Hermes JS engine.
32 | # If set to false, you will be using JSC instead.
33 | hermesEnabled=true
34 |
35 | newArchEnabled=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/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.5.1-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'Revvy'
2 | includeBuild('../node_modules/react-native-gradle-plugin')
3 | include ':@notifee_react-native'
4 | project(':@notifee_react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@notifee/react-native/android')
5 | include ':@react-native-async-storage_async-storage'
6 | project(':@react-native-async-storage_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
7 | include ':react-native-get-random-values'
8 | project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
9 | include ':react-native-randombytes'
10 | project(':react-native-randombytes').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-randombytes/android')
11 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
12 | include ':app'
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Revvy",
3 | "displayName": "DVMob"
4 | }
5 |
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_Black.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_ExtraBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_ExtraLight.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_Light.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_Medium.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_SemiBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Inter/Inter_Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Inter/Inter_Thin.ttf
--------------------------------------------------------------------------------
/assets/fonts/Open Sans/Open Sans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Open Sans/Open Sans.ttf
--------------------------------------------------------------------------------
/assets/fonts/Open Sans/Open Sans_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Open Sans/Open Sans_bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Open Sans/Open Sans_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Open Sans/Open Sans_bold_italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Open Sans/Open Sans_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/fonts/Open Sans/Open Sans_italic.ttf
--------------------------------------------------------------------------------
/assets/images/icon_debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/images/icon_debug.png
--------------------------------------------------------------------------------
/assets/images/icon_release.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/assets/images/icon_release.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry} from 'react-native';
6 | import {App} from './App';
7 | import {name as appName} from './app.json';
8 |
9 | AppRegistry.registerComponent(appName, () => App);
10 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | require_relative '../node_modules/react-native/scripts/react_native_pods'
2 | require_relative '../node_modules/react-native/scripts/native_modules'
3 | platform :ios, min_ios_version_supported
4 | prepare_react_native_project!
5 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
6 | linkage = ENV['USE_FRAMEWORKS']
7 | if linkage != nil
8 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
9 | use_frameworks! :linkage => linkage.to_sym
10 | end
11 |
12 | target 'Revvy' do
13 | config = use_native_modules!
14 |
15 | use_react_native!(
16 | :path => config[:reactNativePath],
17 | # to enable hermes on iOS, change `false` to `true` and then install pods
18 | :hermes_enabled => false
19 | )
20 |
21 | pod 'react-native-randombytes', :path => '../node_modules/react-native-randombytes'
22 |
23 | pod 'react-native-get-random-values', :path => '../node_modules/react-native-get-random-values'
24 |
25 | pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-async-storage/async-storage'
26 |
27 | pod 'RNNotifee', :path => '../node_modules/@notifee/react-native'
28 |
29 | target 'RevvyTests' do
30 | inherit! :complete
31 | # Pods for testing
32 | end
33 |
34 | # Enables Flipper.
35 | #
36 | # Note that if you have use_frameworks! enabled, Flipper will not work and
37 | # you should disable the next line.
38 | use_flipper!()
39 |
40 | post_install do |installer|
41 | react_native_post_install(installer)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/ios/Revvy.xcodeproj/xcshareddata/xcschemes/Revvy.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/ios/Revvy/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | @interface AppDelegate : RCTAppDelegate
4 | @end
--------------------------------------------------------------------------------
/ios/Revvy/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 | #import
5 | #import
6 |
7 | #ifdef FB_SONARKIT_ENABLED
8 | #import
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | static void InitializeFlipper(UIApplication *application) {
16 | FlipperClient *client = [FlipperClient sharedClient];
17 | SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
18 | [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
19 | [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
20 | [client addPlugin:[FlipperKitReactPlugin new]];
21 | [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
22 | [client start];
23 | }
24 | #endif
25 |
26 | @implementation AppDelegate
27 |
28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
29 | {
30 | #ifdef FB_SONARKIT_ENABLED
31 | InitializeFlipper(application);
32 | #endif
33 |
34 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
35 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
36 | moduleName:@"Revvy"
37 | initialProperties:nil];
38 |
39 | if (@available(iOS 13.0, *)) {
40 | rootView.backgroundColor = [UIColor systemBackgroundColor];
41 | } else {
42 | rootView.backgroundColor = [UIColor whiteColor];
43 | }
44 |
45 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
46 | UIViewController *rootViewController = [UIViewController new];
47 | rootViewController.view = rootView;
48 | self.window.rootViewController = rootViewController;
49 | [self.window makeKeyAndVisible];
50 | return YES;
51 | }
52 |
53 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
54 | {
55 | #if DEBUG
56 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
57 | #else
58 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
59 | #endif
60 | }
61 |
62 | @end
63 |
--------------------------------------------------------------------------------
/ios/Revvy/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/ios/Revvy/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Revvy/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Revvy
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | localhost
32 |
33 | NSExceptionAllowsInsecureHTTPLoads
34 |
35 |
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 | UIAppFonts
55 |
56 | AntDesign.ttf
57 | Entypo.ttf
58 | EvilIcons.ttf
59 | Feather.ttf
60 | FontAwesome.ttf
61 | FontAwesome5_Brands.ttf
62 | FontAwesome5_Regular.ttf
63 | FontAwesome5_Solid.ttf
64 | Fontisto.ttf
65 | Foundation.ttf
66 | Ionicons.ttf
67 | MaterialCommunityIcons.ttf
68 | MaterialIcons.ttf
69 | Octicons.ttf
70 | SimpleLineIcons.ttf
71 | Zocial.ttf
72 | Inter.ttf
73 | Inter_Black.ttf
74 | Inter_Bold.ttf
75 | Inter_ExtraBold.ttf
76 | Inter_ExtraLight.ttf
77 | Inter_Light.ttf
78 | Inter_Medium.ttf
79 | Inter_SemiBold.ttf
80 | Inter_Thin.ttf
81 | Open Sans.ttf
82 | Open Sans_bold.ttf
83 | Open Sans_bold_italic.ttf
84 | Open Sans_italic.ttf
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Revvy/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ios/Revvy/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char * argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ios/RevvyTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ios/RevvyTests/RevvyTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface RevvyTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation RevvyTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
38 | if (level >= RCTLogLevelError) {
39 | redboxError = message;
40 | }
41 | });
42 | #endif
43 |
44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 |
48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
50 | return YES;
51 | }
52 | return NO;
53 | }];
54 | }
55 |
56 | #ifdef DEBUG
57 | RCTSetLogFunction(RCTDefaultLogFunction);
58 | #endif
59 |
60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
62 | }
63 |
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/ios/link-assets-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "migIndex": 1,
3 | "data": [
4 | {
5 | "path": "assets/fonts/Inter/Inter.ttf",
6 | "sha1": "7237d9cf55f177702066a28a4dde1e4c7e8ab576"
7 | },
8 | {
9 | "path": "assets/fonts/Inter/Inter_Black.ttf",
10 | "sha1": "283fa65b96dc1f2d71c6504afb9610447bdcdf1b"
11 | },
12 | {
13 | "path": "assets/fonts/Inter/Inter_Bold.ttf",
14 | "sha1": "48747b7a60086f97af0d373febcbd1f1bee87f17"
15 | },
16 | {
17 | "path": "assets/fonts/Inter/Inter_ExtraBold.ttf",
18 | "sha1": "bff772be6bca9895c6fdcd57e5d5be9a47f2e058"
19 | },
20 | {
21 | "path": "assets/fonts/Inter/Inter_ExtraLight.ttf",
22 | "sha1": "421072aef8610cb0f79661b494442a4b9363b43f"
23 | },
24 | {
25 | "path": "assets/fonts/Inter/Inter_Light.ttf",
26 | "sha1": "dd9ec1ae67dd85143bacf7a3cb37e9f0ac979843"
27 | },
28 | {
29 | "path": "assets/fonts/Inter/Inter_Medium.ttf",
30 | "sha1": "a418a8ba73bbcfa8c131c426ab836d78457afa9b"
31 | },
32 | {
33 | "path": "assets/fonts/Inter/Inter_SemiBold.ttf",
34 | "sha1": "431007da316de60d85174aeec9b8389b5c73e7d6"
35 | },
36 | {
37 | "path": "assets/fonts/Inter/Inter_Thin.ttf",
38 | "sha1": "297919d59c9ba4fa17251cdb9f06c26ca906e593"
39 | },
40 | {
41 | "path": "assets/fonts/Open Sans/Open Sans.ttf",
42 | "sha1": "babe8dce93a3e48b6c3c79720a0c048e88dd1fe7"
43 | },
44 | {
45 | "path": "assets/fonts/Open Sans/Open Sans_bold.ttf",
46 | "sha1": "1e3704ee48b5ff7e582488ead87b05249f14dc1c"
47 | },
48 | {
49 | "path": "assets/fonts/Open Sans/Open Sans_bold_italic.ttf",
50 | "sha1": "72068cc905085ca406e6391cc5021ac043689ade"
51 | },
52 | {
53 | "path": "assets/fonts/Open Sans/Open Sans_italic.ttf",
54 | "sha1": "b8d9f5ceb1e6b3a716dab0bdd09061bbc349b41e"
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: true,
13 | inlineRequires: true,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rvmob",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "clean": "react-native-clean-project",
7 | "android": "react-native run-android",
8 | "ios": "react-native run-ios",
9 | "start": "react-native start",
10 | "test": "jest",
11 | "lint": "eslint .",
12 | "windows": "react-native run-windows"
13 | },
14 | "dependencies": {
15 | "@babel/plugin-proposal-decorators": "^7.21.0",
16 | "@babel/preset-env": "^7.20.2",
17 | "@notifee/react-native": "^7.6.1",
18 | "@react-native-async-storage/async-storage": "^1.18.0",
19 | "@react-native-clipboard/clipboard": "^1.11.2",
20 | "@tradle/react-native-http": "^2.0.1",
21 | "@traptitech/markdown-it-spoiler": "^1.1.6",
22 | "@tsconfig/react-native": "^2.0.3",
23 | "@types/jest": "^29.5.0",
24 | "add": "^2.0.6",
25 | "assert": "^2.0.0",
26 | "browserify-zlib": "^0.2.0",
27 | "buffer": "^6.0.3",
28 | "console-browserify": "^1.2.0",
29 | "constants-browserify": "^1.0.0",
30 | "date-fns": "^2.25.0",
31 | "dayjs": "^1.10.7",
32 | "dns.js": "^1.0.1",
33 | "domain-browser": "^4.22.0",
34 | "events": "^3.3.0",
35 | "https-browserify": "^1.0.0",
36 | "markdown-it-regexp": "^0.4.0",
37 | "mobx-react": "^7.6.0",
38 | "mobx-react-lite": "^3.4.3",
39 | "path-browserify": "^1.0.1",
40 | "process": "^0.11.10",
41 | "punycode": "^2.3.0",
42 | "querystring-es3": "^0.2.1",
43 | "react": "^18.2.0",
44 | "react-dom": "^18.2.0",
45 | "react-error-boundary": "^4.0.3",
46 | "react-native": "^0.71.6",
47 | "react-native-bottom-drawer-view": "^1.0.3",
48 | "react-native-clean-project": "^4.0.1",
49 | "react-native-crypto": "^2.2.0",
50 | "react-native-device-info": "^10.6.0",
51 | "react-native-document-picker": "^8.2.0",
52 | "react-native-elements": "^3.4.3",
53 | "react-native-fast-image": "^8.6.3",
54 | "react-native-fs": "^2.18.0",
55 | "react-native-get-random-values": "^1.7.0",
56 | "react-native-image-viewing": "^0.2.0",
57 | "react-native-image-zoom-viewer": "^3.0.1",
58 | "react-native-level-fs": "^3.0.1",
59 | "react-native-markdown-display": "^7.0.0-alpha.2",
60 | "react-native-modal": "^13.0.1",
61 | "react-native-os": "https://github.com/aprock/react-native-os",
62 | "react-native-randombytes": "^3.6.1",
63 | "react-native-side-menu": "^1.1.3",
64 | "react-native-side-menu-updated": "^1.3.2",
65 | "react-native-sliding-modal": "^2.1.0",
66 | "react-native-tcp-socket": "^6.0.6",
67 | "react-native-udp": "^4.1.7",
68 | "react-native-vector-icons": "^9.0.0",
69 | "react-native-web": "^0.19.1",
70 | "react-native-windows": "^0.71.4",
71 | "readable-stream": "^4.3.0",
72 | "revolt.js": "^6.0.20",
73 | "stream-browserify": "^3.0.0",
74 | "timers-browserify": "^2.0.12",
75 | "tty-browserify": "^0.0.1",
76 | "url": "^0.11.0",
77 | "util": "^0.12.5",
78 | "vm-browserify": "^1.1.2",
79 | "yarn": "^1.22.19"
80 | },
81 | "devDependencies": {
82 | "@babel/core": "^7.21.3",
83 | "@babel/runtime": "^7.21.0",
84 | "@react-native-community/eslint-config": "^3.2.0",
85 | "@types/assert": "^1.5.6",
86 | "@types/babel__core": "^7.20.0",
87 | "@types/eslint": "^8.21.3",
88 | "@types/events": "^3.0.0",
89 | "@types/punycode": "^2.1.0",
90 | "@types/react": "^18.0.33",
91 | "@types/react-dom": "^18.0.11",
92 | "@types/react-native-vector-icons": "^6.4.13",
93 | "@types/react-test-renderer": "^18.0.0",
94 | "@types/readable-stream": "^2.3.15",
95 | "babel-jest": "^29.5.0",
96 | "eslint": "^8.38.0",
97 | "jest": "^29.5.0",
98 | "metro-react-native-babel-preset": "^0.76.1",
99 | "react-native-codegen": "^0.72.0",
100 | "react-test-renderer": "^18.2.0",
101 | "rn-nodeify": "^10.3.0",
102 | "typescript": "^5.0.2"
103 | },
104 | "jest": {
105 | "preset": "react-native"
106 | },
107 | "react-native": {
108 | "zlib": "browserify-zlib",
109 | "console": "console-browserify",
110 | "constants": "constants-browserify",
111 | "crypto": "react-native-crypto",
112 | "dns": "dns.js",
113 | "net": "react-native-tcp",
114 | "domain": "domain-browser",
115 | "http": "@tradle/react-native-http",
116 | "https": "https-browserify",
117 | "os": "react-native-os",
118 | "path": "path-browserify",
119 | "querystring": "querystring-es3",
120 | "fs": "react-native-level-fs",
121 | "_stream_transform": "readable-stream/transform",
122 | "_stream_readable": "readable-stream/readable",
123 | "_stream_writable": "readable-stream/writable",
124 | "_stream_duplex": "readable-stream/duplex",
125 | "_stream_passthrough": "readable-stream/passthrough",
126 | "dgram": "react-native-udp",
127 | "stream": "stream-browserify",
128 | "timers": "timers-browserify",
129 | "tty": "tty-browserify",
130 | "vm": "vm-browserify",
131 | "tls": false
132 | },
133 | "browser": {
134 | "zlib": "browserify-zlib",
135 | "console": "console-browserify",
136 | "constants": "constants-browserify",
137 | "crypto": "react-native-crypto",
138 | "dns": "dns.js",
139 | "net": "react-native-tcp",
140 | "domain": "domain-browser",
141 | "http": "@tradle/react-native-http",
142 | "https": "https-browserify",
143 | "os": "react-native-os",
144 | "path": "path-browserify",
145 | "querystring": "querystring-es3",
146 | "fs": "react-native-level-fs",
147 | "_stream_transform": "readable-stream/transform",
148 | "_stream_readable": "readable-stream/readable",
149 | "_stream_writable": "readable-stream/writable",
150 | "_stream_duplex": "readable-stream/duplex",
151 | "_stream_passthrough": "readable-stream/passthrough",
152 | "dgram": "react-native-udp",
153 | "stream": "stream-browserify",
154 | "timers": "timers-browserify",
155 | "tty": "tty-browserify",
156 | "vm": "vm-browserify",
157 | "tls": false
158 | },
159 | "resolutions": {
160 | "revolt-api": "^0.5.17"
161 | },
162 | "packageManager": "yarn@1.22.19"
163 | }
164 |
--------------------------------------------------------------------------------
/react-native.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | project: {
3 | ios: {},
4 | android: {}
5 | },
6 | assets: ['./assets/fonts']
7 | }
--------------------------------------------------------------------------------
/rvmob-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/rvmob-full.png
--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/screenshots/1.png
--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/screenshots/2.png
--------------------------------------------------------------------------------
/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/screenshots/3.png
--------------------------------------------------------------------------------
/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/screenshots/4.png
--------------------------------------------------------------------------------
/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fmhy/dvmob/97df5088f7b47f94af6ef9f0ffa6a78d807c6ef3/screenshots/5.png
--------------------------------------------------------------------------------
/shim.js:
--------------------------------------------------------------------------------
1 | if (typeof __dirname === 'undefined') global.__dirname = '/'
2 | if (typeof __filename === 'undefined') global.__filename = ''
3 | if (typeof process === 'undefined') {
4 | global.process = require('process')
5 | } else {
6 | const bProcess = require('process')
7 | for (var p in bProcess) {
8 | if (!(p in process)) {
9 | process[p] = bProcess[p]
10 | }
11 | }
12 | }
13 |
14 | process.browser = false
15 | if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer
16 |
17 | // global.location = global.location || { port: 80 }
18 | const isDev = typeof __DEV__ === 'boolean' && __DEV__
19 | process.env['NODE_ENV'] = isDev ? 'development' : 'production'
20 | if (typeof localStorage !== 'undefined') {
21 | localStorage.debug = isDev ? '*' : ''
22 | }
23 |
24 | // If using the crypto shim, uncomment the following line to ensure
25 | // crypto is loaded first, so it can populate global.crypto
26 | require('crypto')
27 |
--------------------------------------------------------------------------------
/src/Profile.tsx:
--------------------------------------------------------------------------------
1 | import {observer} from 'mobx-react-lite';
2 | import React from 'react';
3 | import {client, app} from './Generic';
4 | import {currentTheme, styles} from './Theme';
5 | import {Pressable, View} from 'react-native';
6 | import FastImage from 'react-native-fast-image';
7 | import {Server, User, Message, Channel} from 'revolt.js';
8 | import {Text} from './components/common/atoms';
9 | import {getColour} from './lib/utils';
10 | import {DEFAULT_MAX_SIDE, USER_IDS} from './lib/consts';
11 |
12 | const Image = FastImage;
13 |
14 | type UsernameProps = {
15 | server?: Server;
16 | user?: User;
17 | noBadge?: boolean;
18 | size?: number;
19 | masquerade?: string | null;
20 | color?: string;
21 | };
22 |
23 | export const Username = observer(
24 | ({server, user, noBadge, size, masquerade, color}: UsernameProps) => {
25 | if (typeof user !== 'object') {
26 | return (
27 | {''}
28 | );
29 | }
30 | let memberObject = server
31 | ? client.members.getKey({
32 | server: server?._id,
33 | user: user?._id,
34 | })
35 | : undefined;
36 | let roleColor = color ? getColour(color) : styles.textDefault.color;
37 | let name =
38 | server && memberObject?.nickname
39 | ? memberObject?.nickname
40 | : user?.username;
41 | if (server && memberObject?.roles && memberObject?.roles?.length > 0) {
42 | let srv = client.servers.get(memberObject._id.server);
43 | if (srv?.roles) {
44 | for (let role of memberObject?.roles) {
45 | if (srv.roles[role].colour) {
46 | roleColor = getColour(srv.roles[role].colour!);
47 | }
48 | }
49 | }
50 | }
51 | let badgeSize = (size || 14) * 0.6;
52 | let bridgedMessage =
53 | user?._id === USER_IDS.automod && masquerade !== undefined;
54 | let badgeStyle = {
55 | color: currentTheme.accentColorForeground,
56 | backgroundColor: currentTheme.accentColor,
57 | marginLeft: badgeSize * 0.3,
58 | paddingLeft: badgeSize * 0.4,
59 | paddingRight: badgeSize * 0.4,
60 | borderRadius: 3,
61 | fontSize: badgeSize,
62 | height: badgeSize + badgeSize * 0.45,
63 | top: badgeSize * 0.5,
64 | };
65 | return (
66 |
67 |
70 | {masquerade ?? name}
71 |
72 | {!noBadge ? (
73 | <>
74 | {bridgedMessage ? (
75 | BRIDGE
76 | ) : (
77 | <>
78 | {user?.bot ? BOT : null}
79 | {masquerade ? MASQ. : null}
80 | {user?._id === USER_IDS.platformModeration ? (
81 | SYSTEM
82 | ) : null}
83 | >
84 | )}
85 | >
86 | ) : null}
87 |
88 | );
89 | },
90 | );
91 |
92 | type AvatarProps = {
93 | channel?: Channel;
94 | user?: User;
95 | server?: Server;
96 | status?: boolean;
97 | size?: number;
98 | backgroundColor?: string;
99 | masquerade?: string;
100 | pressable?: boolean;
101 | };
102 |
103 | export const Avatar = observer(
104 | ({
105 | channel,
106 | user,
107 | server,
108 | status,
109 | size,
110 | backgroundColor,
111 | masquerade,
112 | pressable,
113 | }: AvatarProps) => {
114 | let memberObject =
115 | server && user
116 | ? client.members.getKey({
117 | server: server?._id,
118 | user: user?._id,
119 | })
120 | : null;
121 | let statusColor;
122 | let statusScale = 2.7;
123 | if (status) {
124 | const s = user?.online ? user.status?.presence || 'Online' : 'Offline';
125 | statusColor = currentTheme[`status${s}`];
126 | }
127 | let Container = pressable
128 | ? ({children}) => (
129 | app.openImage(memberObject?.avatar || user?.avatar)}>
131 | {children}
132 |
133 | )
134 | : View;
135 | if (user) {
136 | return (
137 |
138 |
153 | {status ? (
154 |
166 | ) : null}
167 | {masquerade && app.settings.get('ui.messaging.showMasqAvatar') ? (
168 |
187 | ) : null}
188 |
189 | );
190 | }
191 | if (channel) {
192 | return (
193 |
194 | {channel?.generateIconURL() ? (
195 |
206 | ) : null}
207 |
208 | );
209 | }
210 | return <>>;
211 | },
212 | );
213 |
214 | type MiniProfileProps = {
215 | user?: User;
216 | scale?: number;
217 | channel?: Channel;
218 | server?: Server;
219 | color?: string;
220 | };
221 |
222 | export const MiniProfile = observer(
223 | ({user, scale, channel, server, color}: MiniProfileProps) => {
224 | if (user) {
225 | return (
226 |
227 |
234 |
237 |
243 |
249 | {user.online
250 | ? user.status?.text || user.status?.presence || 'Online'
251 | : 'Offline'}
252 |
253 |
254 |
255 | );
256 | }
257 |
258 | if (channel) {
259 | return (
260 |
261 |
262 |
263 |
269 | {channel.name}
270 |
271 |
277 | {channel?.recipient_ids?.length} members
278 |
279 |
280 |
281 | );
282 | }
283 |
284 | return <>>;
285 | },
286 | );
287 |
288 | type RoleViewProps = {
289 | server: Server;
290 | user: User;
291 | };
292 |
293 | export const RoleView = observer(({server, user}: RoleViewProps) => {
294 | let memberObject = client.members.getKey({
295 | server: server?._id,
296 | user: user?._id,
297 | });
298 |
299 | let roles = memberObject?.roles?.map(r => server.roles![r]) || null;
300 | return memberObject && roles ? (
301 | <>
302 | ROLES
303 |
306 | {roles.map(r => (
307 |
318 |
329 | {r.name}
330 |
331 | ))}
332 |
333 | >
334 | ) : null;
335 | });
336 |
--------------------------------------------------------------------------------
/src/SideMenus.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, TouchableOpacity, ScrollView, FlatList} from 'react-native';
3 |
4 | import FAIcon from 'react-native-vector-icons/FontAwesome';
5 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
6 |
7 | import {Server, User} from 'revolt.js';
8 |
9 | import {app, setFunction, client} from './Generic';
10 | import {MiniProfile, Avatar} from './Profile';
11 | import {styles, currentTheme} from './Theme';
12 | import {Button, Text} from './components/common/atoms';
13 | import {MarkdownView} from './components/common/MarkdownView';
14 | import {ChannelList} from './components/navigation/ChannelList';
15 | import {ServerList} from './components/navigation/ServerList';
16 | import {DEFAULT_API_URL} from './lib/consts';
17 |
18 | export const LeftMenu = ({
19 | currentChannel,
20 | onChannelClick,
21 | onLogOut,
22 | orderedServers,
23 | }: {
24 | currentChannel: any;
25 | onChannelClick: Function;
26 | onLogOut: Function;
27 | orderedServers: string[];
28 | }) => {
29 | const [currentServer, setCurrentServer] = React.useState(
30 | null as Server | null,
31 | );
32 | setFunction('openServer', (s: Server | null) => {
33 | setCurrentServer(s);
34 | });
35 | return (
36 | <>
37 |
38 |
39 | {
41 | currentServer ? setCurrentServer(null) : app.openStatusMenu(true);
42 | }}
43 | onLongPress={() => {
44 | app.openProfile(client.user);
45 | }}
46 | delayLongPress={750}
47 | key={client.user?._id}
48 | style={{margin: 4}}>
49 |
56 |
57 |
65 | setCurrentServer(s)}
67 | onServerLongPress={(s: Server) => app.openServerContextMenu(s)}
68 | ordered={orderedServers}
69 | showDiscover={app.settings.get('app.instance') === DEFAULT_API_URL}
70 | />
71 |
72 |
73 |
78 |
79 |
80 |
89 |
99 |
109 |
119 |
120 | >
121 | );
122 | };
123 |
--------------------------------------------------------------------------------
/src/Theme.tsx:
--------------------------------------------------------------------------------
1 | import {Dimensions, StyleSheet} from 'react-native';
2 | export const themes = {
3 | Light: {
4 | background: '#F6F6F6',
5 | backgroundPrimary: '#FFFFFF',
6 | backgroundSecondary: '#F1F1F1',
7 | backgroundTertiary: '#4D4D4D',
8 | foregroundPrimary: '#000000',
9 | foregroundSecondary: '#F1F1F1',
10 | foregroundTertiary: '#4D4D4D',
11 | headerPrimary: '#F1F1F1',
12 | headerSecondary: '#F1F1F1',
13 | hover: '#0000002B',
14 | messageBox: '#F1F1F1',
15 | blockQuoteBackground: '#11111166',
16 | accentColor: '#1AD4B2',
17 | accentColorForeground: '#000000',
18 | contentType: 'light',
19 | statusOnline: '#3ABF7E',
20 | statusIdle: '#F39F00',
21 | statusBusy: '#F84848',
22 | statusFocus: '#4799F0',
23 | statusStreaming: '#977EFF',
24 | statusOffline: '#A5A5A5',
25 | statusInvisible: '#A5A5A5',
26 | error: '#ED4245',
27 | pingColor: '#FBFF0050',
28 | },
29 | Dark: {
30 | background: '#191919',
31 | backgroundPrimary: '#242424',
32 | backgroundSecondary: '#1E1E1E',
33 | backgroundTertiary: '#4D4D4D',
34 | foregroundPrimary: '#F6F6F6',
35 | foregroundSecondary: '#C8C8C8',
36 | foregroundTertiary: '#848484',
37 | headerPrimary: '#363636',
38 | headerSecondary: '#2D2D2D',
39 | hover: '#0000001A',
40 | messageBox: '#363636',
41 | blockQuoteBackground: '#11111166',
42 | accentColor: '#1AD4B2',
43 | accentColorForeground: '#000000',
44 | contentType: 'light',
45 | statusOnline: '#3ABF7E',
46 | statusIdle: '#F39F00',
47 | statusBusy: '#F84848',
48 | statusFocus: '#4799F0',
49 | statusStreaming: '#977EFF',
50 | statusOffline: '#A5A5A5',
51 | statusInvisible: '#A5A5A5',
52 | error: '#ED4245',
53 | pingColor: '#FBFF000F',
54 | },
55 | // "Solarized": {
56 | // backgroundPrimary: '#001a20',
57 | // backgroundSecondary: '#05252d',
58 | // blockQuoteBackground: '#11111166',
59 | // textPrimary: '#dddddd',
60 | // textSecondary: '#888888',
61 | // accentColor: '#1ad4b2',
62 | // accentColorForeground: '#000000',
63 | // contentType: 'light',
64 | // buttonBorderWidth: 0,
65 | // messageBoxBorderWidth: 0,
66 | // generalBorderWidth: 0,
67 | // buttonBorderColorActive: "#3333ff",
68 | // statusOnline: "#3abf7e",
69 | // statusIdle: "#f39f00",
70 | // statusBusy: "#f84848",
71 | // statusStreaming: "#977eff",
72 | // statusOffline: "#a5a5a5",
73 | // statusInvisible: "#a5a5a5",
74 | // pingColor: "#f84848",
75 | // pingColorForeground: "#ffffff"
76 | // },
77 | // "Vibrant Pink": {
78 | // backgroundPrimary: '#f9bae9',
79 | // backgroundSecondary: '#e99cd6',
80 | // blockQuoteBackground: '#11111166',
81 | // textPrimary: '#000000',
82 | // textSecondary: '#555555',
83 | // accentColor: '#1ad4b2',
84 | // accentColorForeground: '#000000',
85 | // contentType: 'dark',
86 | // buttonBorderWidth: 0,
87 | // messageBoxBorderWidth: 0,
88 | // generalBorderWidth: 0,
89 | // buttonBorderColorActive: "#3333ff",
90 | // statusOnline: "#3abf7e",
91 | // statusIdle: "#f39f00",
92 | // statusBusy: "#f84848",
93 | // statusStreaming: "#977eff",
94 | // statusOffline: "#a5a5a5",
95 | // statusInvisible: "#a5a5a5",
96 | // pingColor: "#f84848",
97 | // pingColorForeground: "#ffffff"
98 | // },
99 | // "AMOLED": {
100 | // backgroundPrimary: '#000000',
101 | // backgroundSecondary: '#000000',
102 | // foregroundPrimary: '#F6F6F6',
103 | // foregroundSecondary: '#C8C8C8',
104 | // foregroundTertiary: '#848484',
105 | // blockQuoteBackground: '#111111',
106 | // textPrimary: '#dddddd',
107 | // textSecondary: '#888888',
108 | // accentColor: '#1ad4b2',
109 | // accentColorForeground: '#000000',
110 | // contentType: 'light',
111 | // buttonBorderColor: "#ffffff99",
112 | // buttonBorderWidth: 1,
113 | // messageBoxBorderColor: "#ffffff99",
114 | // messageBoxBorderWidth: 1,
115 | // generalBorderColor: "#ffffff22",
116 | // generalBorderWidth: 1,
117 | // buttonBorderColorActive: "#3333ff",
118 | // statusOnline: "#3abf7e",
119 | // statusIdle: "#f39f00",
120 | // statusBusy: "#f84848",
121 | // statusStreaming: "#977eff",
122 | // statusOffline: "#a5a5a5",
123 | // statusInvisible: "#a5a5a5",
124 | // pingColor: "#f84848",
125 | // pingColorForeground: "#ffffff"
126 | // }
127 | };
128 | export var currentTheme = themes['Dark'];
129 | export var currentThemeName = 'Dark';
130 |
131 | export var styles: any;
132 | function refreshStyles() {
133 | styles = StyleSheet.create({
134 | outer: {
135 | flex: 1,
136 | backgroundColor: currentTheme.backgroundSecondary,
137 | },
138 | app: {
139 | flex: 1,
140 | backgroundColor: currentTheme.backgroundPrimary,
141 | },
142 | mainView: {
143 | flex: 1,
144 | backgroundColor: currentTheme.backgroundPrimary,
145 | },
146 | loginInput: {
147 | fontFamily: 'Inter',
148 | borderRadius: 8,
149 | padding: 3,
150 | paddingLeft: 10,
151 | paddingRight: 10,
152 | margin: 8,
153 | width: '80%',
154 | backgroundColor: currentTheme.backgroundSecondary,
155 | color: currentTheme.foregroundPrimary,
156 | },
157 | loggingInScreen: {
158 | flex: 1,
159 | alignItems: 'center',
160 | justifyContent: 'center',
161 | },
162 | flex: {
163 | flex: 1,
164 | },
165 | header: {
166 | fontWeight: 'bold',
167 | fontSize: 16,
168 | },
169 | headerv2: {
170 | fontWeight: 'bold',
171 | fontSize: 17.5,
172 | marginBottom: 10,
173 | },
174 | profileSubheader: {
175 | fontWeight: 'bold',
176 | color: currentTheme.foregroundSecondary,
177 | marginVertical: 5,
178 | },
179 | loadingHeader: {
180 | fontWeight: 'bold',
181 | textAlign: 'center',
182 | fontSize: 30,
183 | },
184 | remark: {
185 | color: currentTheme.foregroundSecondary,
186 | textAlign: 'center',
187 | fontSize: 16,
188 | marginTop: 5,
189 | paddingHorizontal: 30,
190 | },
191 | channelName: {
192 | flex: 1,
193 | fontWeight: 'bold',
194 | fontSize: 16,
195 | },
196 | link: {
197 | color: currentTheme.accentColor,
198 | textDecorationLine: 'underline',
199 | // fontWeight: 'bold',
200 | },
201 | leftView: {
202 | flex: 1,
203 | backgroundColor: currentTheme.backgroundSecondary,
204 | flexDirection: 'row',
205 | justifyContent: 'flex-start',
206 | },
207 | rightView: {
208 | flex: 1,
209 | backgroundColor: currentTheme.backgroundSecondary,
210 | },
211 | sheetBackground: {
212 | width: '100%',
213 | height: Dimensions.get('window').height * 0.75,
214 | top: '25%',
215 | padding: 15,
216 | backgroundColor: currentTheme.backgroundSecondary,
217 | },
218 | textDefault: {
219 | color: currentTheme.foregroundPrimary,
220 | },
221 | message: {
222 | width: '100%',
223 | flex: 1,
224 | flexDirection: 'row',
225 | },
226 | messageGrouped: {
227 | paddingLeft: 35,
228 | width: '100%',
229 | },
230 | messageInner: {
231 | flex: 1,
232 | paddingLeft: 10,
233 | },
234 | messageAvatar: {
235 | width: 35,
236 | height: 35,
237 | borderRadius: 100000,
238 | },
239 | messageAvatarReply: {
240 | width: 15,
241 | height: 15,
242 | borderRadius: 100000,
243 | },
244 | messageUsernameReply: {
245 | marginHorizontal: 3,
246 | },
247 | typingBar: {
248 | height: 26,
249 | paddingLeft: 6,
250 | padding: 3,
251 | backgroundColor: currentTheme.backgroundSecondary,
252 | borderBottomColor: currentTheme.backgroundPrimary,
253 | borderBottomWidth: 1,
254 | flexDirection: 'row',
255 | },
256 | messageUsername: {
257 | fontWeight: 'bold',
258 | },
259 | serverButton: {
260 | borderRadius: 5000,
261 | width: 48,
262 | height: 48,
263 | margin: 4,
264 | backgroundColor: currentTheme.backgroundPrimary,
265 | overflow: 'hidden',
266 | },
267 | serverButtonInitials: {
268 | fontWeight: 'bold',
269 | textAlign: 'center',
270 | marginTop: '30%',
271 | },
272 | serverIcon: {
273 | width: 48,
274 | height: 48,
275 | },
276 | serverList: {
277 | width: 60,
278 | flexShrink: 1,
279 | backgroundColor: currentTheme.background,
280 | paddingVertical: 4,
281 | },
282 | channelList: {
283 | flexGrow: 1000,
284 | flex: 1000,
285 | },
286 | channelButton: {
287 | marginHorizontal: 8,
288 | borderRadius: 8,
289 | flexDirection: 'row',
290 | alignItems: 'center',
291 | },
292 | button: {
293 | padding: 10,
294 | paddingHorizontal: 16,
295 | borderRadius: 8,
296 | backgroundColor: currentTheme.headerSecondary,
297 | margin: 5,
298 | justifyContent: 'center',
299 | alignItems: 'center',
300 | flexDirection: 'row',
301 | },
302 | buttonSecondary: {
303 | padding: 10,
304 | paddingHorizontal: 16,
305 | borderRadius: 8,
306 | backgroundColor: currentTheme.backgroundPrimary,
307 | margin: 5,
308 | justifyContent: 'center',
309 | alignItems: 'center',
310 | flexDirection: 'row',
311 | },
312 | channelButtonSelected: {
313 | backgroundColor: currentTheme.hover,
314 | },
315 | iconContainer: {
316 | alignItems: 'center',
317 | justifyContent: 'center',
318 | width: 30,
319 | height: 30,
320 | marginRight: 8,
321 | },
322 | messagesView: {
323 | padding: 10,
324 | flex: 1,
325 | },
326 | messageBoxInner: {
327 | flexDirection: 'row',
328 | alignItems: 'center',
329 | minHeight: 50,
330 | paddingHorizontal: 8,
331 | },
332 | messageBoxOuter: {
333 | backgroundColor: currentTheme.messageBox,
334 | overflow: 'hidden',
335 | },
336 | sendButton: {
337 | margin: 3,
338 | justifyContent: 'center',
339 | alignItems: 'center',
340 | },
341 | headerIcon: {
342 | margin: 5,
343 | marginRight: 10,
344 | justifyContent: 'center',
345 | alignItems: 'center',
346 | },
347 | messageBox: {
348 | color: currentTheme.foregroundPrimary,
349 | paddingLeft: 10,
350 | padding: 6,
351 | flex: 1,
352 | fontFamily: 'Open Sans',
353 | },
354 | serverName: {
355 | margin: 10,
356 | marginTop: 13,
357 | marginBottom: 9,
358 | fontSize: 18,
359 | fontWeight: 'bold',
360 | },
361 | channelHeader: {
362 | height: 50,
363 | backgroundColor: currentTheme.headerPrimary,
364 | alignItems: 'center',
365 | paddingLeft: 20,
366 | flexDirection: 'row',
367 | },
368 | messageContentReply: {
369 | height: 20,
370 | marginLeft: 4,
371 | },
372 | actionTile: {
373 | height: 40,
374 | width: '100%',
375 | alignItems: 'center',
376 | flexDirection: 'row',
377 | backgroundColor: currentTheme.backgroundPrimary,
378 | borderRadius: 8,
379 | paddingLeft: 10,
380 | paddingRight: 10,
381 | marginTop: 5,
382 | },
383 | messageBoxBar: {
384 | padding: 4,
385 | borderBottomColor: currentTheme.backgroundPrimary,
386 | borderBottomWidth: 1,
387 | flexDirection: 'row',
388 | },
389 | attachmentsBar: {
390 | padding: 8,
391 | borderBottomColor: currentTheme.backgroundPrimary,
392 | borderBottomWidth: 1,
393 | flexDirection: 'column',
394 | },
395 | repliedMessagePreviews: {
396 | paddingTop: 4,
397 | },
398 | timestamp: {
399 | fontSize: 12,
400 | color: currentTheme.foregroundTertiary,
401 | position: 'relative',
402 | top: 2,
403 | left: 2,
404 | },
405 | });
406 | }
407 | export function setTheme(themeName: any) {
408 | currentThemeName = themeName;
409 | currentTheme = themes[themeName] ?? themes.Dark;
410 | refreshStyles();
411 | }
412 | setTheme('Dark');
413 |
--------------------------------------------------------------------------------
/src/components/NetworkIndicator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {Client} from 'revolt.js';
6 |
7 | import {currentTheme} from '../Theme';
8 | import {Text} from './common/atoms';
9 |
10 | export const NetworkIndicator = observer(({client}: {client: Client}) => {
11 | if (!client.user?.online) {
12 | return (
13 |
24 |
30 | Connection lost
31 |
32 |
33 | );
34 | }
35 | return <>>;
36 | });
37 |
--------------------------------------------------------------------------------
/src/components/Notification.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {Message} from 'revolt.js';
6 |
7 | import {Avatar, Username} from '../Profile';
8 | import {currentTheme} from '../Theme';
9 | import {Text} from './common/atoms';
10 | import {MarkdownView} from './common/MarkdownView';
11 | import {parseRevoltNodes} from '../lib/utils';
12 |
13 | export const Notification = observer(
14 | ({message, setState}: {message: Message | null; setState: any}) => {
15 | if (message) {
16 | return (
17 | setState()}>
20 |
21 |
31 |
32 |
33 |
34 |
38 |
39 | {' '}
40 | ({message.channel?.name ?? message.channel?._id})
41 |
42 |
43 | {message.content ? (
44 |
45 | {parseRevoltNodes(
46 | message.content.length > 200
47 | ? message.content.slice(0, 200) + '...'
48 | : message.content,
49 | )}
50 |
51 | ) : (
52 |
53 | Tap to view message
54 |
55 | )}
56 |
57 |
58 |
59 |
60 | );
61 | }
62 | return <>>;
63 | },
64 | );
65 |
--------------------------------------------------------------------------------
/src/components/common/MarkdownView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import spoilerPlugin from '@traptitech/markdown-it-spoiler';
4 | import Markdown, {hasParents, MarkdownIt} from 'react-native-markdown-display';
5 |
6 | import {openUrl} from '../../Generic';
7 | import {currentTheme} from '../../Theme';
8 | import {Text} from './atoms';
9 |
10 | const defaultMarkdownIt = MarkdownIt({typographer: true, linkify: true})
11 | .disable(['image'])
12 | .use(spoilerPlugin);
13 |
14 | const spoilerStyle = {
15 | hiddenSpoiler: {
16 | backgroundColor: '#000',
17 | color: 'transparent',
18 | },
19 | revealedSpoiler: {
20 | backgroundColor: currentTheme.backgroundSecondary,
21 | color: currentTheme.foregroundPrimary,
22 | },
23 | };
24 |
25 | const SpoilerContext = React.createContext();
26 | const Spoiler = ({content}) => {
27 | const [revealed, setRevealed] = React.useState(false);
28 | return (
29 |
30 | setRevealed(!revealed)}>{content}
31 |
32 | );
33 | };
34 |
35 | // the text and code_inline rules are the same as the built-in ones,
36 | // except with spoiler support
37 | const spoilerRule = {
38 | spoiler: (node, children) => ,
39 | text: (node, children, parent, styles, inheritedStyles = {}) => {
40 | if (hasParents(parent, 'spoiler')) {
41 | return (
42 |
43 | {isRevealed => (
44 |
52 | {node.content}
53 |
54 | )}
55 |
56 | );
57 | }
58 |
59 | return (
60 |
61 | {node.content}
62 |
63 | );
64 | },
65 | code_inline: (node, children, parent, styles, inheritedStyles = {}) => {
66 | if (hasParents(parent, 'spoiler')) {
67 | return (
68 |
69 | {isRevealed => (
70 |
78 | {node.content}
79 |
80 | )}
81 |
82 | );
83 | }
84 |
85 | return (
86 |
87 | {node.content}
88 |
89 | );
90 | },
91 | };
92 |
93 | export const MarkdownView = (props: any) => {
94 | let newProps = {...props};
95 | if (!newProps.onLinkPress) {
96 | newProps = Object.assign({onLinkPress: openUrl}, newProps);
97 | }
98 | if (!newProps.markdownit) {
99 | newProps = Object.assign({markdownit: defaultMarkdownIt}, newProps);
100 | }
101 | if (!newProps.rules) {
102 | newProps = Object.assign({rules: spoilerRule}, newProps);
103 | }
104 | if (!newProps.style) {
105 | newProps = Object.assign({style: {}}, newProps);
106 | }
107 | if (!newProps.style.body) {
108 | newProps.style = Object.assign({body: {}}, newProps.style);
109 | }
110 | newProps.style.body = Object.assign(
111 | {color: currentTheme.foregroundPrimary},
112 | newProps.style.body,
113 | );
114 | if (!newProps.style.paragraph) {
115 | newProps.style = Object.assign({paragraph: {}}, newProps.style);
116 | }
117 | newProps.style.paragraph = Object.assign(
118 | {color: currentTheme.foregroundPrimary, marginTop: -3, marginBottom: 2},
119 | newProps.style.paragraph,
120 | );
121 | if (!newProps.style.link) {
122 | newProps.style = Object.assign({link: {}}, newProps.style);
123 | }
124 | newProps.style.link = Object.assign(
125 | {color: currentTheme.accentColor},
126 | newProps.style.link,
127 | );
128 | if (!newProps.style.code_inline) {
129 | newProps.style = Object.assign({code_inline: {}}, newProps.style);
130 | }
131 | newProps.style.code_inline = Object.assign(
132 | {
133 | color: currentTheme.foregroundPrimary,
134 | backgroundColor: currentTheme.backgroundSecondary,
135 | },
136 | newProps.style.code_inline,
137 | );
138 | if (!newProps.style.fence) {
139 | newProps.style = Object.assign({fence: {}}, newProps.style);
140 | }
141 | newProps.style.fence = Object.assign(
142 | {
143 | color: currentTheme.foregroundPrimary,
144 | backgroundColor: currentTheme.backgroundSecondary,
145 | borderWidth: 0,
146 | },
147 | newProps.style.fence,
148 | );
149 | if (!newProps.style.code_block) {
150 | newProps.style = Object.assign({code_block: {}}, newProps.style);
151 | }
152 | newProps.style.code_block = Object.assign(
153 | {
154 | borderColor: currentTheme.foregroundPrimary,
155 | color: currentTheme.foregroundPrimary,
156 | backgroundColor: currentTheme.backgroundSecondary,
157 | },
158 | newProps.style.code_block,
159 | );
160 | if (!newProps.style.blockquote) {
161 | newProps.style = Object.assign({blockquote: {}}, newProps.style);
162 | }
163 | newProps.style.blockquote = Object.assign(
164 | {
165 | marginBottom: 2,
166 | paddingVertical: 6,
167 | borderRadius: 4,
168 | borderColor: currentTheme.foregroundPrimary,
169 | color: currentTheme.foregroundPrimary,
170 | backgroundColor: currentTheme.blockQuoteBackground,
171 | },
172 | newProps.style.block_quote,
173 | );
174 | try {
175 | return {newProps.children};
176 | } catch (e) {
177 | return Error rendering markdown;
178 | }
179 | };
180 |
--------------------------------------------------------------------------------
/src/components/common/atoms/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity} from 'react-native';
3 |
4 | import {styles} from '../../../Theme';
5 |
6 | type ButtonProps = {
7 | children?: any;
8 | backgroundColor?: string;
9 | onPress?: any;
10 | onLongPress?: any;
11 | delayLongPress?: number;
12 | style?: any;
13 | };
14 |
15 | export function Button({
16 | children,
17 | backgroundColor,
18 | onPress,
19 | onLongPress,
20 | delayLongPress,
21 | style,
22 | ...props
23 | }: ButtonProps) {
24 | return (
25 |
31 | {children}
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/common/atoms/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {currentTheme} from '../../../Theme';
6 |
7 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
8 |
9 | import {Text} from './Text';
10 |
11 | export const Checkbox = observer(
12 | ({value, callback}: {value: boolean; callback: any}) => {
13 | return (
14 |
26 |
32 | {value ? (
33 |
38 | ) : null}
39 |
40 |
41 | );
42 | },
43 | );
44 |
--------------------------------------------------------------------------------
/src/components/common/atoms/ContextButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity} from 'react-native';
3 |
4 | import {styles} from '../../../Theme';
5 |
6 | type ButtonProps = {
7 | children?: any;
8 | backgroundColor?: string;
9 | onPress?: any;
10 | onLongPress?: any;
11 | delayLongPress?: number;
12 | style?: any;
13 | };
14 |
15 | export function ContextButton({
16 | children,
17 | backgroundColor,
18 | onPress,
19 | onLongPress,
20 | delayLongPress,
21 | style,
22 | ...props
23 | }: ButtonProps) {
24 | return (
25 |
35 | {children}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/common/atoms/CopyIDButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 |
4 | import Clipboard from '@react-native-clipboard/clipboard';
5 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
6 |
7 | import {currentTheme, styles} from '../../../Theme';
8 | import {ContextButton} from './ContextButton';
9 | import {Text} from './Text';
10 |
11 | export const CopyIDButton = ({id}: {id: string}) => {
12 | return (
13 | {
16 | Clipboard.setString(id);
17 | }}>
18 |
19 |
24 |
25 |
26 | Copy ID ({id})
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/common/atoms/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity} from 'react-native';
3 |
4 | import {openUrl} from '../../../Generic';
5 | import {styles} from '../../../Theme';
6 | import {Text} from './Text';
7 |
8 | type LinkProps = {
9 | link: string;
10 | label: string;
11 | style?: any;
12 | };
13 |
14 | export const Link = ({link, label, style}: LinkProps) => {
15 | let finalStyle = styles.link;
16 | if (style) {
17 | finalStyle = Object.assign({}, finalStyle, style);
18 | }
19 |
20 | return (
21 | openUrl(link)}>
22 | {label}
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/common/atoms/Text.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactNative from 'react-native';
3 |
4 | import {currentTheme, styles} from '../../../Theme';
5 |
6 | export const Text = (props: any) => {
7 | let newProps = {...props};
8 | if (!props.style) {
9 | newProps = Object.assign({style: {}}, newProps);
10 | }
11 | const font = props.useInter ? 'Inter' : 'Open Sans';
12 | if (props.type) {
13 | switch (props.type) {
14 | case 'header':
15 | newProps.style = Object.assign({}, styles.headerv2, newProps.style);
16 | break;
17 | default:
18 | break;
19 | }
20 | }
21 | if (props.colour) {
22 | newProps.style.color = props.colour;
23 | }
24 | newProps.style = Object.assign(
25 | {
26 | color: currentTheme.foregroundPrimary,
27 | flexWrap: 'wrap',
28 | fontFamily: font,
29 | },
30 | newProps.style,
31 | );
32 | return {newProps.children};
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/common/atoms/index.tsx:
--------------------------------------------------------------------------------
1 | export {Button} from './Button';
2 | export {Checkbox} from './Checkbox';
3 | export {ContextButton} from './ContextButton';
4 | export {CopyIDButton} from './CopyIDButton';
5 | export {Link} from './Link';
6 | export {Text} from './Text';
7 |
--------------------------------------------------------------------------------
/src/components/common/messaging/InviteEmbed.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react';
2 | import {View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {API, Message} from 'revolt.js';
6 |
7 | import {GeneralAvatar, app, client} from '../../../Generic';
8 | import {currentTheme} from '../../../Theme';
9 | import {Button, Text} from '../atoms';
10 |
11 | export const InviteEmbed = observer(
12 | ({message, invite}: {message: Message; invite: string}) => {
13 | const [invObject, setInvObject] = React.useState({} as API.InviteResponse);
14 | const [error, setError] = React.useState('');
15 |
16 | useEffect(() => {
17 | async function getInv() {
18 | try {
19 | const i = await client.fetchInvite(invite);
20 | setInvObject(i);
21 | } catch (e) {
22 | const errorType = (e as string).match('404')
23 | ? 'notFound'
24 | : (e as string).match('429')
25 | ? 'rateLimited'
26 | : 'otherError';
27 | if (errorType === 'otherError') {
28 | console.warn(
29 | `[INVITEEMBED] Unrecognised error fetching invite: ${e}`,
30 | );
31 | } else {
32 | console.log(`[INVITEEMBED] Error fetching invite: ${e}`);
33 | }
34 | setError(errorType);
35 | }
36 | }
37 | getInv();
38 | }, [invite]);
39 |
40 | return error ? (
41 |
48 | error: {error}
49 |
50 | ) : invObject.type === 'Server' ? (
51 |
58 |
61 |
66 | {message.author?.username}
67 | {' '}
68 | invited you to a server
69 |
70 |
71 |
76 |
83 |
84 | {invObject.server_name}
85 |
86 |
87 | {invObject?.member_count}{' '}
88 | {invObject?.member_count === 1 ? 'member' : 'members'}
89 |
90 |
91 |
92 |
105 |
106 | ) : (
107 | Invite: {invite}
108 | );
109 | },
110 | );
111 |
--------------------------------------------------------------------------------
/src/components/common/messaging/MessageEmbed.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Dimensions, Pressable, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import FastImage from 'react-native-fast-image';
6 |
7 | import {API} from 'revolt.js';
8 |
9 | import {app, client} from '../../../Generic';
10 | import {currentTheme} from '../../../Theme';
11 | import {MarkdownView} from '../MarkdownView';
12 | import {Link, Text} from '../atoms';
13 | const Image = FastImage;
14 |
15 | export const MessageEmbed = observer((eRaw: API.Embed) => {
16 | // @ts-expect-error This seems to be necessary even though it clashses with the API types
17 | const e = eRaw.embed;
18 | switch (e.type) {
19 | case 'Text':
20 | case 'Website':
21 | return (
22 |
28 | {e.type === 'Website' && e.site_name ? (
29 |
32 | {e.site_name}
33 |
34 | ) : null}
35 | {e.title && e.url ? (
36 |
41 | ) : (
42 |
47 | {e.title}
48 |
49 | )}
50 | {e.description ? {e.description} : null}
51 | {(() => {
52 | if (e.type === 'Website' && e.image) {
53 | let width = e.image.width;
54 | let height = e.image.height;
55 | if (width > Dimensions.get('screen').width - 82) {
56 | let sizeFactor = (Dimensions.get('screen').width - 82) / width;
57 | width = width * sizeFactor;
58 | height = height * sizeFactor;
59 | }
60 | return (
61 | app.openImage(e.image?.url)}>
62 |
71 |
72 | );
73 | }
74 | })()}
75 |
76 | );
77 | case 'Image':
78 | // if (e.image?.size === "Large") {}
79 | let width = e.width;
80 | let height = e.height;
81 | if (width > Dimensions.get('screen').width - 75) {
82 | let sizeFactor = (Dimensions.get('screen').width - 75) / width;
83 | width = width * sizeFactor;
84 | height = height * sizeFactor;
85 | }
86 | return (
87 | app.openImage(client.proxyFile(e.url))}>
88 |
97 |
98 | );
99 | case 'Video':
100 | return (
101 |
102 | Video embeds are not currently supported
103 |
104 | );
105 | case 'None':
106 | default:
107 | console.log(JSON.stringify(e));
108 | return (
109 |
110 | embed - type: {e.type === 'None' ? 'none' : e.type ?? 'how'}, other
111 | info:
112 | {JSON.stringify(e)}
113 |
114 | );
115 | }
116 | });
117 |
--------------------------------------------------------------------------------
/src/components/common/messaging/ReplyMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 |
4 | import {Message} from 'revolt.js';
5 |
6 | import {Avatar, Username} from '../../../Profile';
7 | import {styles} from '../../../Theme';
8 | import {Text} from '../atoms';
9 |
10 | type ReplyProps = {
11 | message?: Message;
12 | mention?: boolean;
13 | style?: any;
14 | };
15 |
16 | export const ReplyMessage = (props: ReplyProps) => {
17 | if (!props.message?.system) {
18 | return (
19 |
20 | ↱
21 | {props.message ? (
22 | props.message.author ? (
23 | <>
24 |
30 | {props.mention ? '@' : ''}
31 |
36 |
37 | {props.message.content?.split('\n').join(' ')}
38 |
39 | >
40 | ) : null
41 | ) : (
42 | Message not loaded
43 | )}
44 |
45 | );
46 | } else {
47 | return <>>;
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/common/messaging/index.ts:
--------------------------------------------------------------------------------
1 | export {InviteEmbed} from './InviteEmbed';
2 | export {Message} from './Message';
3 | export {MessageEmbed} from './MessageEmbed';
4 | export {ReplyMessage} from './ReplyMessage';
--------------------------------------------------------------------------------
/src/components/navigation/ChannelHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, TouchableOpacity} from 'react-native';
3 |
4 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
5 |
6 | import {app} from '../../Generic';
7 | import {currentTheme, styles} from '../../Theme';
8 |
9 | export const ChannelHeader = ({children}: {children: any}) => {
10 | return (
11 |
12 | {
15 | app.openLeftMenu();
16 | }}>
17 |
18 |
23 |
24 |
25 | {children}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/navigation/ChannelList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import FastImage from 'react-native-fast-image';
6 |
7 | import {Channel, Server} from 'revolt.js';
8 |
9 | import {ChannelButton, app, client} from '../../Generic';
10 | import {styles} from '../../Theme';
11 | import {Text} from '../common/atoms';
12 | const Image = FastImage;
13 |
14 | type ChannelListProps = {
15 | onChannelClick: any;
16 | currentChannel: Channel;
17 | currentServer: Server;
18 | };
19 |
20 | // thanks a lot, revolt.js 🙄
21 | type Category = {
22 | id: string;
23 | title: string;
24 | channels: string[];
25 | };
26 |
27 | const ServerChannelListCategory = observer(
28 | ({category, props}: {category: Category; props: ChannelListProps}) => {
29 | const [isVisible, setIsVisible] = React.useState(true);
30 | return (
31 |
32 | {
35 | setIsVisible(!isVisible);
36 | }}>
37 |
43 | {category.title?.toUpperCase()}
44 |
45 |
46 | {isVisible &&
47 | category.channels.map((cid: string) => {
48 | let c = client.channels.get(cid);
49 | if (c) {
50 | return (
51 | {
55 | props.onChannelClick(c);
56 | }}
57 | selected={props.currentChannel?._id === c._id}
58 | />
59 | );
60 | }
61 | })}
62 |
63 | );
64 | },
65 | );
66 |
67 | const ServerChannelList = observer((props: ChannelListProps) => {
68 | const [processedChannels, setProcessedChannels] = React.useState(
69 | [] as string[],
70 | );
71 | const [res, setRes] = React.useState([] as JSX.Element[] | undefined);
72 |
73 | React.useEffect(() => {
74 | let categories = props.currentServer.categories?.map(c => {
75 | const element = (
76 |
81 | );
82 | for (const cnl of c.channels) {
83 | if (!processedChannels.includes(cnl)) {
84 | let newProcessedChannels = processedChannels;
85 | newProcessedChannels.push(cnl);
86 | setProcessedChannels(newProcessedChannels);
87 | }
88 | }
89 | return element;
90 | });
91 | setRes(categories);
92 | }, [props, processedChannels]);
93 |
94 | return (
95 | <>
96 | {props.currentServer.banner ? (
97 |
100 | app.openServerContextMenu(props.currentServer)}>
102 | {props.currentServer.name}
103 |
104 |
105 | ) : (
106 | app.openServerContextMenu(props.currentServer)}>
108 | {props.currentServer.name}
109 |
110 | )}
111 |
112 | {props.currentServer.channels.map(c => {
113 | if (c) {
114 | if (!processedChannels.includes(c._id)) {
115 | return (
116 | {
120 | props.onChannelClick(c);
121 | }}
122 | selected={props.currentChannel?._id === c._id}
123 | />
124 | );
125 | }
126 | }
127 | })}
128 | {res}
129 | >
130 | );
131 | });
132 |
133 | export const ChannelList = observer((props: ChannelListProps) => {
134 | return (
135 | <>
136 | {!props.currentServer ? (
137 | <>
138 |
145 | Direct Messages
146 |
147 |
148 | {
150 | props.onChannelClick(null);
151 | }}
152 | key={'home'}
153 | channel={'Home'}
154 | selected={props.currentChannel === null}
155 | />
156 |
157 | {
159 | props.onChannelClick('friends');
160 | }}
161 | key={'friends'}
162 | channel={'Friends'}
163 | selected={props.currentChannel === 'friends'}
164 | />
165 |
166 | {
168 | props.onChannelClick(await client.user?.openDM());
169 | }}
170 | key={'notes'}
171 | channel={'Saved Notes'}
172 | selected={props.currentChannel?.channel_type === 'SavedMessages'}
173 | />
174 |
175 | {__DEV__ ? (
176 | {
178 | await client.user?.openDM();
179 | props.onChannelClick('debug');
180 | }}
181 | key={'debugChannel'}
182 | channel={'Debug'}
183 | selected={props.currentChannel === 'debug'}
184 | />
185 | ) : null}
186 |
187 | {[...client.channels.values()]
188 | .filter(
189 | c =>
190 | c.channel_type === 'DirectMessage' ||
191 | c.channel_type === 'Group',
192 | )
193 | .sort((c1, c2) => c2.updatedAt - c1.updatedAt)
194 | .map(dm => (
195 | {
197 | props.onChannelClick(dm);
198 | }}
199 | onLongPress={() => {
200 | app.openProfile(dm.recipient);
201 | }}
202 | delayLongPress={750}
203 | key={dm._id}
204 | channel={dm}
205 | style={props.currentChannel?._id === dm._id}
206 | />
207 | ))}
208 | >
209 | ) : null}
210 | {props.currentServer ? (
211 |
216 | ) : null}
217 | >
218 | );
219 | });
220 |
--------------------------------------------------------------------------------
/src/components/navigation/ServerList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import FastImage from 'react-native-fast-image';
6 | import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
7 |
8 | import {client, openUrl} from '../../Generic';
9 | import {currentTheme, styles} from '../../Theme';
10 | import {Text} from '../common/atoms';
11 | import {DEFAULT_MAX_SIDE} from '../../lib/consts';
12 |
13 | const Image = FastImage;
14 |
15 | export const ServerList = observer(
16 | ({
17 | onServerPress,
18 | onServerLongPress,
19 | filter,
20 | ordered,
21 | showUnread = true,
22 | showDiscover = true,
23 | }: {
24 | onServerPress: any;
25 | onServerLongPress?: any;
26 | ordered?: string[];
27 | filter?: any;
28 | showUnread?: boolean;
29 | showDiscover?: boolean;
30 | }) => {
31 | let servers = [...client.servers.values()];
32 | if (filter) {
33 | servers = servers.filter(filter);
34 | }
35 | if (ordered) {
36 | servers.sort((server1, server2) => {
37 | return ordered.indexOf(server1._id) - ordered.indexOf(server2._id);
38 | });
39 | }
40 | return (
41 |
42 | {servers.map(s => {
43 | let iconURL = s.generateIconURL();
44 | let pings = s.getMentions().length;
45 | let initials = '';
46 | for (const word of s.name.split(' ')) {
47 | initials += word.charAt(0);
48 | }
49 | return (
50 |
51 | {showUnread && s.getMentions().length > 0 ? (
52 |
65 |
68 | {pings > 9 ? '9+' : pings}
69 |
70 |
71 | ) : showUnread && s.isUnread() ? (
72 |
86 | ) : null}
87 | {
89 | onServerPress(s);
90 | }}
91 | onLongPress={() => {
92 | onServerLongPress(s);
93 | }}
94 | key={s._id}
95 | style={styles.serverButton}>
96 | {iconURL ? (
97 |
102 | ) : (
103 |
106 | {initials}
107 |
108 | )}
109 |
110 |
111 | );
112 | })}
113 | {showDiscover ? (
114 | <>
115 |
123 | {
125 | openUrl('https://rvlt.gg/discover');
126 | }}
127 | key={'serverlist-discover'}
128 | style={styles.serverButton}>
129 |
130 |
135 |
136 |
137 | >
138 | ) : null}
139 |
140 | );
141 | },
142 | );
143 |
--------------------------------------------------------------------------------
/src/components/navigation/UserList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {User} from 'revolt.js';
6 |
7 | import {app} from '../../Generic';
8 | import {MiniProfile} from '../../Profile';
9 | import {currentTheme} from '../../Theme';
10 |
11 | import {Button} from '../common/atoms';
12 |
13 | export const UserList = observer(({users}: {users: User[]}) => {
14 | return (
15 | <>
16 | {users.map(u => (
17 |
33 | ))}
34 | >
35 | );
36 | });
37 |
--------------------------------------------------------------------------------
/src/components/pages/FriendsPage.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {ScrollView, TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {client, ChannelIcon, app} from '../../Generic';
6 | import {MiniProfile} from '../../Profile';
7 | import {styles} from '../../Theme';
8 | import {ChannelHeader} from '../navigation/ChannelHeader';
9 | import {Button, Text} from '../common/atoms';
10 |
11 | type DisplayStates = {
12 | onlineFriends: boolean;
13 | offlineFriends: boolean;
14 | incoming: boolean;
15 | outgoing: boolean;
16 | blocked: boolean;
17 | };
18 |
19 | export const FriendsPage = observer(() => {
20 | const sectionHeaderStyles = {
21 | fontWeight: 'bold',
22 | margin: 5,
23 | marginLeft: 10,
24 | marginTop: 10,
25 | };
26 |
27 | const [displayState, setDisplayState] = useState({
28 | onlineFriends: true,
29 | offlineFriends: true,
30 | outgoing: true,
31 | incoming: true,
32 | blocked: true,
33 | } as DisplayStates);
34 |
35 | // sort the user list, then filter for friends/blocked users/outgoing/incoming friend requests and render the buttons
36 | const sortedUsers = [...client.users.values()].sort((f1, f2) =>
37 | f1.username.localeCompare(f2.username),
38 | );
39 |
40 | const onlineFriends = [] as JSX.Element[];
41 | const offlineFriends = [] as JSX.Element[];
42 | const incoming = [] as JSX.Element[];
43 | const outgoing = [] as JSX.Element[];
44 | const blocked = [] as JSX.Element[];
45 |
46 | for (const u of sortedUsers) {
47 | switch (u.relationship) {
48 | case 'Friend':
49 | (u.online ? onlineFriends : offlineFriends).push(
50 | ,
56 | );
57 | break;
58 | case 'Incoming':
59 | incoming.push(
60 | ,
66 | );
67 | break;
68 | case 'Outgoing':
69 | outgoing.push(
70 | ,
76 | );
77 | break;
78 | case 'Blocked':
79 | blocked.push(
80 | ,
86 | );
87 | break;
88 | default:
89 | break;
90 | }
91 | }
92 |
93 | return (
94 |
95 |
96 |
97 |
98 |
99 | Friends
100 |
101 |
102 | {/* incoming requests */}
103 |
105 | setDisplayState({
106 | ...displayState,
107 | incoming: !displayState.incoming,
108 | })
109 | }>
110 |
111 | INCOMING REQUESTS - {incoming.length}
112 |
113 |
114 | {displayState.incoming && (
115 |
116 | {incoming.length > 0 ? (
117 | incoming
118 | ) : (
119 | No incoming requests
120 | )}
121 |
122 | )}
123 |
124 | {/* outgoing requests */}
125 |
127 | setDisplayState({
128 | ...displayState,
129 | outgoing: !displayState.outgoing,
130 | })
131 | }>
132 |
133 | OUTGOING REQUESTS - {outgoing.length}
134 |
135 |
136 | {displayState.outgoing && (
137 |
138 | {outgoing.length > 0 ? (
139 | outgoing
140 | ) : (
141 | No outgoing requests
142 | )}
143 |
144 | )}
145 |
146 | {/* online friends */}
147 |
149 | setDisplayState({
150 | ...displayState,
151 | onlineFriends: !displayState.onlineFriends,
152 | })
153 | }>
154 |
155 | ONLINE FRIENDS - {onlineFriends.length}
156 |
157 |
158 | {displayState.onlineFriends && (
159 |
160 | {onlineFriends.length > 0 ? (
161 | onlineFriends
162 | ) : (
163 | No online friends
164 | )}
165 |
166 | )}
167 |
168 | {/* offline friends */}
169 |
171 | setDisplayState({
172 | ...displayState,
173 | offlineFriends: !displayState.offlineFriends,
174 | })
175 | }>
176 |
177 | OFFLINE FRIENDS - {offlineFriends.length}
178 |
179 |
180 | {displayState.offlineFriends && (
181 |
182 | {offlineFriends.length > 0 ? (
183 | offlineFriends
184 | ) : (
185 | No offline friends
186 | )}
187 |
188 | )}
189 |
190 | {/* blocked users */}
191 |
193 | setDisplayState({
194 | ...displayState,
195 | blocked: !displayState.blocked,
196 | })
197 | }>
198 | BLOCKED - {blocked.length}
199 |
200 | {displayState.blocked && (
201 |
202 | {blocked.length > 0 ? (
203 | blocked
204 | ) : (
205 | No blocked users
206 | )}
207 |
208 | )}
209 |
210 |
211 | );
212 | });
213 |
--------------------------------------------------------------------------------
/src/components/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {app, ChannelIcon, client, openUrl} from '../../Generic';
6 | import {Avatar, Username} from '../../Profile';
7 | import {
8 | SPECIAL_DATES,
9 | SPECIAL_DATE_OBJECTS,
10 | SPECIAL_SERVERS,
11 | } from '../../lib/consts';
12 | import {styles} from '../../Theme';
13 | import {ChannelHeader} from '../navigation/ChannelHeader';
14 | import {Button, Text} from '../common/atoms';
15 |
16 | export const HomePage = observer(() => {
17 | // holiday emoji
18 | const rawDate = new Date();
19 | const rawMonth = rawDate.getMonth() + 1;
20 | const date = `${rawDate.getDate()}/${rawMonth}`;
21 | const month = `month${rawMonth}`;
22 |
23 | let holidayEmoji = SPECIAL_DATES.includes(date) ? (
24 | {
26 | // @ts-expect-error TODO: figure out types for this
27 | openUrl(SPECIAL_DATE_OBJECTS[date].link);
28 | }}>
29 |
33 | {
34 | // @ts-expect-error as above
35 | SPECIAL_DATE_OBJECTS[date].emoji
36 | }
37 |
38 |
39 | ) : SPECIAL_DATES.includes(month) ? (
40 | {
42 | // @ts-expect-error as above
43 | openUrl(SPECIAL_DATE_OBJECTS[month].link);
44 | }}>
45 |
48 | {
49 | // @ts-expect-error as above
50 | SPECIAL_DATE_OBJECTS[month].emoji
51 | }
52 |
53 |
54 | ) : null;
55 |
56 | return (
57 | <>
58 |
59 |
60 |
61 |
62 | Home
63 |
64 |
71 | {
78 | let user = client.users.get(client.user?._id!);
79 | if (user) {
80 | app.openProfile(user);
81 | }
82 | }}>
83 |
84 |
85 |
86 |
87 |
88 | RVMob
89 |
90 |
93 | Swipe from the left of the screen or press the three lines icon to see
94 | your servers and messages!
95 |
96 |
105 |
116 |
122 | {app.settings.get('ui.home.holidays') ? holidayEmoji : null}
123 |
124 | >
125 | );
126 | });
127 |
--------------------------------------------------------------------------------
/src/components/pages/LoginSettingsPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TextInput, View} from 'react-native';
3 |
4 | import {app} from '../../Generic';
5 | import {currentTheme, styles} from '../../Theme';
6 | import {Button, Text} from '../common/atoms';
7 |
8 | export const LoginSettingsPage = ({state}: {state: any}) => {
9 | const [instanceURL, setInstanceURL] = React.useState(
10 | (app.settings.get('app.instance') as string) ?? '',
11 | );
12 | const [testResponse, setTestResponse] = React.useState(null as string | null);
13 |
14 | const [saved, setSaved] = React.useState(false);
15 |
16 | async function testURL(url: string, returnIfSuccessful: boolean) {
17 | try {
18 | console.log('[LOGINSETTINGS] Testing URL...');
19 | const req = await fetch(url);
20 | console.log('[LOGINSETTINGS] Request succeeded!');
21 | const data = await req.json();
22 | if (data.revolt && data.features) {
23 | console.log('[LOGINSETTINGS] This looks like a Revolt instance!');
24 | setTestResponse('valid');
25 | if (returnIfSuccessful) {
26 | return true;
27 | }
28 | } else {
29 | console.log(
30 | "[LOGINSETTINGS] This doesn't look like a Revolt instance...",
31 | );
32 | setTestResponse('invalid');
33 | }
34 | } catch (err: any) {
35 | if (err.toString().match('Network request failed')) {
36 | console.log(`[LOGINSETTINGS] Could not fetch ${instanceURL}`);
37 | setTestResponse('requestFailed');
38 | } else if (err.toString().match('JSON Parse error')) {
39 | console.log(
40 | "[LOGINSETTINGS] Could not parse the response (it's probably HTML)",
41 | );
42 | setTestResponse('notJSON');
43 | } else {
44 | console.log(err);
45 | setTestResponse('error');
46 | }
47 | }
48 | }
49 |
50 | return (
51 |
52 |
53 | {saved ? (
54 | <>
55 | Saved!
56 |
57 | For now, you'll have to close and reopen the app for your changes
58 | to apply. We know this isn't ideal - we'll fix this at a later
59 | date.
60 |
61 | >
62 | ) : (
63 | <>
64 | Instance
65 | {
70 | setInstanceURL(text);
71 | }}
72 | value={instanceURL}
73 | />
74 |
75 | {testResponse
76 | ? testResponse === 'valid'
77 | ? 'This looks like a Revolt instance!'
78 | : testResponse === 'invalid'
79 | ? "This doesn't look like a Revolt instance..."
80 | : testResponse === 'notJSON'
81 | ? "Could not parse response - make sure you're linking to the API URL"
82 | : testResponse === 'requestFailed'
83 | ? 'Could not fetch that URL'
84 | : 'Something went wrong!'
85 | : null}
86 |
87 |
93 |
105 | >
106 | )}
107 |
108 |
109 | );
110 | };
111 |
--------------------------------------------------------------------------------
/src/components/pages/VoiceChannel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 |
4 | import {styles} from '../../Theme';
5 | import {Text} from '../common/atoms';
6 |
7 | export const VoiceChannel = () => {
8 | return (
9 |
16 |
17 | Voice channels aren't supported in RVMob yet!
18 |
19 |
20 | In the meantime, you can join them via the web app or Revolt Desktop.
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/sheets/ChannelInfoSheet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {ScrollView, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {Channel, User} from 'revolt.js';
6 |
7 | import {currentTheme} from '../../Theme';
8 | import {Text} from '../common/atoms';
9 | import {MarkdownView} from '../common/MarkdownView';
10 |
11 | export const ChannelInfoSheet = observer(({channel}: {channel: Channel}) => {
12 | const [groupMembers, setGroupMembers] = React.useState([] as User[]);
13 |
14 | React.useEffect(() => {
15 | async function fetchMembers() {
16 | const m =
17 | channel.channel_type === 'Group' ? await channel.fetchMembers() : [];
18 | setGroupMembers(m);
19 | }
20 | fetchMembers();
21 | }, [channel]);
22 | return (
23 |
24 |
25 |
31 | {channel.name}
32 |
33 |
38 | {channel.channel_type === 'Group'
39 | ? `Group (${groupMembers.length} ${
40 | groupMembers.length === 1 ? 'member' : 'members'
41 | })`
42 | : 'Regular channel'}
43 |
44 | {channel.description ? (
45 |
51 |
57 | {channel.description}
58 |
59 |
60 | ) : null}
61 |
62 |
63 | );
64 | });
65 |
--------------------------------------------------------------------------------
/src/components/sheets/MemberListSheet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {ScrollView, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {Channel, Member, Server, User} from 'revolt.js';
6 |
7 | import {Text} from '../common/atoms';
8 | import {UserList} from '../navigation/UserList';
9 |
10 | interface ServerMemberList {
11 | context: Server;
12 | users: User[]; // Member[];
13 | }
14 |
15 | interface ChannelMemberList {
16 | context: Channel;
17 | users: User[];
18 | }
19 |
20 | export const MemberListSheet = observer(
21 | ({context, users}: ServerMemberList | ChannelMemberList) => {
22 | return (
23 |
24 | {context.name ?? context._id} members
25 |
26 |
27 |
28 | );
29 | },
30 | );
31 |
--------------------------------------------------------------------------------
/src/components/sheets/MessageMenuSheet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {ScrollView, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import Clipboard from '@react-native-clipboard/clipboard';
6 | import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
7 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
8 |
9 | import {Message} from 'revolt.js';
10 |
11 | import {app} from '../../Generic';
12 | import {currentTheme, styles} from '../../Theme';
13 | import {ContextButton, CopyIDButton, Text} from '../common/atoms';
14 | import {ReplyMessage} from '../common/messaging';
15 |
16 | export const MessageMenuSheet = observer(
17 | ({state, message}: {state: any; message: Message}) => {
18 | return (
19 | <>
20 |
21 |
22 | state.setState({contextMenuMessage: null})}>
24 |
25 |
30 |
31 | Close
32 |
33 | {message?.channel?.havePermission('SendMessage') ? (
34 | {
36 | let replyingMessages = [...app.getReplyingMessages()];
37 | if (
38 | replyingMessages.filter(m => m.message._id === message._id)
39 | .length > 0
40 | ) {
41 | return;
42 | }
43 | if (replyingMessages.length >= 5) {
44 | return;
45 | }
46 | if (app.getEditingMessage()) {
47 | return;
48 | }
49 | replyingMessages.push({
50 | message: message,
51 | mentions: false,
52 | });
53 | app.setReplyingMessages(replyingMessages);
54 | state.setState({contextMenuMessage: null});
55 | }}>
56 |
57 |
62 |
63 | Reply
64 |
65 | ) : null}
66 | {message.content ? (
67 | {
69 | Clipboard.setString(message.content!);
70 | }}>
71 |
72 |
77 |
78 | Copy content
79 |
80 | ) : null}
81 | {app.settings.get('ui.showDeveloperFeatures') ? (
82 |
83 | ) : null}
84 | {message?.channel?.havePermission('ManageMessages') ||
85 | message?.author?.relationship === 'User' ? (
86 | {
88 | message.delete();
89 | state.setState({contextMenuMessage: null});
90 | }}>
91 |
92 |
97 |
98 | Delete
99 |
100 | ) : null}
101 | {message?.author?.relationship === 'User' ? (
102 | {
104 | app.setMessageBoxInput(message?.content);
105 | app.setEditingMessage(message);
106 | app.setReplyingMessages([]);
107 | state.setState({contextMenuMessage: null});
108 | }}>
109 |
110 |
115 |
116 | Edit
117 |
118 | ) : null}
119 | {message?.author?.relationship !== 'User' ? (
120 | {
122 | app.openReportMenu(message, 'Message');
123 | state.setState({contextMenuMessage: null});
124 | }}>
125 |
126 |
131 |
132 | Report Message
133 |
134 | ) : null}
135 |
136 |
137 | >
138 | );
139 | },
140 | );
141 |
--------------------------------------------------------------------------------
/src/components/sheets/ServerInfoSheet.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react';
2 | import {ScrollView, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import FastImage from 'react-native-fast-image';
6 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
7 |
8 | import {Member, Server, User} from 'revolt.js';
9 |
10 | import {GeneralAvatar, app, client} from '../../Generic';
11 | import {SPECIAL_SERVERS} from '../../lib/consts';
12 | import {currentTheme, styles} from '../../Theme';
13 | import {ContextButton, CopyIDButton, Text} from '../common/atoms';
14 | import {MarkdownView} from '../common/MarkdownView';
15 |
16 | const Image = FastImage;
17 |
18 | export const ServerInfoSheet = observer(
19 | ({state, server}: {state: any; server: Server}) => {
20 | const [members, setMembers] = React.useState(
21 | {} as {members: Member[]; users: User[]},
22 | );
23 |
24 | useEffect(() => {
25 | async function fetchMembers() {
26 | if (server._id !== SPECIAL_SERVERS.lounge.id) {
27 | const start = new Date().getTime();
28 | console.log(`[SERVERINFOSHEET] Fetching members... (${start})`);
29 | const m = await server.fetchMembers();
30 | const mid = new Date().getTime();
31 | console.log(`[SERVERINFOSHEET] Fetched members (${mid})`);
32 | setMembers(m);
33 | const end = new Date().getTime();
34 | console.log(`[SERVERINFOSHEET] Set members (${end})`);
35 | }
36 | }
37 | fetchMembers();
38 | }, [server]);
39 |
40 | return (
41 |
42 |
43 | {server.banner ? (
44 |
48 | ) : null}
49 | {server.icon ? (
50 |
51 | ) : null}
52 |
58 | {server.name}
59 |
60 |
65 | {server._id === SPECIAL_SERVERS.lounge.id
66 | ? 'Member count disabled for this server'
67 | : members.members
68 | ? `${members.members.length} ${
69 | members.members.length === 1 ? 'member' : 'members'
70 | }`
71 | : 'Fetching member count...'}
72 |
73 | {server.description ? (
74 |
80 |
86 | {server.description}
87 |
88 |
89 | ) : null}
90 |
91 |
96 | {app.settings.get('ui.showDeveloperFeatures') ? (
97 |
98 | ) : null}
99 | {server.owner !== client.user?._id ? (
100 | <>
101 | {
104 | await app.openServer();
105 | state.setState({contextMenuServer: null});
106 | server.delete();
107 | }}>
108 |
109 |
114 |
115 | Leave Server
116 |
117 | {
120 | app.openReportMenu(server, 'Server');
121 | }}>
122 |
123 |
128 |
129 | Report Server
130 |
131 | >
132 | ) : null}
133 |
134 |
135 | );
136 | },
137 | );
138 |
--------------------------------------------------------------------------------
/src/components/sheets/UserMenuSheet.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Pressable, ScrollView, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
6 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
7 |
8 | import {User} from 'revolt.js';
9 |
10 | import {app} from '../../Generic';
11 | import {currentTheme, styles} from '../../Theme';
12 | import {ContextButton, CopyIDButton, Text} from '../common/atoms';
13 |
14 | export const UserMenuSheet = observer(
15 | ({state, user}: {state: any; user: User}) => {
16 | return (
17 | <>
18 |
19 | {
26 | state(false);
27 | }}>
28 |
33 |
39 | Close
40 |
41 |
42 | {app.settings.get('ui.showDeveloperFeatures') ? (
43 |
44 | ) : null}
45 | {user.relationship !== 'User' ? (
46 | {
48 | app.openReportMenu(user, 'User');
49 | state(false);
50 | }}>
51 |
52 |
57 |
58 | Report User
59 |
60 | ) : null}
61 |
62 |
63 | >
64 | );
65 | },
66 | );
67 |
--------------------------------------------------------------------------------
/src/components/sheets/index.tsx:
--------------------------------------------------------------------------------
1 | export {ChannelInfoSheet} from './ChannelInfoSheet';
2 | export {MemberListSheet} from './MemberListSheet';
3 | export {MessageMenuSheet} from './MessageMenuSheet';
4 | export {ProfileSheet} from './ProfileSheet';
5 | export {ReportModal as ReportSheet} from './ReportSheet';
6 | export {ServerInfoSheet} from './ServerInfoSheet';
7 | export {SettingsSheet} from './SettingsSheet';
8 | export {UserMenuSheet} from './UserMenuSheet';
9 |
--------------------------------------------------------------------------------
/src/components/views/ChannelView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {TouchableOpacity, View} from 'react-native';
3 | import {observer} from 'mobx-react-lite';
4 |
5 | import {ErrorBoundary} from 'react-error-boundary';
6 | import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
7 |
8 | import {Channel} from 'revolt.js';
9 |
10 | import {app, ChannelIcon, client} from '../../Generic';
11 | import {Messages, NewMessageView} from '../../MessageView';
12 | import {MessageBox} from '../../MessageBox';
13 | import {currentTheme, styles} from '../../Theme';
14 | import {FriendsPage} from '../pages/FriendsPage';
15 | import {HomePage} from '../pages/HomePage';
16 | import {ChannelHeader} from '../navigation/ChannelHeader';
17 | import {Button, Text} from '../common/atoms';
18 |
19 | function MessageViewErrorMessage({
20 | error,
21 | resetErrorBoundary,
22 | }: {
23 | error: any;
24 | resetErrorBoundary: Function;
25 | }) {
26 | console.error(`[MESSAGEVIEW] Uncaught error: ${error}`);
27 | return (
28 | <>
29 | Error rendering messages: {error}
30 |
36 | >
37 | );
38 | }
39 |
40 | type CVChannel = Channel | 'friends' | 'debug' | null;
41 |
42 | export const ChannelView = observer(
43 | ({state, channel}: {state: any; channel: CVChannel}) => {
44 | const [replacementChannel, setReplacementChannel] = React.useState(
45 | undefined as Channel | undefined,
46 | );
47 | const handledMessages = [] as string[];
48 | React.useEffect(() => {
49 | async function getChannel() {
50 | const type = 'spam';
51 | const c =
52 | type === 'spam'
53 | ? client.channels.get('01FBCT2PXQC0SSH6GD3V1BEHRP')
54 | : type === 'lounge'
55 | ? client.channels.get('01F7ZSBSFHCAAJQ92ZGTY67HMN')
56 | : await client.user?.openDM();
57 | setReplacementChannel(c);
58 | }
59 | getChannel();
60 | });
61 | console.log(
62 | `[CHANNELVIEW] Rendering channel view for ${
63 | channel instanceof Channel ? channel._id : channel
64 | }...`,
65 | );
66 | return (
67 |
68 | {channel ? (
69 | channel === 'friends' ? (
70 |
71 | ) : channel === 'debug' ? (
72 |
73 |
74 |
75 |
76 |
77 |
78 | Debug Menu (New MessageView)
79 |
80 |
81 |
85 |
86 | ) : (
87 |
88 |
89 |
90 |
100 |
101 |
102 | {channel.channel_type === 'DirectMessage'
103 | ? channel.recipient?.username
104 | : channel.channel_type === 'SavedMessages'
105 | ? 'Saved Notes'
106 | : channel.name}
107 |
108 | {channel.channel_type === 'Group' || channel.server ? (
109 |
110 | app.openChannelContextMenu(channel)}>
112 |
117 |
118 |
119 | ) : null}
120 | {channel.channel_type === 'Group' ? (
121 |
122 |
124 | app.openMemberList(
125 | channel,
126 | await channel.fetchMembers(),
127 | )
128 | }>
129 |
134 |
135 |
136 | ) : null}
137 |
138 | {channel?.channel_type === 'VoiceChannel' ? (
139 |
146 |
147 | Voice channels aren't supported in RVMob yet!
148 |
149 |
150 | In the meantime, you can join them via the web app or Revolt
151 | Desktop.
152 |
153 |
154 | ) : !channel?.nsfw ||
155 | app.settings.get('ui.messaging.showNSFWContent') ? (
156 |
157 | {
160 | app.openMessage(m);
161 | }}
162 | onUserPress={m => {
163 | app.openProfile(m.author, channel.server);
164 | }}
165 | onImagePress={a => {
166 | state.setState({imageViewerImage: a});
167 | }}
168 | onUsernamePress={m =>
169 | state.setState({
170 | currentText:
171 | state.state.currentText + '<@' + m.author?._id + '>',
172 | })
173 | }
174 | />
175 |
176 |
177 | ) : (
178 |
185 |
186 | Hold it!
187 |
188 |
189 | This is an NSFW channel. Are you sure you want to enter?
190 | {'\n'}
191 | (This can be reversed in Settings.)
192 |
193 |
202 |
203 | )}
204 |
205 | )
206 | ) : (
207 |
208 | )}
209 |
210 | );
211 | },
212 | );
213 |
--------------------------------------------------------------------------------
/src/lib/consts.ts:
--------------------------------------------------------------------------------
1 | // notable user IDs (mostly used for badges)
2 | export const USER_IDS = {
3 | // bots/built-in users
4 | automod: '01FHGJ3NPP7XANQQH8C2BE44ZY',
5 | platformModeration: '01FC17E1WTM2BGE4F3ARN3FDAF',
6 |
7 | // devs/team members
8 | developers: ['01FC1HP5H22F0M34MFFM9DZ099', '01FEEFJCKY5C4DMMJYZ20ACWWC'],
9 | teamMembers: {
10 | insert: '01EX2NCWQ0CHS3QJF0FEQS1GR4',
11 | lea: '01EXAF3KX65608AJ4NG27YG1HM',
12 | },
13 | };
14 |
15 | // notable servers (used for the home screen + other features)
16 | export const SPECIAL_SERVERS = {
17 | // the Revolt Lounge
18 | lounge: {
19 | id: '01F7ZSBSFHQ8TA81725KQCSDDP',
20 | invite: 'Testers',
21 | },
22 |
23 | // RVMob's support server
24 | supportServer: {
25 | id: '01FKES1VJN27SVV4QJX82ZS3ME',
26 | invite: 'ZFGGw6ry',
27 | },
28 | };
29 |
30 | // default API URL - when support for other instances is added, this will be the default one
31 | export const DEFAULT_API_URL = 'https://api.divolt.xyz'; // TODO: switch to https://revolt.chat/api when it's available
32 |
33 | // default max side param - used to specify the size of images
34 | export const DEFAULT_MAX_SIDE = '128';
35 |
36 | // default amount of messages to load
37 | export const DEFAULT_MESSAGE_LOAD_COUNT = 50;
38 |
39 | // server invite paths for the official instance
40 | export const INVITE_PATHS = [
41 | 'app.revolt.chat/invite',
42 | 'nightly.revolt.chat/invite',
43 | 'local.revolt.chat/invite',
44 | 'rvlt.gg',
45 | 'divolt.xyz/invite',
46 | ];
47 |
48 | // regex to find server invites
49 | export const RE_INVITE = new RegExp(
50 | `(?:${INVITE_PATHS.map(x => x?.split('.').join('\\.')).join(
51 | '|',
52 | )})/([A-Za-z0-9]*)`,
53 | 'g',
54 | );
55 |
56 | // bot invite paths for the official instance
57 | export const BOT_INVITE_PATHS = [
58 | 'app.revolt.chat/bot',
59 | 'nightly.revolt.chat/bot',
60 | 'local.revolt.chat/bot',
61 | 'divolt.xyz/bot',
62 | ];
63 |
64 | // regex to find bot invites
65 | export const RE_BOT_INVITE = new RegExp(
66 | `(?:${BOT_INVITE_PATHS.map(x => x.split('.').join('\\.')).join(
67 | '|',
68 | )})/([A-Za-z0-9]*)`,
69 | 'g',
70 | );
71 |
72 | // link to discover, used by the invite finder to ignore these links
73 | export const DISCOVER_URL = 'rvlt.gg/discover';
74 |
75 | // link to the revolt wiki, per above
76 | export const WIKI_URL = 'wiki.rvlt.gg';
77 |
78 | export type SpecialDateObject = {
79 | name: string;
80 | key: string;
81 | emoji: string;
82 | link: string;
83 | };
84 |
85 | // list of holidays, used for the home screen
86 | export const SPECIAL_DATES = [
87 | '1/1',
88 | '14/2',
89 | '31/3',
90 | '1/4',
91 | '31/10',
92 | '20/11',
93 | '31/12',
94 | 'month6',
95 | 'month12',
96 | ]; // NYD, Valentine's Day, TDOV, April Fool's, Halloween, TDOR and NYE + Pride Month/December
97 |
98 | // objects for each holiday
99 | export const SPECIAL_DATE_OBJECTS = {
100 | '1/1': {
101 | name: "New Year's Day",
102 | key: 'app-home-holiday-nyd',
103 | emoji: '🎉',
104 | link: "https://en.wikipedia.org/wiki/New_Year's_Eve",
105 | } as SpecialDateObject,
106 | '14/2': {
107 | name: "Valentine's Day",
108 | key: 'app-home-holiday-valentines',
109 | emoji: '💖',
110 | link: "https://en.wikipedia.org/wiki/Valentine's_Day",
111 | } as SpecialDateObject,
112 | '31/3': {
113 | name: 'International Trans Day of Visibility',
114 | key: 'app-home-holiday-tdov',
115 | emoji: '🏳️⚧️',
116 | link: 'https://en.wikipedia.org/wiki/TDOV',
117 | } as SpecialDateObject,
118 | '1/4': {
119 | name: "April Fools' Day",
120 | key: 'app-home-holiday-april-fools',
121 | emoji: '🤡',
122 | link: 'https://en.wikipedia.org/wiki/April_Fools%27_Day',
123 | } as SpecialDateObject,
124 | '31/10': {
125 | name: 'Halloween',
126 | key: 'app-home-holiday-halloween',
127 | emoji: '🎃',
128 | link: 'https://en.wikipedia.org/wiki/Halloween',
129 | } as SpecialDateObject,
130 | '20/11': {
131 | name: 'Trans Day of Remembrance',
132 | key: 'app-home-holiday-tdor',
133 | emoji: '🕯️',
134 | link: 'https://en.wikipedia.org/wiki/TDoR',
135 | } as SpecialDateObject,
136 | '31/12': {
137 | name: "New Year's Eve",
138 | key: 'app-home-holiday-nye',
139 | emoji: '⏰',
140 | link: "https://en.wikipedia.org/wiki/New_Year's_Eve",
141 | } as SpecialDateObject,
142 | month6: {
143 | name: 'Pride Month',
144 | key: 'app-home-holiday-pride',
145 | emoji: '🏳️🌈🏳️⚧️',
146 | link: 'https://en.wikipedia.org/wiki/Pride_Month',
147 | } as SpecialDateObject,
148 | month12: {
149 | name: 'Holiday Season',
150 | key: 'app-home-holiday-dechols',
151 | emoji: '🎄❄️',
152 | link: 'https://en.wikipedia.org/wiki/Christmas_and_holiday_season',
153 | } as SpecialDateObject,
154 | };
155 |
156 | export const BADGES = {
157 | Developer: 1,
158 | Translator: 2,
159 | Supporter: 4,
160 | ResponsibleDisclosure: 8,
161 | Founder: 16,
162 | PlatformModeration: 32,
163 | ActiveSupporter: 64,
164 | Paw: 128,
165 | EarlyAdopter: 256,
166 | ReservedRelevantJokeBadge1: 512,
167 | ReservedRelevantJokeBadge2: 1024,
168 | };
169 |
170 | export const LOADING_SCREEN_REMARKS = [
171 | "I'm writing a complaint to the Head of Loading Screens.",
172 | "I don't think we can load any longer!",
173 | 'Fun fact: RVMob is built with React Native.',
174 | 'Better grab a book or something.',
175 | 'When will the madness end?',
176 | 'You know, what does DVMob even stand for?',
177 | 'Why do they call it a "building" if it\'s already built?',
178 | 'balls',
179 | ];
180 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import {differenceInMinutes} from 'date-fns';
2 | import {Channel, Message} from 'revolt.js';
3 | import {decodeTime} from 'ulid';
4 |
5 | import {client} from '../Generic';
6 | import {currentTheme} from '../Theme';
7 | import {DEFAULT_MESSAGE_LOAD_COUNT} from './consts';
8 |
9 | /**
10 | * Returns the correct colour as a HEX string/theme variable. Supports regular HEX colours, client variables (e.g. `var(--accent)`)
11 | * and gradient roles (which, for now, will return as the first colour from the gradient - this will change if React Native adds support for gradients).
12 | * @param c The string to check over
13 | */
14 | export function getColour(c: string) {
15 | // first check for css variables...
16 | const isVariable = c.match('var');
17 | if (isVariable) {
18 | switch (c) {
19 | case 'var(--error)':
20 | return currentTheme.error;
21 | case 'var(--accent)':
22 | return currentTheme.accentColorForeground;
23 | case 'var(--foreground)':
24 | return currentTheme.foregroundPrimary;
25 | case 'var(--background)':
26 | return currentTheme.backgroundPrimary;
27 | default:
28 | break;
29 | }
30 | }
31 |
32 | // ...then check for gradients
33 | const gradientRegex = /(linear|conical|radial)-gradient\s*\(/;
34 | const degRegex = /[0-9]{0,3}deg,\s*/;
35 | const bracketRegex = /\)\s*(text)?$/;
36 | const percentRegex = /[0-9]{0,3}%(,|\))?\s*/;
37 |
38 | const isGradient = c.match(gradientRegex);
39 | if (isGradient) {
40 | const filteredC = c
41 | .replace(gradientRegex, '')
42 | .replace(bracketRegex, '')
43 | .replace(degRegex, '')
44 | .replace(percentRegex, '');
45 |
46 | const filteredAsArray = filteredC.split(',');
47 |
48 | console.log(
49 | `[UTILS] getColour detected a gradient role: ${c}, filtered: ${filteredC}, to array: ${filteredAsArray}, ${filteredAsArray[0]}`,
50 | );
51 |
52 | if (c.match('linear')) {
53 | return filteredAsArray[0];
54 | }
55 |
56 | return c;
57 | }
58 |
59 | // at this point, c is probably just a regular HEX code so return it directly
60 | return c;
61 | }
62 |
63 | /**
64 | * Sleep for the specified amount of milliseconds before continuing.
65 | * @param ms The amount of time to sleep for in milliseconds
66 | */
67 | export const sleep = (ms: number | undefined) =>
68 | new Promise((r: any) => setTimeout(r, ms));
69 |
70 | /**
71 | * Parses the given string for pings, channel links and custom emoji
72 | * @param text The text to parse
73 | * @returns The parsed text
74 | */
75 | export function parseRevoltNodes(text: string) {
76 | text = text.replace(/<@[0-9A-Z]*>/g, ping => {
77 | let id = ping.slice(2, -1);
78 | let user = client.users.get(id);
79 | if (user) {
80 | return `[@${user.username}](/@${user._id})`;
81 | }
82 | return ping;
83 | });
84 | text = text.replace(/<#[0-9A-Z]*>/g, ping => {
85 | let id = ping.slice(2, -1);
86 | let channel = client.channels.get(id);
87 | if (channel) {
88 | return `[#${channel.name
89 | ?.split(']')
90 | .join('\\]')
91 | .split('[')
92 | .join('\\[')
93 | .split('*')
94 | .join('\\*')
95 | .split('`')
96 | .join('\\`')}](/server/${channel.server?._id}/channel/${channel._id})`;
97 | }
98 | return ping;
99 | });
100 | text = text.replace(/:[0-9A-Z]*:/g, ping => {
101 | let id = ping.slice(1, -1);
102 | let emoji = client.emojis.get(id);
103 | if (emoji) {
104 | return `%EMOJI - [${emoji.name}](${emoji.imageURL}) - EMOJI%`;
105 | }
106 | return ping;
107 | });
108 | return text;
109 | }
110 |
111 | export function getReadableFileSize(size: number | null) {
112 | return size !== null
113 | ? size / 1000000 >= 0.01
114 | ? `${(size / 1000000).toFixed(2)} MB`
115 | : size / 10000 >= 0.01
116 | ? `${(size / 1000).toFixed(2)} KB`
117 | : `${size} bytes`
118 | : 'Unknown';
119 | }
120 |
121 | export function calculateGrouped(msg1: Message, msg2: Message) {
122 | // if the author is somehow null don't group the message
123 | if (!msg1.author || !msg2.author) {
124 | return false;
125 | }
126 | return (
127 | // a message is grouped with the previous message if all of the following is true:
128 | msg1.author._id === msg2.author._id && // the author is the same
129 | !(msg2.reply_ids && msg2.reply_ids.length > 0) && // the message is not a reply
130 | differenceInMinutes(
131 | // the time difference is less than 7 minutes and
132 | decodeTime(msg1._id),
133 | decodeTime(msg2._id),
134 | ) < 7 &&
135 | (msg2.masquerade // the masquerade is the same
136 | ? msg2.masquerade.avatar === msg1.masquerade?.avatar &&
137 | msg2.masquerade.name === msg1.masquerade?.name
138 | : true)
139 | );
140 | }
141 |
142 | type FetchInput = {
143 | id?: string;
144 | type?: 'before' | 'after';
145 | };
146 |
147 | export async function fetchMessages(
148 | channel: Channel,
149 | input: FetchInput,
150 | existingMessages: Message[],
151 | sliceMessages?: false,
152 | ) {
153 | const type = input.type ?? 'before';
154 | let params = {
155 | // input.before ? DEFAULT_MESSAGE_LOAD_COUNT / 2 :
156 | limit: DEFAULT_MESSAGE_LOAD_COUNT,
157 | } as {limit: number; before?: string; after?: string};
158 | params[type] = input.id;
159 | // if (type == "after") {
160 | // params.sort = "Oldest"
161 | // }
162 | const res = await channel.fetchMessagesWithUsers(params);
163 | console.log(
164 | `[FETCHMESSAGES] Finished fetching ${res.messages.length} message(s) for ${channel._id}`,
165 | );
166 |
167 | let oldMessages = existingMessages;
168 | if (sliceMessages) {
169 | if (input.type === 'before') {
170 | oldMessages = oldMessages.slice(0, DEFAULT_MESSAGE_LOAD_COUNT / 2 - 1);
171 | } else if (input.type === 'after') {
172 | oldMessages = oldMessages.slice(
173 | DEFAULT_MESSAGE_LOAD_COUNT / 2 - 1,
174 | DEFAULT_MESSAGE_LOAD_COUNT - 1,
175 | );
176 | }
177 | }
178 | let messages = res.messages.reverse();
179 | let result =
180 | input.type === 'before'
181 | ? messages.concat(oldMessages)
182 | : input.type === 'after'
183 | ? oldMessages.concat(messages)
184 | : messages;
185 | console.log(
186 | `[FETCHEDMESSAGES] Finished preparing fetched messages for ${channel._id}`,
187 | );
188 |
189 | return result;
190 | }
191 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true
4 | },
5 | "extends": "@tsconfig/react-native/tsconfig.json"
6 | }
--------------------------------------------------------------------------------