diff --git a/.yarn/patches/react-native-htmlview-npm-0.16.0-501f1b89ba.patch b/.yarn/patches/react-native-htmlview-npm-0.16.0-501f1b89ba.patch deleted file mode 100644 index f42c82e7..00000000 --- a/.yarn/patches/react-native-htmlview-npm-0.16.0-501f1b89ba.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/HTMLView.js b/HTMLView.js -index 43f8b7eb552d9a44b5feef74ec2ae7d7ddbc2fca..334d144f1fb96067726bca199ff37267c2fa0fb2 100644 ---- a/HTMLView.js -+++ b/HTMLView.js -@@ -1,7 +1,7 @@ - import React, {PureComponent} from 'react'; - import PropTypes from 'prop-types'; - import htmlToElement from './htmlToElement'; --import {Linking, Platform, StyleSheet, View, ViewPropTypes} from 'react-native'; -+import {Linking, Platform, StyleSheet, View} from 'react-native'; - - const boldStyle = {fontWeight: 'bold'}; - const italicStyle = {fontStyle: 'italic'}; -@@ -146,7 +146,7 @@ HtmlView.propTypes = { - renderNode: PropTypes.func, - RootComponent: PropTypes.func, - rootComponentProps: PropTypes.object, -- style: ViewPropTypes.style, -+ style: PropTypes.object, - stylesheet: PropTypes.object, - TextComponent: PropTypes.func, - textComponentProps: PropTypes.object, diff --git a/README.md b/README.md index 7d29999d..34d1f97c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GPL-3.0](https://img.shields.io/github/license/tooot-app/push)](LICENSE) ![GitHub issues](https://img.shields.io/github/issues/tooot-app/app) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/tooot-app/app?include_prereleases) [![Crowdin](https://badges.crowdin.net/tooot/localized.svg)](https://crowdin.tooot.app/project/tooot) -![GitHub Workflow Status (candidate)](https://img.shields.io/github/workflow/status/tooot-app/app/build/candidate?label=build%20candidate) ![GitHub Workflow Status (release)](https://img.shields.io/github/workflow/status/tooot-app/app/build/release?label=build%20release) +![GitHub Workflow Status (candidate)](https://img.shields.io/github/actions/workflow/status/tooot-app/app/build.yml?branch=candidate&label=build%20candidate) ![GitHub Workflow Status (release)](https://img.shields.io/github/actions/workflow/status/tooot-app/app/build.yml?branch=release&label=build%20release) ## Contribute to translation @@ -11,28 +11,16 @@ Please **do not** create a pull request to update translation. tooot's translati ## Special thanks -[@amrtf](https://crowdin.com/profile/amrtf) for Catalan and Spanish translation - -[@forenta](https://github.com/forenta) for German translation - -[@pat](https://piaille.fr/@pat) for French translation - -[@andrigamerita](https://github.com/andrigamerita) for Italian translation - -[@Hikaru](https://github.com/Hikali-47041) and [@la_la](https://mstdn.jp/@la_la_la) for Japanese translation - -[@hellojaccc](https://github.com/hellojaccc) for Korean translation - -[@jan-vandenberg](https://crowdin.com/profile/jan-vandenberg) for Dutch translation - -[@luizpicolo](https://github.com/luizpicolo) for Brazilian Portuguese - -[@janlindblom](https://github.com/janlindblom) for Swedish - -[@ihoryan](https://crowdin.com/profile/ihoryan) for Ukrainian - -[@duy@mas.to](https://mas.to/@duy) for Vietnamese translation - -[@jimmyorz](https://github.com/jimmyorz) for Traditional Chinese translation - -[@jk@mastodon.social](https://mastodon.social/@jk) for the famous Mastodon boop sound +- [@amrtf](https://crowdin.com/profile/amrtf) for Catalan and Spanish translation +- [@forenta](https://github.com/forenta) for German translation +- [@pat](https://piaille.fr/@pat) for French translation +- [@andrigamerita](https://github.com/andrigamerita) for Italian translation +- [@Hikaru](https://github.com/Hikali-47041) and [@la_la](https://mstdn.jp/@la_la_la) for Japanese translation +- [@hellojaccc](https://github.com/hellojaccc) for Korean translation +- [@jan-vandenberg](https://crowdin.com/profile/jan-vandenberg) for Dutch translation +- [@luizpicolo](https://github.com/luizpicolo) for Brazilian Portuguese +- [@janlindblom](https://github.com/janlindblom) for Swedish +- [@ihoryan](https://crowdin.com/profile/ihoryan) for Ukrainian +- [@duy@mas.to](https://mas.to/@duy) for Vietnamese translation +- [@jimmyorz](https://github.com/jimmyorz) for Traditional Chinese translation +- [@jk@mastodon.social](https://mastodon.social/@jk) for the famous Mastodon boop sound diff --git a/babel.config.js b/babel.config.js index 1ffee43c..0d5803fa 100644 --- a/babel.config.js +++ b/babel.config.js @@ -8,11 +8,8 @@ module.exports = function (api) { { root: ['./'], alias: { - '@assets': './assets', - '@root': './src', - '@api': './src/api', - '@helpers': './src/helpers', '@components': './src/components', + '@i18n': './src/i18n', '@screens': './src/screens', '@utils': './src/utils' } diff --git a/demo/screenshots/Tab-Shared-Report.png b/demo/screenshots/Tab-Shared-Report.png new file mode 100644 index 00000000..48eb76bf Binary files /dev/null and b/demo/screenshots/Tab-Shared-Report.png differ diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 731c525f..62516967 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,4 +1,5 @@ Enjoy toooting! This version includes following improvements and fixes: - Allowing adding more context of reports - Option to disable autoplay gif -- Hide boosts from users \ No newline at end of file +- Hide boosts from users +- Followed hashtags are underlined \ No newline at end of file diff --git a/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/zh-Hans/release_notes.txt index af941da8..8225007b 100644 --- a/fastlane/metadata/zh-Hans/release_notes.txt +++ b/fastlane/metadata/zh-Hans/release_notes.txt @@ -1,4 +1,5 @@ toooting愉快!此版本包括以下改进和修复: - 可添加举报细节 - 新增暂停自动播放gif动画选项 -- 隐藏用户的转嘟 \ No newline at end of file +- 隐藏用户的转嘟 +- 下划线高亮正在关注的话题标签 \ No newline at end of file diff --git a/index.js b/index.js index 40a9d636..d292a7a1 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ import { registerRootComponent } from 'expo' -import App from '@root/App' +import App from './src/App' // registerRootComponent calls AppRegistry.registerComponent('main', () => App); // It also ensures that whether you load the app in the Expo client or in a native build, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e148c49d..478da943 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -65,6 +65,9 @@ PODS: - libwebp/mux (1.2.4): - libwebp/demux - libwebp/webp (1.2.4) + - MMKV (1.2.14): + - MMKVCore (~> 1.2.14) + - MMKVCore (1.2.14) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -305,6 +308,9 @@ PODS: - React - react-native-menu (0.7.2): - React + - react-native-mmkv (2.5.1): + - MMKV (>= 1.2.13) + - React-Core - react-native-netinfo (9.3.7): - React-Core - react-native-pager-view (6.1.2): @@ -494,6 +500,7 @@ DEPENDENCIES: - react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`) - react-native-language-detection (from `../node_modules/react-native-language-detection`) - "react-native-menu (from `../node_modules/@react-native-menu/menu`)" + - react-native-mmkv (from `../node_modules/react-native-mmkv`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) - "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)" @@ -527,6 +534,8 @@ SPEC REPOS: - fmt - libevent - libwebp + - MMKV + - MMKVCore - SDWebImage - SDWebImageWebPCoder - Sentry @@ -629,6 +638,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-language-detection" react-native-menu: :path: "../node_modules/@react-native-menu/menu" + react-native-mmkv: + :path: "../node_modules/react-native-mmkv" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-pager-view: @@ -714,6 +725,8 @@ SPEC CHECKSUMS: hermes-engine: 2af7b7a59128f250adfd86f15aa1d5a2ecd39995 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef + MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd + MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: e1866f61af7049eb3d8e08e8b133abd38bc1ca7a RCTTypeSafety: 27c2ac1b00609a432ced1ae701247593f07f901e @@ -736,6 +749,7 @@ SPEC CHECKSUMS: react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac react-native-language-detection: f414937fa715108ab50a6269a3de0bcb95e4ceb0 react-native-menu: 8e172cfcf0e42e92f028e7781eddf84d430cae24 + react-native-mmkv: 69b9c003f10afdd01addf7c6ee784ce42ee2eff3 react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983 react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43 react-native-paste-input: 88709b4fd586ea8cc56ba5e2fc4cdfe90597730c diff --git a/package.json b/package.json index aba133e8..714441c9 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,11 @@ "@react-native-community/blur": "^4.3.0", "@react-native-community/netinfo": "9.3.7", "@react-native-community/segmented-control": "^2.2.2", - "@react-native-menu/menu": "^0.7.2", + "@react-native-menu/menu": "^0.7.3", "@react-navigation/bottom-tabs": "^6.5.2", "@react-navigation/native": "^6.1.1", "@react-navigation/native-stack": "^6.9.7", "@react-navigation/stack": "^6.3.10", - "@reduxjs/toolkit": "^1.9.1", "@sentry/react-native": "4.12.0", "@sharcoux/slider": "^6.1.1", "@tanstack/react-query": "^4.20.4", @@ -60,6 +59,7 @@ "expo-store-review": "^6.0.0", "expo-video-thumbnails": "^7.0.0", "expo-web-browser": "~12.0.0", + "htmlparser2": "^8.0.1", "i18next": "^22.4.6", "linkify-it": "^4.0.1", "lodash": "^4.17.21", @@ -75,10 +75,10 @@ "react-native-feather": "^1.1.2", "react-native-flash-message": "^0.3.1", "react-native-gesture-handler": "~2.8.0", - "react-native-htmlview": "^0.16.0", "react-native-image-picker": "^4.10.3", "react-native-ios-context-menu": "^1.15.1", "react-native-language-detection": "^0.2.2", + "react-native-mmkv": "^2.5.1", "react-native-pager-view": "^6.1.2", "react-native-reanimated": "^2.13.0", "react-native-reanimated-zoom": "^0.3.3", @@ -89,7 +89,6 @@ "react-native-swipe-list-view": "^3.2.9", "react-native-tab-view": "^3.3.4", "react-redux": "^8.0.5", - "redux-persist": "^6.0.0", "rn-placeholder": "^3.0.3", "rtl-detect": "^1.0.4", "valid-url": "^1.0.9", @@ -120,7 +119,6 @@ "resolutions": { "react-native-fast-image@^8.6.3": "patch:react-native-fast-image@npm%3A8.6.3#./.yarn/patches/react-native-fast-image-npm-8.6.3-03ee2d23c0.patch", "expo-av@^13.0.2": "patch:expo-av@npm%3A13.0.2#./.yarn/patches/expo-av-npm-13.0.2-7a651776f1.patch", - "react-native-htmlview@^0.16.0": "patch:react-native-htmlview@npm%3A0.16.0#./.yarn/patches/react-native-htmlview-npm-0.16.0-501f1b89ba.patch", "react-native-share-menu@^6.0.0": "patch:react-native-share-menu@npm%3A6.0.0#./.yarn/patches/react-native-share-menu-npm-6.0.0-f1094c3204.patch", "@types/react-native-share-menu@^5.0.2": "patch:@types/react-native-share-menu@npm%3A5.0.2#./.yarn/patches/@types-react-native-share-menu-npm-5.0.2-373df17ecc.patch" } diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index 80b3e477..d2a83751 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -264,14 +264,6 @@ declare namespace Mastodon { } type Filter = T extends 'v2' ? Filter_V2 : Filter_V1 - type Filter_V1 = { - id: string - phrase: string - context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[] - expires_at?: string - irreversible: boolean - whole_word: boolean - } type Filter_V2 = { id: string title: string @@ -281,6 +273,14 @@ declare namespace Mastodon { keywords: FilterKeyword[] statuses: FilterStatus[] } + type Filter_V1 = { + id: string + phrase: string + context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[] + expires_at?: string + irreversible: boolean + whole_word: boolean + } type FilterKeyword = { id: string; keyword: string; whole_word: boolean } @@ -298,7 +298,45 @@ declare namespace Mastodon { replies_policy: 'none' | 'list' | 'followed' } - type Instance = { + type Instance = T extends 'v2' ? Instance_V2 : Instance_V1 + type Instance_V2 = { + domain: string + title: string + version: string + source_url: string + description: string + usage: { users: { active_month: number } } + thumbnail: { url: string; blurhash?: string; versions?: { '@1x'?: string; '@2x'?: string } } + languages: string[] + configuration: { + urls: { streaming_api: string } + accounts: { max_featured_tags: number } + statuses: { + max_characters: number + max_media_attachments: number + characters_reserved_per_url: number + } + media_attachments: { + supported_mime_types: string[] + image_size_limit: number + image_matrix_limit: number + video_size_limit: number + video_frame_rate_limit: number + video_matrix_limit: number + } + polls: { + max_options: number + max_characters_per_option: number + min_expiration: number + max_expiration: number + } + translation: { enabled: boolean } + registrations: { enabled: boolean; approval_required: boolean; message?: string } + contact: { email: string; account: Account } + rules: Rule[] + } + } + type Instance_V1 = { // Base uri: string title: string diff --git a/src/@types/untyped.d.ts b/src/@types/untyped.d.ts index ad27df89..e00bdb1d 100644 --- a/src/@types/untyped.d.ts +++ b/src/@types/untyped.d.ts @@ -1,7 +1,5 @@ declare module 'gl-react-blurhash' -declare module 'htmlparser2-without-node-native' declare module 'react-native-feather' -declare module 'react-native-htmlview' declare module 'react-native-toast-message' declare module 'rtl-detect' diff --git a/src/App.tsx b/src/App.tsx index a3f00f75..1e872150 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,30 +1,34 @@ import { ActionSheetProvider } from '@expo/react-native-action-sheet' -import getLanguage from '@helpers/getLanguage' -import queryClient from '@helpers/queryClient' -import i18n from '@root/i18n/i18n' -import Screens from '@root/Screens' -import audio from '@root/startup/audio' -import log from '@root/startup/log' -import netInfo from '@root/startup/netInfo' -import push from '@root/startup/push' -import sentry from '@root/startup/sentry' -import timezone from '@root/startup/timezone' -import { persistor, store } from '@root/store' import * as Sentry from '@sentry/react-native' +import { QueryClientProvider } from '@tanstack/react-query' import AccessibilityManager from '@utils/accessibility/AccessibilityManager' -import { changeLanguage } from '@utils/slices/settingsSlice' +import getLanguage from '@utils/helpers/getLanguage' +import queryClient from '@utils/queryHooks' +import audio from '@utils/startup/audio' +import log from '@utils/startup/log' +import netInfo from '@utils/startup/netInfo' +import push from '@utils/startup/push' +import sentry from '@utils/startup/sentry' +import timezone from '@utils/startup/timezone' +import { storage } from '@utils/storage' +import { + getGlobalStorage, + removeAccount, + setAccount, + setGlobalStorage +} from '@utils/storage/actions' +import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV' import ThemeManager from '@utils/styles/ThemeManager' import * as Localization from 'expo-localization' import * as SplashScreen from 'expo-splash-screen' import React, { useCallback, useEffect, useState } from 'react' -import { IntlProvider } from 'react-intl' import { LogBox, Platform } from 'react-native' import { GestureHandlerRootView } from 'react-native-gesture-handler' +import { MMKV } from 'react-native-mmkv' import { SafeAreaProvider } from 'react-native-safe-area-context' import { enableFreeze } from 'react-native-screens' -import { QueryClientProvider } from '@tanstack/react-query' -import { Provider } from 'react-redux' -import { PersistGate } from 'redux-persist/integration/react' +import i18n from './i18n' +import Screens from './screens' Platform.select({ android: LogBox.ignoreLogs(['Setting a timer for a long period of time']) @@ -36,83 +40,95 @@ push() timezone() enableFreeze(true) +log('log', 'App', 'delay splash') +SplashScreen.preventAutoHideAsync() + const App: React.FC = () => { log('log', 'App', 'rendering App') + const [appIsReady, setAppIsReady] = useState(false) const [localCorrupt, setLocalCorrupt] = useState() + const [hasMigrated, setHasMigrated] = useState(versionStorageGlobal !== undefined) + useEffect(() => { - const delaySplash = async () => { - log('log', 'App', 'delay splash') - try { - await SplashScreen.preventAutoHideAsync() - } catch (e) { - console.warn(e) + const prepare = async () => { + if (!hasMigrated) { + try { + await migrateFromAsyncStorage() + setHasMigrated(true) + } catch {} + } else { + log('log', 'App', 'loading from MMKV') + const account = getGlobalStorage.string('account.active') + if (account) { + const storageAccount = new MMKV({ id: account }) + const token = storageAccount.getString('auth.token') + if (token) { + log('log', 'App', `Binding storage of ${account}`) + storage.account = storageAccount + } else { + log('log', 'App', `Token not found for ${account}`) + removeAccount(account) + } + } else { + log('log', 'App', 'No active account available') + const accounts = getGlobalStorage.object('accounts') + if (accounts?.length) { + log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`) + setAccount(accounts[accounts.length - 1]) + } else { + setGlobalStorage('account.active', undefined) + } + } } + + let netInfoRes = undefined + try { + netInfoRes = await netInfo() + } catch {} + + if (netInfoRes && netInfoRes.corrupted && netInfoRes.corrupted.length) { + setLocalCorrupt(netInfoRes.corrupted) + } + + log('log', 'App', `locale: ${Localization.locale}`) + const language = getLanguage() + if (!language) { + if (Platform.OS !== 'ios') { + setGlobalStorage('app.language', 'en') + } + i18n.changeLanguage('en') + } else { + i18n.changeLanguage(language) + } + + setAppIsReady(true) } - delaySplash() + + prepare() }, []) - - const onBeforeLift = useCallback(async () => { - let netInfoRes = undefined - try { - netInfoRes = await netInfo() - } catch {} - - if (netInfoRes && netInfoRes.corrupted && netInfoRes.corrupted.length) { - setLocalCorrupt(netInfoRes.corrupted) - } - - log('log', 'App', 'hide splash') - try { + const onLayoutRootView = useCallback(async () => { + if (appIsReady) { + log('log', 'App', 'hide splash') await SplashScreen.hideAsync() - return Promise.resolve() - } catch (e) { - console.warn(e) - return Promise.reject() } - }, []) + }, [appIsReady]) + if (!appIsReady) { + return null + } return ( - + - - { - log('log', 'App', 'bootstrapped') - if (bootstrapped) { - log('log', 'App', 'loading actual app :)') - log('log', 'App', `Locale: ${Localization.locale}`) - const language = getLanguage() - if (!language) { - if (Platform.OS !== 'ios') { - store.dispatch(changeLanguage('en')) - } - i18n.changeLanguage('en') - } else { - i18n.changeLanguage(language) - } - - return ( - - - - - - - - - - - - ) - } else { - return null - } - }} - /> - + + + + + + + + + ) diff --git a/src/components/AccountButton.tsx b/src/components/AccountButton.tsx index 21ef8288..d36f6bb4 100644 --- a/src/components/AccountButton.tsx +++ b/src/components/AccountButton.tsx @@ -1,19 +1,24 @@ import { useNavigation } from '@react-navigation/native' -import initQuery from '@utils/initQuery' -import { InstanceLatest } from '@utils/migrations/instances/migration' +import { generateAccountKey, getAccountDetails, setAccount } from '@utils/storage/actions' +import { StorageGlobal } from '@utils/storage/global' import { StyleConstants } from '@utils/styles/constants' import React from 'react' import Button from './Button' import haptics from './haptics' interface Props { - instance: InstanceLatest + account: NonNullable[number] selected?: boolean additionalActions?: () => void } -const AccountButton: React.FC = ({ instance, selected = false, additionalActions }) => { +const AccountButton: React.FC = ({ account, selected = false, additionalActions }) => { const navigation = useNavigation() + const accountDetails = getAccountDetails( + ['auth.account.acct', 'auth.domain', 'auth.account.id'], + account + ) + if (!accountDetails) return null return (