diff --git a/App.tsx b/App.tsx index 60635065..4b265f42 100644 --- a/App.tsx +++ b/App.tsx @@ -1,360 +1,57 @@ import 'react-native-get-random-values'; // react native moment -import {Component as ReactComponent, useContext, useState} from 'react'; -import {StatusBar, StyleSheet, View} from 'react-native'; +import {useEffect, useState} from 'react'; +import {StyleSheet} from 'react-native'; import {ErrorBoundary} from 'react-error-boundary'; -// import ConfirmHcaptcha from '@hcaptcha/react-native-hcaptcha'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; -import {Channel, Server} from 'revolt.js'; - -import {client, app, randomizeRemark} from './src/Generic'; -import {setFunction} from './src/Generic'; -import {SideMenuHandler} from './src/SideMenu'; -import {Modals} from './src/Modals'; +import {setFunction} from '@rvmob/Generic'; +import {MainView} from '@rvmob/MainView'; import {ErrorMessage} from '@rvmob/components/ErrorMessage'; -import {NetworkIndicator} from './src/components/NetworkIndicator'; -import {Notification} from './src/components/Notification'; -import {loginWithSavedToken} from './src/lib/auth'; -import { - createChannel, - sendNotifeeNotification, - setUpNotifeeListener, -} from '@rvmob/lib/notifications'; -import {sleep} from '@rvmob/lib/utils'; -import {LoginViews} from '@rvmob/pages/LoginViews'; -import {themes, Theme, ThemeContext} from '@rvmob/lib/themes'; import {LoadingScreen} from '@rvmob/components/views/LoadingScreen'; +import {storage} from '@rvmob/lib/storage'; +import {migrateToMMKV} from '@rvmob/lib/storage/migration'; +import {initialiseSettings} from '@rvmob/lib/storage/utils'; +import {themes, Theme, ThemeContext} from '@rvmob/lib/themes'; -async function openLastChannel() { - try { - const lastServer = await AsyncStorage.getItem('lastServer'); - if (lastServer) { - app.openServer(client.servers.get(lastServer)); - try { - const channelData = await AsyncStorage.getItem('serverLastChannels'); - let serverLastChannels = JSON.parse(channelData || '{}') || {}; - let lastChannel = serverLastChannels[lastServer]; - if (lastChannel) { - let fetchedLastChannel = client.channels.get(lastChannel); - if (fetchedLastChannel) { - app.openChannel(fetchedLastChannel); - } - } - } catch (channelErr) { - console.log(`[APP] Error getting last channel: ${channelErr}`); - } - } - } catch (serverErr) { - console.log(`[APP] Error getting last server: ${serverErr}`); - } -} - -async function checkLastVersion() { - const lastVersion = app.settings.get('app.lastVersion'); - console.log(app.version, lastVersion); - if (!lastVersion || lastVersion === '') { - console.log( - `[APP] lastVersion is null (${lastVersion}), setting to app.version (${app.version})`, - ); - await app.settings.set('app.lastVersion', app.version); - } else { - app.version === lastVersion - ? console.log( - `[APP] lastVersion (${lastVersion}) is equal to app.version (${app.version})`, - ) - : console.log( - `[APP] lastVersion (${lastVersion}) is different from app.version (${app.version})`, - ); - } -} - -function LoggedInViews({state, setChannel}: {state: any; setChannel: any}) { - return ( - <> - - - - - - state.setState({ - notificationMessage: null, - }) - } - openChannel={() => - state.setState({ - notificationMessage: null, - currentChannel: state.state.notificationMessage.channel, - }) - } - /> - - - ); -} - -function AppViews({state, setChannel}: {state: any; setChannel: any}) { - const {currentTheme} = useContext(ThemeContext); - const localStyles = generateAppViewStyles(currentTheme); - - return ( - <> - - - {state.state.status === 'loggedIn' ? ( - - ) : state.state.status === 'loggedOut' ? ( - state.setState({status: 'loggedIn'})} - /> - ) : ( - - )} - - - ); -} - -class MainView extends ReactComponent { - constructor(props) { - super(props); - this.state = { - status: 'loggedOut', - currentChannel: null, - notificationMessage: null, - orderedServers: [], - serverNotifications: null, - channelNotifications: null, - }; - setFunction('openChannel', async c => { - // if (!this.state.currentChannel || this.state.currentChannel?.server?._id != c.server?._id) c.server?.fetchMembers() - this.setState({currentChannel: c}); - app.openLeftMenu(false); - }); - setFunction('joinInvite', async (i: string) => { - await client.joinInvite(i); - }); - setFunction('logOut', async () => { - console.log( - `[AUTH] Logging out of current session... (user: ${client.user?._id})`, - ); - AsyncStorage.multiSet([ - ['token', ''], - ['sessionID', ''], - ]); - this.setState({status: 'loggedOut', currentChannel: null}); - await client.logout(); - app.setLoggedOutScreen('loginPage'); - }); - } - componentDidUpdate(_, prevState) { - if (prevState.status !== this.state.status) { - randomizeRemark(); - } - } - async componentDidMount() { - console.log(`[APP] Mounted component (${new Date().getTime()})`); - - let defaultNotif = await createChannel(); - console.log(`[NOTIFEE] Created channel: ${defaultNotif}`); - - await checkLastVersion(); - - client.on('connecting', () => { - app.setLoadingStage('connecting'); - console.log(`[APP] Connecting to instance... (${new Date().getTime()})`); - }); - - client.on('connected', () => { - app.setLoadingStage('connected'); - console.log(`[APP] Connected to instance (${new Date().getTime()})`); - }); - - client.on('ready', async () => { - let orderedServers, - server, - channel = null; - try { - const rawSettings = await client.syncFetchSettings([ - 'ordering', - 'notifications', - ]); - try { - orderedServers = JSON.parse(rawSettings.ordering[1]).servers; - ({server, channel} = JSON.parse(rawSettings.notifications[1])); - } catch (err) { - console.log(`[APP] Error parsing fetched settings: ${err}`); - } - } catch (err) { - console.log(`[APP] Error fetching settings: ${err}`); - } - - this.setState({ - status: 'loggedIn', - network: 'ready', - orderedServers, - serverNotifications: server, - channelNotifications: channel, - }); - console.log(`[APP] Client is ready (${new Date().getTime()})`); - - setUpNotifeeListener(client, this.setState); - - if (app.settings.get('app.reopenLastChannel')) { - await openLastChannel(); - } - }); - - client.on('dropped', async () => { - this.setState({network: 'dropped'}); - }); - - client.on('message', async msg => { - console.log(`[APP] Handling message ${msg._id}`); - - let channelNotif = this.state.channelNotifications - ? this.state.channelNotifications[msg.channel?._id] - : undefined; - let serverNotif = this.state.serverNotifications - ? this.state.serverNotifications[msg.channel?.server?._id] - : undefined; - - const isMuted = - (channelNotif && channelNotif === 'none') || - channelNotif === 'muted' || - (serverNotif && serverNotif === 'none') || - serverNotif === 'muted'; - - const alwaysNotif = - channelNotif === 'all' || (!isMuted && serverNotif === 'all'); - - const mentionsUser = - (msg.mention_ids?.includes(client.user?._id!) && - (app.settings.get('app.notifications.notifyOnSelfPing') || - msg.author?._id !== client.user?._id)) || - msg.channel?.channel_type === 'DirectMessage'; - - const shouldNotif = - (alwaysNotif && - (app.settings.get('app.notifications.notifyOnSelfPing') || - msg.author?._id !== client.user?._id)) || - (!isMuted && mentionsUser); - - console.log( - `[NOTIFICATIONS] Should notify for ${msg._id}: ${shouldNotif} (channel/server muted? ${isMuted}, notifications for all messages enabled? ${alwaysNotif}, message mentions the user? ${mentionsUser})`, - ); +export const App = () => { + const [checkedMigration, setCheckedMigration] = useState(false); + const [theme, setTheme] = useState(themes.Dark); - if (app.settings.get('app.notifications.enabled') && shouldNotif) { - console.log( - `[NOTIFICATIONS] Pushing notification for message ${msg._id}`, - ); - if (this.state.currentChannel !== msg.channel) { - this.setState({notificationMessage: msg}); - await sleep(5000); - this.setState({notificationMessage: null}); - } + const localStyles = generateLocalStyles(theme); - await sendNotifeeNotification(msg, client, defaultNotif); - } - }); + setFunction('setTheme', (themeName: string) => { + const newTheme = themes[themeName] ?? themes.Dark; + setTheme(newTheme); + }); - client.on('packet', async p => { - if (p.type === 'UserSettingsUpdate') { - console.log('[WEBSOCKET] Synced settings updated'); - try { - if ('ordering' in p.update) { - const orderedServers = JSON.parse(p.update.ordering[1]).servers; - this.setState({orderedServers}); - } - if ('notifications' in p.update) { - const {server, channel} = JSON.parse(p.update.notifications[1]); - this.setState({ - serverNotifications: server, - channelNotifications: channel, - }); - } - } catch (err) { - console.log(`[APP] Error fetching settings: ${err}`); - } - } - }); + useEffect(() => { + async function migrationCheck() { + const hasMigrated = storage.getBoolean('hasMigrated'); - client.on('server/delete', async s => { - const currentServer = app.getCurrentServer(); - if (currentServer === s) { - app.openServer(undefined); - app.openChannel(null); + if (!hasMigrated) { + await migrateToMMKV(); } - }); - await loginWithSavedToken(this.state.status); - } + initialiseSettings(); - async setChannel(channel: string | Channel | null, server?: Server) { - this.setState({ - currentChannel: channel, - messages: [], - }); - app.openLeftMenu(false); - if (channel) { - await AsyncStorage.getItem('serverLastChannels', async (err, data) => { - if (!err) { - let parsedData = JSON.parse(data || '{}') || {}; - parsedData[server?._id || 'DirectMessage'] = - typeof channel === 'string' ? channel : channel._id; - console.log(parsedData); - await AsyncStorage.setItem( - 'serverLastChannels', - JSON.stringify(parsedData), - ); - } else { - console.log(`[APP] Error getting last channel: ${err}`); - } - }); + setCheckedMigration(true); } - } - render() { - return ; - } -} - -export const App = () => { - const [theme, setTheme] = useState(themes.Dark); - - setFunction('setTheme', (themeName: string) => { - const newTheme = themes[themeName] ?? themes.Dark; - setTheme(newTheme); - }); - - const localStyles = generateLocalStyles(theme); + migrationCheck(); + }, []); return ( - + {checkedMigration ? ( + + ) : ( + + )} @@ -369,12 +66,3 @@ const generateLocalStyles = (currentTheme: Theme) => { }, }); }; - -const generateAppViewStyles = (currentTheme: Theme) => { - return StyleSheet.create({ - app: { - flex: 1, - backgroundColor: currentTheme.backgroundPrimary, - }, - }); -}; diff --git a/i18n/getLanguage.ts b/i18n/getLanguage.ts index 46738d22..31f735d9 100644 --- a/i18n/getLanguage.ts +++ b/i18n/getLanguage.ts @@ -1,30 +1,39 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; import type {ModuleType} from 'i18next'; +import { storage } from '@rvmob/lib/storage'; + const STORE_LANGUAGE_KEY = 'app.language'; export const languageDetectorPlugin = { type: 'languageDetector' as ModuleType, - async: true, init: () => {}, - detect: async function (callback: (lang: string) => void) { + detect: function () { try { - await AsyncStorage.getItem(STORE_LANGUAGE_KEY).then(language => { - if (language) { - return callback(language); - } else { - // TODO: use the device's locale - return 'en'; + const settings = JSON.parse(storage.getString('settings') ?? '[]'); + for (const setting of settings) { + if (setting.key === STORE_LANGUAGE_KEY) { + return setting.value; } - }); + } + // TODO: use device language + return 'en'; } catch (error) { console.warn(`[APP] Error reading language: ${error}`); + return 'en'; } }, - cacheUserLanguage: async function (language: string) { + cacheUserLanguage: function (language: string) { try { - //save a user's language choice in Async storage - await AsyncStorage.setItem(STORE_LANGUAGE_KEY, language); - } catch (error) {} + const settings = JSON.parse(storage.getString('settings') ?? '[]'); + for (const setting of settings) { + if (setting.key === STORE_LANGUAGE_KEY) { + const index = settings.indexOf(setting); + settings.splice(index, 1); + } + } + settings.push({key: STORE_LANGUAGE_KEY, value: language}); + const newData = JSON.stringify(settings); + storage.set('settings', newData); + } catch (error) {} }, }; diff --git a/i18n/strings/en.json b/i18n/strings/en.json index 28a87894..926d4bf0 100644 --- a/i18n/strings/en.json +++ b/i18n/strings/en.json @@ -26,6 +26,7 @@ "loading": { "generic": "Loading…", "connecting": "Logging in…", + "migrating_settings": "Migrating settings…", "unknown_state": "Uh oh…", "unknown_state_body": "Please let the developers know that you saw this (value: {{state}})" }, diff --git a/package.json b/package.json index 3fc02e43..697c7d12 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "react-native-drawer-layout": "^4.1.1", "react-native-gesture-handler": "^2.21.2", "react-native-get-random-values": "^1.11.0", + "react-native-mmkv": "^3.2.0", "react-native-reanimated": "3.16.6", "react-native-reanimated-image-viewer": "^1.0.2", "react-native-svg": "^15.10.1", diff --git a/src/Generic.tsx b/src/Generic.tsx index d28e0e9c..b106a23d 100644 --- a/src/Generic.tsx +++ b/src/Generic.tsx @@ -1,12 +1,10 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import {Client} from 'revolt.js'; import type {API, Channel, Message, Server, User} from 'revolt.js'; import {setLanguage} from '@rvmob-i18n/i18n'; import {languages} from '@rvmob-i18n/languages'; import {DEFAULT_API_URL, LOADING_SCREEN_REMARKS} from '@rvmob/lib/consts'; import {checkNotificationPerms} from '@rvmob/lib/notifications'; +import {storage} from '@rvmob/lib/storage'; import {themes} from '@rvmob/lib/themes'; import { CreateChannelModalProps, @@ -65,7 +63,7 @@ export const app = { setting.type === 'number' ? parseInt(raw as string, 10) || 0 : raw; return toreturn; }, - set: async (k: string, v: string | boolean | undefined) => { + set: (k: string, v: string | boolean | undefined) => { try { const setting = app.settings._fetch(k); if (!setting) { @@ -74,12 +72,12 @@ export const app = { } setting.value = v; setting.onChange && setting.onChange(v); - await app.settings.save(); + app.settings.save(); } catch (err) { console.log(`[SETTINGS] Error setting setting ${k} to ${v}: ${err}`); } }, - save: async () => { + save: () => { try { let out: object[] = []; for (const s of app.settings.list) { @@ -87,19 +85,18 @@ export const app = { out.push({key: s.key, value: s.value}); } } - await AsyncStorage.setItem('settings', JSON.stringify(out)); + storage.set('settings', JSON.stringify(out)); } catch (err) { console.log(`[SETTINGS] Error saving settings: ${err}`); } }, - clear: async () => { + clear: () => { try { - AsyncStorage.setItem('settings', '{}').then(() => { - for (const s of app.settings.list) { - delete s.value; - s.onChange && s.onChange(s.default); - } - }); + storage.set('settings', '{}'); + for (const s of app.settings.list) { + delete s.value; + s.onChange && s.onChange(s.default); + } } catch (err) { console.log(`[SETTINGS] Error saving settings: ${err}`); } @@ -409,69 +406,6 @@ export function setFunction(name: string, func: any) { app[name] = func; } -async function initialiseSettings() { - const s = await AsyncStorage.getItem('settings'); - console.log(s); - if (s) { - try { - const settings = JSON.parse(s) as {key: string; value: any}[]; - settings.forEach(key => { - let st: Setting | undefined; - for (const setting of app.settings.list) { - if (setting.key === key.key) { - st = setting; - } - } - if (st) { - st.value = key.value; - st.onInitialize && st.onInitialize(key.value); - } else { - // ignore known good key - if (key.key !== 'app.lastVersion') { - console.warn( - `[SETTINGS] Unknown setting in AsyncStorage settings: ${key}`, - ); - } - } - }); - } catch (e) { - console.error(e); - } - } -} - -async function getAPIURL() { - await initialiseSettings(); - console.log(`[APP] Initialised settings (${new Date().getTime()})`); - let url: string = ''; - console.log('[AUTH] Getting API URL...'); - const instance = app.settings.get('app.instance') as - | string - | null - | undefined; - if (!instance) { - console.log( - '[AUTH] Unable to fetch app.instance; setting apiURL to default', - ); - url = DEFAULT_API_URL; - } else { - console.log(`[AUTH] Fetched app.instance; setting apiURL to ${instance}`); - url = instance; - } - return url; -} - -export let client = undefined as unknown as Client; - -getAPIURL().then(url => { - const apiURL = url; - console.log(`[AUTH] Creating client... (instance: ${apiURL})`); - client = new Client({ - unreads: true, - apiURL: apiURL, - }); -}); - export var selectedRemark = LOADING_SCREEN_REMARKS[ Math.floor(Math.random() * LOADING_SCREEN_REMARKS.length) diff --git a/src/LegacyMessageView.tsx b/src/LegacyMessageView.tsx index 5c6193e0..241f2a7c 100644 --- a/src/LegacyMessageView.tsx +++ b/src/LegacyMessageView.tsx @@ -10,7 +10,8 @@ import {autorun} from 'mobx'; import {Message as RevoltMessage} from 'revolt.js'; -import {client, app, setFunction, randomizeRemark} from './Generic'; +import {app, setFunction, randomizeRemark} from './Generic'; +import { client } from './lib/client'; import {styles} from './Theme'; import {Text} from './components/common/atoms'; import {Message} from './components/common/messaging'; diff --git a/src/MainView.tsx b/src/MainView.tsx new file mode 100644 index 00000000..a0365dbf --- /dev/null +++ b/src/MainView.tsx @@ -0,0 +1,339 @@ +import {Component as ReactComponent, useCallback, useContext} from 'react'; +import {StatusBar, StyleSheet, View} from 'react-native'; + +import {Channel, Server} from 'revolt.js'; + +import {app, randomizeRemark, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; +import {Modals} from '@rvmob/Modals'; +import {SideMenuHandler} from '@rvmob/SideMenu'; +import {NetworkIndicator} from '@rvmob/components/NetworkIndicator'; +import {Notification} from '@rvmob/components/Notification'; +import {LoadingScreen} from '@rvmob/components/views/LoadingScreen'; +import {loginWithSavedToken} from '@rvmob/lib/auth'; +import { + createChannel, + sendNotifeeNotification, + setUpNotifeeListener, +} from '@rvmob/lib/notifications'; +import {storage} from '@rvmob/lib/storage'; +import {Theme, ThemeContext} from '@rvmob/lib/themes'; +import {sleep} from '@rvmob/lib/utils'; +import {LoginViews} from '@rvmob/pages/LoginViews'; + +function openLastChannel() { + try { + const lastServer = storage.getString('lastServer'); + if (lastServer) { + app.openServer(client.servers.get(lastServer)); + try { + const channelData = storage.getString('lastOpenedChannels'); + let lastOpenedChannels = JSON.parse(channelData || '{}') || {}; + let lastChannel = lastOpenedChannels[lastServer]; + if (lastChannel) { + let fetchedLastChannel = client.channels.get(lastChannel); + if (fetchedLastChannel) { + app.openChannel(fetchedLastChannel); + } + } + } catch (channelErr) { + console.log(`[APP] Error getting last channel: ${channelErr}`); + } + } + } catch (serverErr) { + console.log(`[APP] Error getting last server: ${serverErr}`); + } +} + +function checkLastVersion() { + const lastVersion = app.settings.get('app.lastVersion'); + console.log(app.version, lastVersion); + if (!lastVersion || lastVersion === '') { + console.log( + `[APP] lastVersion is null (${lastVersion}), setting to app.version (${app.version})`, + ); + app.settings.set('app.lastVersion', app.version); + } else { + app.version === lastVersion + ? console.log( + `[APP] lastVersion (${lastVersion}) is equal to app.version (${app.version})`, + ) + : console.log( + `[APP] lastVersion (${lastVersion}) is different from app.version (${app.version})`, + ); + } +} + +function LoggedInViews({state, setChannel}: {state: any; setChannel: any}) { + return ( + <> + + + + + + state.setState({ + notificationMessage: null, + }) + } + openChannel={() => + state.setState({ + notificationMessage: null, + currentChannel: state.state.notificationMessage.channel, + }) + } + /> + + + ); +} + +function AppViews({state}: {state: any}) { + const {currentTheme} = useContext(ThemeContext); + const localStyles = generateLocalStyles(currentTheme); + + const setChannel = useCallback( + (channel: string | Channel | null, server?: Server) => { + state.setState({ + currentChannel: channel, + messages: [], + }); + app.openLeftMenu(false); + if (channel) { + const lastOpenedChannels = storage.getString('lastOpenedChannels'); + try { + let parsedData = JSON.parse(lastOpenedChannels || '{}') || {}; + parsedData[server?._id || 'DirectMessage'] = + typeof channel === 'string' ? channel : channel._id; + console.log(parsedData); + storage.set('lastOpenedChannels', JSON.stringify(parsedData)); + } catch (err) { + console.log(`[APP] Error getting last channel: ${err}`); + } + } + }, + [state], + ); + + return ( + <> + + + {state.state.status === 'loggedIn' ? ( + + ) : state.state.status === 'loggedOut' ? ( + state.setState({status: 'loggedIn'})} + /> + ) : ( + + )} + + + ); +} + +export class MainView extends ReactComponent { + constructor(props) { + super(props); + this.state = { + status: 'loggedOut', + currentChannel: null, + notificationMessage: null, + orderedServers: [], + serverNotifications: null, + channelNotifications: null, + }; + setFunction('openChannel', async c => { + // if (!this.state.currentChannel || this.state.currentChannel?.server?._id != c.server?._id) c.server?.fetchMembers() + this.setState({currentChannel: c}); + app.openLeftMenu(false); + }); + setFunction('joinInvite', async (i: string) => { + await client.joinInvite(i); + }); + setFunction('logOut', async () => { + console.log( + `[AUTH] Logging out of current session... (user: ${client.user?._id})`, + ); + storage.set('token', ''); + storage.set('sessionID', ''); + this.setState({status: 'loggedOut', currentChannel: null}); + await client.logout(); + app.setLoggedOutScreen('loginPage'); + }); + } + componentDidUpdate(_, prevState) { + if (prevState.status !== this.state.status) { + randomizeRemark(); + } + } + async componentDidMount() { + console.log(`[APP] Mounted component (${new Date().getTime()})`); + + let defaultNotif = await createChannel(); + console.log(`[NOTIFEE] Created channel: ${defaultNotif}`); + + checkLastVersion(); + + client.on('connecting', () => { + app.setLoadingStage('connecting'); + console.log(`[APP] Connecting to instance... (${new Date().getTime()})`); + }); + + client.on('connected', () => { + app.setLoadingStage('connected'); + console.log(`[APP] Connected to instance (${new Date().getTime()})`); + }); + + client.on('ready', async () => { + let orderedServers, + server, + channel = null; + try { + const rawSettings = await client.syncFetchSettings([ + 'ordering', + 'notifications', + ]); + try { + orderedServers = JSON.parse(rawSettings.ordering[1]).servers; + ({server, channel} = JSON.parse(rawSettings.notifications[1])); + } catch (err) { + console.log(`[APP] Error parsing fetched settings: ${err}`); + } + } catch (err) { + console.log(`[APP] Error fetching settings: ${err}`); + } + + this.setState({ + status: 'loggedIn', + network: 'ready', + orderedServers, + serverNotifications: server, + channelNotifications: channel, + }); + console.log(`[APP] Client is ready (${new Date().getTime()})`); + + setUpNotifeeListener(client, this.setState); + + if (app.settings.get('app.reopenLastChannel')) { + openLastChannel(); + } + }); + + client.on('dropped', async () => { + this.setState({network: 'dropped'}); + }); + + client.on('message', async msg => { + console.log(`[APP] Handling message ${msg._id}`); + + let channelNotif = this.state.channelNotifications + ? this.state.channelNotifications[msg.channel?._id] + : undefined; + let serverNotif = this.state.serverNotifications + ? this.state.serverNotifications[msg.channel?.server?._id] + : undefined; + + const isMuted = + (channelNotif && channelNotif === 'none') || + channelNotif === 'muted' || + (serverNotif && serverNotif === 'none') || + serverNotif === 'muted'; + + const alwaysNotif = + channelNotif === 'all' || (!isMuted && serverNotif === 'all'); + + const mentionsUser = + (msg.mention_ids?.includes(client.user?._id!) && + (app.settings.get('app.notifications.notifyOnSelfPing') || + msg.author?._id !== client.user?._id)) || + msg.channel?.channel_type === 'DirectMessage'; + + const shouldNotif = + (alwaysNotif && + (app.settings.get('app.notifications.notifyOnSelfPing') || + msg.author?._id !== client.user?._id)) || + (!isMuted && mentionsUser); + + console.log( + `[NOTIFICATIONS] Should notify for ${msg._id}: ${shouldNotif} (channel/server muted? ${isMuted}, notifications for all messages enabled? ${alwaysNotif}, message mentions the user? ${mentionsUser})`, + ); + + if (app.settings.get('app.notifications.enabled') && shouldNotif) { + console.log( + `[NOTIFICATIONS] Pushing notification for message ${msg._id}`, + ); + if (this.state.currentChannel !== msg.channel) { + this.setState({notificationMessage: msg}); + await sleep(5000); + this.setState({notificationMessage: null}); + } + + await sendNotifeeNotification(msg, client, defaultNotif); + } + }); + + client.on('packet', async p => { + if (p.type === 'UserSettingsUpdate') { + console.log('[WEBSOCKET] Synced settings updated'); + try { + if ('ordering' in p.update) { + const orderedServers = JSON.parse(p.update.ordering[1]).servers; + this.setState({orderedServers}); + } + if ('notifications' in p.update) { + const {server, channel} = JSON.parse(p.update.notifications[1]); + this.setState({ + serverNotifications: server, + channelNotifications: channel, + }); + } + } catch (err) { + console.log(`[APP] Error fetching settings: ${err}`); + } + } + }); + + client.on('server/delete', async s => { + const currentServer = app.getCurrentServer(); + if (currentServer === s) { + app.openServer(undefined); + app.openChannel(null); + } + }); + + await loginWithSavedToken(this.state.status); + } + + render() { + return ; + } +} + +const generateLocalStyles = (currentTheme: Theme) => { + return StyleSheet.create({ + app: { + flex: 1, + backgroundColor: currentTheme.backgroundPrimary, + }, + }); +}; diff --git a/src/MessageView.tsx b/src/MessageView.tsx index fc060cab..9dd338d5 100644 --- a/src/MessageView.tsx +++ b/src/MessageView.tsx @@ -15,7 +15,8 @@ import {ErrorBoundary} from 'react-error-boundary'; import {Channel, Message as RevoltMessage} from 'revolt.js'; -import {client, app} from './Generic'; +import {app} from './Generic'; +import { client } from './lib/client'; import {MessageBox} from './components/MessageBox'; import {styles} from './Theme'; import {Button, Text} from './components/common/atoms'; diff --git a/src/Modals.tsx b/src/Modals.tsx index 98e87f97..808a82f0 100644 --- a/src/Modals.tsx +++ b/src/Modals.tsx @@ -4,7 +4,8 @@ import {observer} from 'mobx-react-lite'; import {API, Channel, Server, User} from 'revolt.js'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from './lib/client'; import { CreateChannelModalProps, DeletableObject, diff --git a/src/SideMenu.tsx b/src/SideMenu.tsx index ebfa08aa..15606f95 100644 --- a/src/SideMenu.tsx +++ b/src/SideMenu.tsx @@ -8,19 +8,20 @@ import { View, } from 'react-native'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import {useBackHandler} from '@react-native-community/hooks'; import {Drawer} from 'react-native-drawer-layout'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Server} from 'revolt.js'; -import {app, setFunction, client} from './Generic'; +import {app, setFunction} from './Generic'; +import { client } from './lib/client'; import {Avatar, Button} from './components/common/atoms'; import {ChannelList} from './components/navigation/ChannelList'; import {ServerList} from './components/navigation/ServerList'; import {ChannelView} from './components/views/ChannelView'; import {DEFAULT_API_URL} from './lib/consts'; +import {storage} from '@rvmob/lib/storage'; import {commonValues, Theme, ThemeContext} from '@rvmob/lib/themes'; const SideMenu = ({ @@ -40,7 +41,7 @@ const SideMenu = ({ ); function setCurrentServer(s: Server | null) { setCurrentServerInner(s); - AsyncStorage.setItem('lastServer', s?._id || 'DirectMessage'); + storage.set('lastServer', s?._id || 'DirectMessage'); } setFunction('getCurrentServer', () => { return currentServer?._id ?? undefined; diff --git a/src/components/ImageViewer.tsx b/src/components/ImageViewer.tsx index 7f8afde3..84fc0ace 100644 --- a/src/components/ImageViewer.tsx +++ b/src/components/ImageViewer.tsx @@ -5,7 +5,7 @@ import {gestureHandlerRootHOC} from 'react-native-gesture-handler'; import ImageViewerCore from 'react-native-reanimated-image-viewer'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from '@rvmob/components/common/atoms'; import {GapView} from '@rvmob/components/layout'; import {commonValues, Theme, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index 4b302d33..c8031eb5 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -3,7 +3,6 @@ import {Platform, Pressable, StyleSheet, TextInput, View} from 'react-native'; import {useTranslation} from 'react-i18next'; import {observer} from 'mobx-react-lite'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import {type DocumentPickerResponse} from 'react-native-document-picker'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; @@ -11,11 +10,13 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import {Channel, Message} from 'revolt.js'; import {ulid} from 'ulid'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {DocumentPicker} from '@rvmob/crossplat/DocumentPicker'; import {Avatar, Text, Username} from '@rvmob/components/common/atoms'; import {USER_IDS} from '@rvmob/lib/consts'; +import {storage} from '@rvmob/lib/storage'; import {commonValues, Theme, ThemeContext} from '@rvmob/lib/themes'; import {ReplyingMessage} from '@rvmob/lib/types'; import {getReadableFileSize, showToast} from '@rvmob/lib/utils'; @@ -277,7 +278,7 @@ export const MessageBox = observer((props: MessageBoxProps) => { ), }); let uploaded = []; - const token = await AsyncStorage.getItem('token'); + const token = storage.getString('token'); if (token) { for (let a of attachments) { const formdata = new FormData(); diff --git a/src/components/common/atoms/Avatar.tsx b/src/components/common/atoms/Avatar.tsx index ac60ed2f..7db0fc8b 100644 --- a/src/components/common/atoms/Avatar.tsx +++ b/src/components/common/atoms/Avatar.tsx @@ -5,7 +5,8 @@ import {observer} from 'mobx-react-lite'; import {Server, User, Channel} from 'revolt.js'; import {Image} from '@rvmob/crossplat/Image'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {DEFAULT_MAX_SIDE} from '@rvmob/lib/consts'; import {ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/common/atoms/GeneralAvatar.tsx b/src/components/common/atoms/GeneralAvatar.tsx index 824621b2..0fcb227f 100644 --- a/src/components/common/atoms/GeneralAvatar.tsx +++ b/src/components/common/atoms/GeneralAvatar.tsx @@ -1,7 +1,7 @@ import {View} from 'react-native'; import {Image} from '@rvmob/crossplat/Image'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {DEFAULT_MAX_SIDE} from '@rvmob/lib/consts'; export const GeneralAvatar = ({ diff --git a/src/components/common/atoms/Username.tsx b/src/components/common/atoms/Username.tsx index ac485697..c9cc0615 100644 --- a/src/components/common/atoms/Username.tsx +++ b/src/components/common/atoms/Username.tsx @@ -4,7 +4,8 @@ import {observer} from 'mobx-react-lite'; import {Server, User} from 'revolt.js'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from './Text'; import {USER_IDS} from '@rvmob/lib/consts'; import {ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/common/messaging/Emoji.tsx b/src/components/common/messaging/Emoji.tsx index a07ff618..24cf4adc 100644 --- a/src/components/common/messaging/Emoji.tsx +++ b/src/components/common/messaging/Emoji.tsx @@ -4,7 +4,8 @@ import {StyleSheet} from 'react-native'; import {SvgUri} from 'react-native-svg'; import {Image} from '@rvmob/crossplat/Image'; -import {client, app} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from '../atoms'; import { RE_CUSTOM_EMOJI, diff --git a/src/components/common/messaging/InviteEmbed.tsx b/src/components/common/messaging/InviteEmbed.tsx index feb1f0d6..4970e49d 100644 --- a/src/components/common/messaging/InviteEmbed.tsx +++ b/src/components/common/messaging/InviteEmbed.tsx @@ -4,7 +4,8 @@ import {observer} from 'mobx-react-lite'; import {API, Message} from 'revolt.js'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Button, GeneralAvatar, Text} from '../atoms'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index f5741120..9d4fd47a 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -13,7 +13,8 @@ import {enGB, enUS} from 'date-fns/locale'; import {Message as RevoltMessage} from 'revolt.js'; import {decodeTime} from 'ulid'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Avatar, Text, Username} from '@rvmob/components/common/atoms'; import {MarkdownView} from '@rvmob/components/common/MarkdownView'; import {InviteEmbed} from '@rvmob/components/common/messaging/InviteEmbed'; diff --git a/src/components/common/messaging/MessageEmbed.tsx b/src/components/common/messaging/MessageEmbed.tsx index c90f58d5..ab1019b6 100644 --- a/src/components/common/messaging/MessageEmbed.tsx +++ b/src/components/common/messaging/MessageEmbed.tsx @@ -5,7 +5,8 @@ import {observer} from 'mobx-react-lite'; import {API} from 'revolt.js'; import {Image} from '@rvmob/crossplat/Image'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {MarkdownView} from '../MarkdownView'; import {Link, Text} from '../atoms'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/common/messaging/MessageReactions.tsx b/src/components/common/messaging/MessageReactions.tsx index f262087d..264b7703 100644 --- a/src/components/common/messaging/MessageReactions.tsx +++ b/src/components/common/messaging/MessageReactions.tsx @@ -5,7 +5,7 @@ import {observer} from 'mobx-react-lite'; import {Message} from 'revolt.js'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from '@rvmob/components/common/atoms'; import {Image} from '@rvmob/crossplat/Image'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx index 83436f4e..3897f1f7 100644 --- a/src/components/common/messaging/SystemMessage.tsx +++ b/src/components/common/messaging/SystemMessage.tsx @@ -8,7 +8,8 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Message as RevoltMessage} from 'revolt.js'; import {Text, Username} from '@rvmob/components/common/atoms'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Theme, ThemeContext} from '@rvmob/lib/themes'; const SYSTEM_MESSAGE_ICONS = { diff --git a/src/components/common/profile/RoleView.tsx b/src/components/common/profile/RoleView.tsx index b424dab9..3e1db1b3 100644 --- a/src/components/common/profile/RoleView.tsx +++ b/src/components/common/profile/RoleView.tsx @@ -4,7 +4,7 @@ import {observer} from 'mobx-react-lite'; import {Server, User} from 'revolt.js'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from '@rvmob/components/common/atoms/Text'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; import {getColour} from '@rvmob/lib/utils'; diff --git a/src/components/common/settings/sections/app/AccountSettingsSection.tsx b/src/components/common/settings/sections/app/AccountSettingsSection.tsx index eed3dc67..dd08a535 100644 --- a/src/components/common/settings/sections/app/AccountSettingsSection.tsx +++ b/src/components/common/settings/sections/app/AccountSettingsSection.tsx @@ -2,15 +2,16 @@ import {useContext, useEffect, useState} from 'react'; import {Pressable, View} from 'react-native'; import {observer} from 'mobx-react-lite'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import Clipboard from '@react-native-clipboard/clipboard'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {Text} from '@rvmob/components/common/atoms'; import {GapView} from '@rvmob/components/layout'; import {SettingsEntry} from '@rvmob/components/common/settings/atoms'; +import {storage} from '@rvmob/lib/storage'; import {ThemeContext} from '@rvmob/lib/themes'; export const AccountSettingsSection = observer(() => { @@ -20,7 +21,7 @@ export const AccountSettingsSection = observer(() => { email: '', mfaEnabled: false, sessions: [] as {_id: string; name: string}[], - sessionID: '' as string | null, + sessionID: '' as string | undefined, }); const [showEmail, setShowEmail] = useState(false); @@ -29,7 +30,7 @@ export const AccountSettingsSection = observer(() => { const e = await client.api.get('/auth/account/'); const m = await client.api.get('/auth/mfa/'); const s = await client.api.get('/auth/session/all'); - const i = await AsyncStorage.getItem('sessionID'); + const i = storage.getString('sessionID'); setAuthInfo({ email: e.email, mfaEnabled: m.totp_mfa ?? m.security_key_mfa ?? false, diff --git a/src/components/common/settings/sections/app/ProfileSettingsSection.tsx b/src/components/common/settings/sections/app/ProfileSettingsSection.tsx index c0663075..c61dcd73 100644 --- a/src/components/common/settings/sections/app/ProfileSettingsSection.tsx +++ b/src/components/common/settings/sections/app/ProfileSettingsSection.tsx @@ -5,7 +5,8 @@ import {observer} from 'mobx-react-lite'; import Clipboard from '@react-native-clipboard/clipboard'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {Text} from '@rvmob/components/common/atoms'; import {SettingsEntry} from '@rvmob/components/common/settings/atoms'; diff --git a/src/components/common/settings/sections/server/EmojiSettingsSection.tsx b/src/components/common/settings/sections/server/EmojiSettingsSection.tsx index 40afdc4f..12fdbb02 100644 --- a/src/components/common/settings/sections/server/EmojiSettingsSection.tsx +++ b/src/components/common/settings/sections/server/EmojiSettingsSection.tsx @@ -7,7 +7,7 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Server} from 'revolt.js'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {Text} from '@rvmob/components/common/atoms'; import {SettingsEntry} from '@rvmob/components/common/settings/atoms'; diff --git a/src/components/navigation/ChannelList.tsx b/src/components/navigation/ChannelList.tsx index 8fbe24a4..b62670aa 100644 --- a/src/components/navigation/ChannelList.tsx +++ b/src/components/navigation/ChannelList.tsx @@ -14,7 +14,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import type {Channel, Server} from 'revolt.js'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {ChannelButton, Text} from '../common/atoms'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/navigation/ServerList.tsx b/src/components/navigation/ServerList.tsx index b945d7ea..f0a2132c 100644 --- a/src/components/navigation/ServerList.tsx +++ b/src/components/navigation/ServerList.tsx @@ -6,7 +6,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import {decodeTime} from 'ulid'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Text} from '../common/atoms'; import {Image} from '@rvmob/crossplat/Image'; import {DEFAULT_MAX_SIDE} from '@rvmob/lib/consts'; diff --git a/src/components/pages/FriendsPage.tsx b/src/components/pages/FriendsPage.tsx index 4da8d073..8f95b839 100644 --- a/src/components/pages/FriendsPage.tsx +++ b/src/components/pages/FriendsPage.tsx @@ -4,7 +4,8 @@ import {observer} from 'mobx-react-lite'; import {User} from 'revolt.js'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {MiniProfile} from '@rvmob/components/common/profile'; import {ChannelHeader} from '@rvmob/components/navigation/ChannelHeader'; diff --git a/src/components/pages/HomePage.tsx b/src/components/pages/HomePage.tsx index a8993d32..48133ad1 100644 --- a/src/components/pages/HomePage.tsx +++ b/src/components/pages/HomePage.tsx @@ -2,7 +2,8 @@ import {TouchableOpacity, View} from 'react-native'; import {useTranslation} from 'react-i18next'; import {observer} from 'mobx-react-lite'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import { SPECIAL_DATES, SPECIAL_DATE_OBJECTS, diff --git a/src/components/sheets/BotInviteSheet.tsx b/src/components/sheets/BotInviteSheet.tsx index 754c7fc2..6bb81f20 100644 --- a/src/components/sheets/BotInviteSheet.tsx +++ b/src/components/sheets/BotInviteSheet.tsx @@ -4,7 +4,7 @@ import {observer} from 'mobx-react-lite'; import {Server, User} from 'revolt.js'; -import {client} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Button, GeneralAvatar, Text} from '@rvmob/components/common/atoms'; import {ServerList} from '@rvmob/components/navigation/ServerList'; import {ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/sheets/ProfileSheet.tsx b/src/components/sheets/ProfileSheet.tsx index 6a97c86b..fb397843 100644 --- a/src/components/sheets/ProfileSheet.tsx +++ b/src/components/sheets/ProfileSheet.tsx @@ -10,7 +10,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import {User, Server} from 'revolt.js'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import { Avatar, diff --git a/src/components/sheets/ReportSheet.tsx b/src/components/sheets/ReportSheet.tsx index d32cb22d..c34e4bdb 100644 --- a/src/components/sheets/ReportSheet.tsx +++ b/src/components/sheets/ReportSheet.tsx @@ -7,7 +7,8 @@ import {useBackHandler} from '@react-native-community/hooks'; import {Message} from 'revolt.js'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Avatar, Button, Text, Username} from '@rvmob/components/common/atoms'; import {BottomSheet} from '@rvmob/components/common/BottomSheet'; import {MarkdownView} from '@rvmob/components/common/MarkdownView'; diff --git a/src/components/sheets/ServerInfoSheet.tsx b/src/components/sheets/ServerInfoSheet.tsx index 392c2903..bb1d3252 100644 --- a/src/components/sheets/ServerInfoSheet.tsx +++ b/src/components/sheets/ServerInfoSheet.tsx @@ -9,7 +9,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import {Member, Server} from 'revolt.js'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {SERVER_FLAGS, SPECIAL_SERVERS} from '@rvmob/lib/consts'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/sheets/ServerInviteSheet.tsx b/src/components/sheets/ServerInviteSheet.tsx index b7fe430a..657871b8 100644 --- a/src/components/sheets/ServerInviteSheet.tsx +++ b/src/components/sheets/ServerInviteSheet.tsx @@ -6,7 +6,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import {API} from 'revolt.js'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Button, GeneralAvatar, Text} from '@rvmob/components/common/atoms'; import {Image} from '@rvmob/crossplat/Image'; import {commonValues, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/sheets/ServerSettingsSheet.tsx b/src/components/sheets/ServerSettingsSheet.tsx index 813dd331..c814d341 100644 --- a/src/components/sheets/ServerSettingsSheet.tsx +++ b/src/components/sheets/ServerSettingsSheet.tsx @@ -8,7 +8,8 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Server} from 'revolt.js'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {Image} from '@rvmob/crossplat/Image'; import {MAX_SIDE_HQ} from '@rvmob/lib/consts'; import {Theme, ThemeContext} from '@rvmob/lib/themes'; diff --git a/src/components/sheets/SettingsSheet.tsx b/src/components/sheets/SettingsSheet.tsx index 49194734..2238d0b2 100644 --- a/src/components/sheets/SettingsSheet.tsx +++ b/src/components/sheets/SettingsSheet.tsx @@ -3,7 +3,6 @@ import {Platform, ScrollView, View} from 'react-native'; import {useTranslation} from 'react-i18next'; import {observer} from 'mobx-react-lite'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import Clipboard from '@react-native-clipboard/clipboard'; import { getApiLevel, @@ -14,14 +13,16 @@ import { import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import {app, client, setFunction} from '@rvmob/Generic'; +import {app, setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {OPEN_ISSUES} from '@rvmob/lib/consts'; +import {storage} from '@rvmob/lib/storage'; import {ThemeContext} from '@rvmob/lib/themes'; import {SettingsSection} from '@rvmob/lib/types'; import {openUrl} from '@rvmob/lib/utils'; import {styles} from '@rvmob/Theme'; -import {BackButton, ContextButton, Text} from '../common/atoms'; -import {SettingsCategory} from '../common/settings'; +import {BackButton, ContextButton, Text} from '@rvmob/components/common/atoms'; +import {SettingsCategory} from '@rvmob/components/common/settings'; import { AppInfoSection, AccountSettingsSection, @@ -44,7 +45,7 @@ async function copyDebugInfo() { appInfo: { userID: client.user?._id ?? 'ERR_ID_UNDEFINED', - settings: await AsyncStorage.getItem('settings'), + settings: storage.getString('settings'), version: app.version, }, }; diff --git a/src/components/sheets/StatusSheet.tsx b/src/components/sheets/StatusSheet.tsx index c9407d66..2706e86e 100644 --- a/src/components/sheets/StatusSheet.tsx +++ b/src/components/sheets/StatusSheet.tsx @@ -5,7 +5,8 @@ import {observer} from 'mobx-react-lite'; import BottomSheetCore from '@gorhom/bottom-sheet'; import {useBackHandler} from '@react-native-community/hooks'; -import {client, setFunction} from '@rvmob/Generic'; +import {setFunction} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {ContextButton, InputWithButton, Text} from '../common/atoms'; import {BottomSheet} from '../common/BottomSheet'; import {STATUSES} from '@rvmob/lib/consts'; diff --git a/src/lib/auth/login.ts b/src/lib/auth/login.ts index ae4efdb0..c859be7a 100644 --- a/src/lib/auth/login.ts +++ b/src/lib/auth/login.ts @@ -1,11 +1,12 @@ import {Platform} from 'react-native'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import {API} from 'revolt.js'; -import {decodeTime} from 'ulid'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; +import {storage} from '@rvmob/lib/storage'; -import {app, client} from '@rvmob/Generic'; -import {API} from 'revolt.js'; +import {decodeTime} from 'ulid'; async function connectAndSave( session: API.ResponseLogin, @@ -21,10 +22,8 @@ async function connectAndSave( const token = session.token; console.log('[AUTH] Logging in with a new token...'); await client.useExistingSession({token: token}); - await AsyncStorage.multiSet([ - ['token', token], - ['sessionID', session._id], - ]); + storage.set('token', token); + storage.set('sessionID', session._id); console.log('[AUTH] Successfuly logged in and saved the token/session ID!'); setStatus('loggedIn'); } catch (err) { @@ -133,7 +132,7 @@ export async function loginWithToken( token, }); console.log(`[AUTH] Setting saved token to ${token}`); - AsyncStorage.setItem('token', token); + storage.set('token', token); setStatus('loggedIn'); } catch (tokenErr) { console.error(tokenErr); @@ -144,30 +143,26 @@ export async function loginWithToken( } export async function loginWithSavedToken(status: any) { - AsyncStorage.getItem('token', async (err, res) => { - if (!err) { - if (typeof res !== 'string') { - console.log( - `[AUTH] Saved token was not a string: ${typeof res}, ${res}`, - ); - app.setLoggedOutScreen('loginPage'); - return; - } - try { - await client.useExistingSession({token: res}); - } catch (e: any) { - console.log(e); - !( - e.message?.startsWith('Read error') || e.message === 'Network Error' - ) && client.user - ? app.setLoggedOutScreen('loginPage') - : status === 'loggedIn' - ? null - : app.setLoggedOutScreen('loginPage'); - } - } else { - console.log(err); + try { + const res = storage.getString('token'); + if (typeof res !== 'string') { + console.log(`[AUTH] Saved token was not a string: ${typeof res}, ${res}`); app.setLoggedOutScreen('loginPage'); + return; } - }); + try { + await client.useExistingSession({token: res}); + } catch (e: any) { + console.log(e); + !(e.message?.startsWith('Read error') || e.message === 'Network Error') && + client.user + ? app.setLoggedOutScreen('loginPage') + : status === 'loggedIn' + ? null + : app.setLoggedOutScreen('loginPage'); + } + } catch (err) { + console.log(err); + app.setLoggedOutScreen('loginPage'); + } } diff --git a/src/lib/client/index.ts b/src/lib/client/index.ts new file mode 100644 index 00000000..7c04e0a4 --- /dev/null +++ b/src/lib/client/index.ts @@ -0,0 +1,31 @@ +import { Client } from 'revolt.js'; + +import { app } from '@rvmob/Generic'; +import { DEFAULT_API_URL } from '@rvmob/lib/consts'; + +function getAPIURL() { + console.log(`[APP] Initialised settings (${new Date().getTime()})`); + let url: string = ''; + console.log('[AUTH] Getting API URL...'); + const instance = app.settings.get('app.instance') as string | + null | + undefined; + if (!instance) { + console.log( + '[AUTH] Unable to fetch app.instance; setting apiURL to default' + ); + url = DEFAULT_API_URL; + } else { + console.log(`[AUTH] Fetched app.instance; setting apiURL to ${instance}`); + url = instance; + } + return url; +} + +const apiURL = getAPIURL(); +console.log(`[AUTH] Creating client... (instance: ${apiURL})`); + +export let client = new Client({ + unreads: true, + apiURL: apiURL, +}); diff --git a/src/lib/storage/index.ts b/src/lib/storage/index.ts new file mode 100644 index 00000000..b70a50e2 --- /dev/null +++ b/src/lib/storage/index.ts @@ -0,0 +1,3 @@ +import {MMKV} from 'react-native-mmkv'; + +export const storage = new MMKV(); diff --git a/src/lib/storage/migration.ts b/src/lib/storage/migration.ts new file mode 100644 index 00000000..27a661a9 --- /dev/null +++ b/src/lib/storage/migration.ts @@ -0,0 +1,18 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import {storage} from '.'; + +export async function migrateToMMKV() { + console.log('hi'); + const keys = await AsyncStorage.getAllKeys(); + console.log(keys); + + for (const key of keys) { + if (key === 'token' || key === 'settings') { + const value = await AsyncStorage.getItem(key); + console.log(value); + storage.set(key, value ?? ''); + } + } + + storage.set('hasMigrated', true); +} diff --git a/src/lib/storage/utils.ts b/src/lib/storage/utils.ts new file mode 100644 index 00000000..c6c1f5d7 --- /dev/null +++ b/src/lib/storage/utils.ts @@ -0,0 +1,31 @@ +import { app } from '@rvmob/Generic'; +import { storage } from '@rvmob/lib/storage'; +import { Setting } from '@rvmob/lib/types'; + +export function initialiseSettings() { + const s = storage.getString('settings'); + if (s) { + try { + const settings = JSON.parse(s) as {key: string; value: any}[]; + settings.forEach(key => { + let st: Setting | undefined; + for (const setting of app.settings.list) { + if (setting.key === key.key) { + st = setting; + } + } + if (st) { + st.value = key.value; + st.onInitialize && st.onInitialize(key.value); + } else { + // ignore known good key + if (key.key !== 'app.lastVersion') { + console.warn(`[SETTINGS] Unknown setting in MMKV settings: ${key}`); + } + } + }); + } catch (e) { + console.error(e); + } + } + } diff --git a/src/lib/utils/utils.ts b/src/lib/utils/utils.ts index bde4c871..947ee07c 100644 --- a/src/lib/utils/utils.ts +++ b/src/lib/utils/utils.ts @@ -4,7 +4,8 @@ import {differenceInMinutes, isSameDay} from 'date-fns'; import {Channel, Message} from 'revolt.js'; import {decodeTime} from 'ulid'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import { DEFAULT_MESSAGE_LOAD_COUNT, DISCOVER_URL, diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx index 404c2998..1c79951c 100644 --- a/src/pages/auth/LoginPage.tsx +++ b/src/pages/auth/LoginPage.tsx @@ -5,7 +5,8 @@ import {useTranslation} from 'react-i18next'; import {useBackHandler} from '@react-native-community/hooks'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import { BackButton, Button, diff --git a/src/pages/discover/DiscoverPage.tsx b/src/pages/discover/DiscoverPage.tsx index a245c10b..3c9d7825 100644 --- a/src/pages/discover/DiscoverPage.tsx +++ b/src/pages/discover/DiscoverPage.tsx @@ -4,7 +4,8 @@ import {useTranslation} from 'react-i18next'; import {DOMParser} from '@xmldom/xmldom'; -import {app, client} from '@rvmob/Generic'; +import {app} from '@rvmob/Generic'; +import { client } from '@rvmob/lib/client'; import {styles} from '@rvmob/Theme'; import {Button, GeneralAvatar, Text} from '@rvmob/components/common/atoms'; import {ChannelHeader} from '@rvmob/components/navigation/ChannelHeader'; diff --git a/yarn.lock b/yarn.lock index 757d60ea..ac716e1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12142,6 +12142,16 @@ __metadata: languageName: node linkType: hard +"react-native-mmkv@npm:^3.2.0": + version: 3.2.0 + resolution: "react-native-mmkv@npm:3.2.0" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10c0/41abc5b46d61de7bf0f6af0ed66fab19a391d3fdc30b011cc49ba4dce65ab7c7b1c53eda768bc71f718543e89c7d46848dab2cd537b2af39afd624a8b4f0fec4 + languageName: node + linkType: hard + "react-native-reanimated-image-viewer@npm:^1.0.2": version: 1.0.2 resolution: "react-native-reanimated-image-viewer@npm:1.0.2" @@ -12902,6 +12912,7 @@ __metadata: react-native-drawer-layout: "npm:^4.1.1" react-native-gesture-handler: "npm:^2.21.2" react-native-get-random-values: "npm:^1.11.0" + react-native-mmkv: "npm:^3.2.0" react-native-reanimated: "npm:3.16.6" react-native-reanimated-image-viewer: "npm:^1.0.2" react-native-svg: "npm:^15.10.1"