diff --git a/.github/workflows/android-debug.yml b/.github/workflows/android-debug.yml
new file mode 100644
index 00000000..0c70ee23
--- /dev/null
+++ b/.github/workflows/android-debug.yml
@@ -0,0 +1,58 @@
+name: Android debug
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Set up Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: canary
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: temurin
+ cache: gradle
+
+ - name: Set up Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Install Android SDK packages
+ run: sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;27.1.12297006"
+
+ - name: Decode Google services config
+ run: |
+ if [ -z "$GOOGLE_SERVICES_JSON_BASE64" ]; then
+ echo "GOOGLE_SERVICES_JSON_BASE64 secret is required to build Android."
+ exit 1
+ fi
+
+ printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > android/app/google-services.json
+ env:
+ GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
+
+ - name: Build Android debug APK
+ working-directory: android
+ run: ./gradlew :app:assembleDebug --no-daemon
+
+ - name: Upload Android debug APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-debug-apk
+ path: android/app/build/outputs/apk/debug/app-debug.apk
diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml
new file mode 100644
index 00000000..cd3f35ac
--- /dev/null
+++ b/.github/workflows/android-release.yml
@@ -0,0 +1,81 @@
+name: Android release
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Set up Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: canary
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: temurin
+ cache: gradle
+
+ - name: Set up Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Install Android SDK packages
+ run: sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;27.1.12297006"
+
+ - name: Validate release secrets
+ run: |
+ missing=0
+
+ for name in GOOGLE_SERVICES_JSON_BASE64 ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
+ if [ -z "$(printenv "$name")" ]; then
+ echo "$name secret is required."
+ missing=1
+ fi
+ done
+
+ exit "$missing"
+ env:
+ GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
+ ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
+ ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
+
+ - name: Decode Google services config
+ run: printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > android/app/google-services.json
+ env:
+ GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
+
+ - name: Decode Android keystore
+ run: printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore
+ env:
+ ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+
+ - name: Build Android release bundle
+ working-directory: android
+ run: ./gradlew :app:bundleRelease --no-daemon
+ env:
+ ANDROID_KEYSTORE_FILE: ${{ github.workspace }}/android/app/release.keystore
+ ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
+ ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
+
+ - name: Upload Android App Bundle
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-release-aab
+ path: android/app/build/outputs/bundle/release/app-release.aab
diff --git a/__tests__/api/MicroBlogApi.test.js b/__tests__/api/MicroBlogApi.test.js
new file mode 100644
index 00000000..b639a671
--- /dev/null
+++ b/__tests__/api/MicroBlogApi.test.js
@@ -0,0 +1,67 @@
+import axios from 'axios'
+
+import MicroBlogApi from '../../src/api/MicroBlogApi'
+
+jest.mock('axios', () => ({
+ defaults: {},
+ post: jest.fn()
+}))
+
+jest.mock('../../src/stores/Auth', () => ({
+ selected_user: null
+}))
+
+describe('MicroBlogApi Apple sign in', () => {
+ beforeEach(() => {
+ axios.post.mockReset()
+ })
+
+ test('posts Apple credentials and returns verified account data', async () => {
+ axios.post.mockResolvedValue({
+ data: {
+ username: 'vincent',
+ token: 'app-token'
+ }
+ })
+
+ const result = await MicroBlogApi.login_with_apple({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token',
+ email: 'vincent@example.com',
+ full_name: 'Vincent Ritter'
+ })
+
+ expect(axios.post).toHaveBeenCalledWith(
+ '/account/apple',
+ expect.any(FormData)
+ )
+ const form = axios.post.mock.calls[0][1]
+ expect(form.get('user_id')).toBe('apple-user-id')
+ expect(form.get('identity_token')).toBe('apple-identity-token')
+ expect(form.get('email')).toBe('vincent@example.com')
+ expect(form.get('full_name')).toBe('Vincent Ritter')
+ expect(form.get('app_name')).toBe('Micro.blog for iOS')
+ expect(result).toEqual({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ })
+
+ test('returns Apple account error responses', async () => {
+ axios.post.mockResolvedValue({
+ data: {
+ error: 'That username is not available.'
+ }
+ })
+
+ const result = await MicroBlogApi.login_with_apple({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token',
+ username: 'vincent'
+ })
+
+ expect(result).toEqual({
+ error: 'That username is not available.'
+ })
+ })
+})
diff --git a/__tests__/api/MicroBlogAuth.test.js b/__tests__/api/MicroBlogAuth.test.js
new file mode 100644
index 00000000..eb4120b1
--- /dev/null
+++ b/__tests__/api/MicroBlogAuth.test.js
@@ -0,0 +1,40 @@
+import {
+ build_micro_blog_auth_url,
+ extract_micro_blog_callback_params,
+ extract_signin_token,
+ get_micro_blog_redirect_uri,
+ is_micro_blog_callback_url,
+ is_signin_token_url,
+} from '../../src/api/MicroBlogAuth'
+
+describe('MicroBlogAuth helpers', () => {
+ test('builds an IndieAuth URL for the Micro.blog app', () => {
+ const auth_url = build_micro_blog_auth_url({ state: 'abc123' })
+
+ expect(auth_url).toContain('https://micro.blog/indieauth/auth?')
+ expect(auth_url).toContain('client_id=https%3A%2F%2Fmicro.blog%2Fclient.json')
+ expect(auth_url).toContain('app=1')
+ expect(auth_url).toContain('response_type=code')
+ expect(auth_url).toContain('scope=create')
+ expect(auth_url).toContain('state=abc123')
+ expect(auth_url).toContain(`redirect_uri=${encodeURIComponent(get_micro_blog_redirect_uri())}`)
+ })
+
+ test('detects and parses Micro.blog OAuth callbacks', () => {
+ const callback_url = 'microblog://auth/callback?code=CODE123&state=STATE456'
+
+ expect(is_micro_blog_callback_url(callback_url)).toBe(true)
+ expect(extract_micro_blog_callback_params(callback_url)).toEqual({
+ code: 'CODE123',
+ state: 'STATE456',
+ })
+ })
+
+ test('detects legacy microblog sign-in token URLs', () => {
+ const signin_url = 'microblog://signin/token-value-here'
+
+ expect(is_signin_token_url(signin_url)).toBe(true)
+ expect(extract_signin_token(signin_url)).toBe('token-value-here')
+ expect(is_micro_blog_callback_url(signin_url)).toBe(false)
+ })
+})
diff --git a/__tests__/components/highlighting_text.test.js b/__tests__/components/highlighting_text.test.js
new file mode 100644
index 00000000..15280356
--- /dev/null
+++ b/__tests__/components/highlighting_text.test.js
@@ -0,0 +1,47 @@
+import HighlightingText from '../../src/components/text/highlighting_text'
+import App from '../../src/stores/App'
+
+jest.mock('../../src/stores/App', () => ({
+ font_scale: 1
+}))
+
+jest.mock('mobx-react', () => ({
+ observer: component => component
+}))
+
+jest.mock('react-native-webview', () => ({
+ WebView: 'WebView'
+}))
+
+jest.mock('../../src/components/keyboard/editor_keyboard_avoiding_view', () => {
+ const React = require('react')
+ return {
+ EditorKeyboardFrameContext: React.createContext({
+ window_bottom: 0,
+ keyboard_height: 0
+ })
+ }
+})
+
+describe('HighlightingText font scaling', () => {
+ test('uses the full system font scale by default', () => {
+ App.font_scale = 3.5
+ const editor = new HighlightingText({})
+
+ expect(editor.scaledFontSize(18)).toBe(63)
+ })
+
+ test('supports a maximum font size multiplier', () => {
+ App.font_scale = 3.5
+ const editor = new HighlightingText({ maxFontSizeMultiplier: 2 })
+
+ expect(editor.scaledFontSize(18)).toBe(36)
+ })
+
+ test('can disable font scaling', () => {
+ App.font_scale = 3.5
+ const editor = new HighlightingText({ allowFontScaling: false })
+
+ expect(editor.scaledFontSize(18)).toBe(18)
+ })
+})
diff --git a/__tests__/stores/App.test.js b/__tests__/stores/App.test.js
new file mode 100644
index 00000000..1e4fda19
--- /dev/null
+++ b/__tests__/stores/App.test.js
@@ -0,0 +1,203 @@
+import App from '../../src/stores/App'
+import Push from '../../src/stores/Push'
+import Login from '../../src/stores/Login'
+import { Linking } from 'react-native'
+import { CommonActions } from '@react-navigation/native'
+
+jest.mock('../../src/api/MicroBlogApi', () => ({
+ __esModule: true,
+ default: {}
+}))
+
+jest.mock('../../src/stores/Auth', () => ({
+ selected_user: null,
+ users: []
+}))
+
+jest.mock('../../src/stores/Login', () => ({
+ is_loading: false,
+ can_handle_open_url: jest.fn(() => false),
+ trigger_login_from_url: jest.fn()
+}))
+jest.mock('../../src/stores/Reply', () => ({
+ hydrate: jest.fn()
+}))
+jest.mock('../../src/stores/Discover', () => ({}))
+jest.mock('../../src/stores/Settings', () => ({}))
+jest.mock('../../src/stores/Services', () => ({}))
+
+jest.mock('../../src/stores/Push', () => ({
+ replay_pending_notification: jest.fn(),
+ set_auth_ready: jest.fn(),
+ check_and_remove_notifications_with_post_id: jest.fn()
+}))
+
+jest.mock('react-native-simple-toast', () => ({
+ show: jest.fn()
+}))
+
+jest.mock('expo-web-browser', () => ({
+ openBrowserAsync: jest.fn()
+}))
+
+jest.mock('@react-native-async-storage/async-storage', () => ({
+ getItem: jest.fn(),
+ setItem: jest.fn(),
+ removeItem: jest.fn()
+}))
+
+jest.mock('react-native-actions-sheet', () => ({
+ SheetManager: {
+ show: jest.fn(),
+ hide: jest.fn()
+ }
+}))
+
+jest.mock('@react-navigation/native', () => ({
+ CommonActions: {
+ reset: jest.fn(payload => ({
+ type: 'RESET',
+ payload
+ }))
+ },
+ StackActions: {
+ popToTop: jest.fn(() => ({
+ type: 'POP_TO_TOP'
+ }))
+ }
+}))
+
+describe('App navigation reset', () => {
+ let current_time = 1000
+
+ beforeEach(async () => {
+ current_time += 10000
+ jest.spyOn(Date, 'now').mockImplementation(() => current_time)
+ Linking.canOpenURL = jest.fn(() => Promise.resolve(true))
+ Linking.openURL = jest.fn(() => Promise.resolve())
+ await App.set_navigation(null)
+ await App.set_navigation_ready(false)
+ await App.set_current_tab_key('Timeline')
+ await App.set_is_loading(true)
+ Push.replay_pending_notification.mockReset()
+ Push.check_and_remove_notifications_with_post_id.mockReset()
+ CommonActions.reset.mockClear()
+ })
+
+ test('starts in a loading state before hydrate finishes', () => {
+ expect(App.is_loading).toBe(true)
+ })
+
+ afterEach(() => {
+ Date.now.mockRestore()
+ })
+
+ test('waits for navigation readiness before consuming a pending tab reset', async () => {
+ const resetRoot = jest.fn()
+ const isReady = jest.fn(() => false)
+ const navigation = {
+ isReady,
+ resetRoot
+ }
+ const reset_state = {
+ index: 0,
+ routes: [{ name: 'Tabs' }]
+ }
+
+ await App.reset_to_tabs()
+ expect(resetRoot).not.toHaveBeenCalled()
+
+ await App.set_navigation(navigation)
+ expect(resetRoot).not.toHaveBeenCalled()
+
+ isReady.mockReturnValue(true)
+ await App.set_navigation_ready(true)
+ expect(resetRoot).toHaveBeenCalledWith(reset_state)
+
+ await App.set_navigation_ready(true)
+ expect(resetRoot).toHaveBeenCalledTimes(1)
+ })
+
+ test('consumes a pending tab reset when the navigation ref is already ready', async () => {
+ const resetRoot = jest.fn()
+ const navigation = {
+ isReady: jest.fn(() => true),
+ resetRoot
+ }
+ const reset_state = {
+ index: 0,
+ routes: [{ name: 'Tabs' }]
+ }
+
+ await App.reset_to_tabs()
+ await App.set_navigation(navigation)
+
+ expect(resetRoot).toHaveBeenCalledWith(reset_state)
+
+ await App.set_navigation_ready(true)
+ expect(resetRoot).toHaveBeenCalledTimes(1)
+ })
+
+ test('lets direct webview post open URLs navigate to conversation', async () => {
+ const navigation = {
+ isReady: jest.fn(() => true),
+ navigate: jest.fn()
+ }
+
+ await App.set_navigation(navigation)
+ await App.set_navigation_ready(true)
+ await App.handle_url_from_webview('microblog://open/123')
+
+ expect(navigation.navigate).toHaveBeenCalledWith('Timeline-Conversation', { conversation_id: '123' })
+ })
+
+ test('suppresses a conversation URL immediately after a webview link', async () => {
+ const navigation = {
+ isReady: jest.fn(() => true),
+ navigate: jest.fn()
+ }
+
+ await App.set_navigation(navigation)
+ await App.set_navigation_ready(true)
+ await App.handle_url_from_webview('https://example.com/article')
+ await App.handle_url_from_webview('https://micro.blog/example/123')
+
+ expect(navigation.navigate).not.toHaveBeenCalled()
+
+ current_time += 1000
+ await App.handle_url_from_webview('microblog://open/456')
+
+ expect(navigation.navigate).toHaveBeenCalledWith('Timeline-Conversation', { conversation_id: '456' })
+ })
+})
+
+describe('App auth callback URLs', () => {
+ let url_event_handler
+
+ beforeEach(async () => {
+ Login.is_loading = false
+ Login.can_handle_open_url.mockReset()
+ Login.trigger_login_from_url.mockReset()
+ Login.can_handle_open_url.mockReturnValue(false)
+ Linking.getInitialURL = jest.fn(() => Promise.resolve(null))
+ if (!url_event_handler) {
+ Linking.addEventListener = jest.fn((type, handler) => {
+ if (type === 'url') {
+ url_event_handler = handler
+ }
+ return { remove: jest.fn() }
+ })
+ await App.set_up_url_listener()
+ }
+ })
+
+ test('signs in from a Micro.blog auth callback while the auth sheet is still open', async () => {
+ const callback_url = 'microblog://auth/callback?code=27D1AEC374F9F621CB2D&state=4f36064754e102267754952f2535ce21'
+ Login.is_loading = true
+ Login.can_handle_open_url.mockReturnValue(true)
+
+ url_event_handler({ url: callback_url })
+
+ expect(Login.trigger_login_from_url).toHaveBeenCalledWith(callback_url)
+ })
+})
diff --git a/__tests__/stores/Login.test.js b/__tests__/stores/Login.test.js
new file mode 100644
index 00000000..c88d013b
--- /dev/null
+++ b/__tests__/stores/Login.test.js
@@ -0,0 +1,516 @@
+import Login from '../../src/stores/Login'
+import MicroBlogApi, { APPLE_USERNAME_REQUIRED } from '../../src/api/MicroBlogApi'
+import {
+ build_micro_blog_auth_url,
+ create_oauth_state,
+ exchange_micro_blog_code,
+ get_micro_blog_redirect_uri,
+} from '../../src/api/MicroBlogAuth'
+import App from '../../src/stores/App'
+import Auth from '../../src/stores/Auth'
+import { Alert } from 'react-native'
+import * as WebBrowser from 'expo-web-browser'
+
+jest.mock('../../src/api/MicroBlogApi', () => ({
+ __esModule: true,
+ default: {
+ login_with_apple: jest.fn(),
+ login_with_email: jest.fn(),
+ login_with_token: jest.fn()
+ },
+ APPLE_USERNAME_REQUIRED: 12,
+ LOGIN_ERROR: 2,
+ LOGIN_INCORRECT: 1,
+ LOGIN_SUCCESS: 3,
+ LOGIN_TOKEN_INVALID: 4
+}))
+
+jest.mock('../../src/api/MicroBlogAuth', () => {
+ const actual = jest.requireActual('../../src/api/MicroBlogAuth')
+ return {
+ ...actual,
+ build_micro_blog_auth_url: jest.fn(actual.build_micro_blog_auth_url),
+ create_oauth_state: jest.fn(() => 'oauth-state'),
+ exchange_micro_blog_code: jest.fn(),
+ get_micro_blog_redirect_uri: jest.fn(actual.get_micro_blog_redirect_uri),
+ }
+})
+
+jest.mock('expo-web-browser', () => ({
+ openAuthSessionAsync: jest.fn(),
+ dismissAuthSession: jest.fn(),
+ maybeCompleteAuthSession: jest.fn(),
+}))
+
+jest.mock('../../src/stores/Auth', () => ({
+ handle_new_login: jest.fn(),
+ is_logged_in: jest.fn(() => false),
+ is_selecting_user: true,
+ selected_user: null,
+ users: []
+}))
+
+const mockCanGoBack = jest.fn()
+const mockGoBack = jest.fn()
+const mockNavigate = jest.fn()
+const mockReset = jest.fn()
+
+jest.mock('../../src/stores/App', () => ({
+ close_sheet: jest.fn(),
+ navigate_to_screen: jest.fn(),
+ reset_to_tabs: jest.fn(),
+ navigation: jest.fn(() => ({
+ canGoBack: mockCanGoBack,
+ goBack: mockGoBack,
+ navigate: mockNavigate,
+ reset: mockReset
+ })),
+ open_sheet: jest.fn(),
+ bump_web_view_epoch: jest.fn()
+}))
+
+describe('Login Apple sign in', () => {
+ beforeEach(() => {
+ Login.reset()
+ MicroBlogApi.login_with_apple.mockReset()
+ MicroBlogApi.login_with_email.mockReset()
+ MicroBlogApi.login_with_token.mockReset()
+ App.navigate_to_screen.mockReset()
+ App.reset_to_tabs.mockReset()
+ App.bump_web_view_epoch.mockReset()
+ App.close_sheet.mockReset()
+ App.open_sheet.mockReset()
+ mockCanGoBack.mockReset()
+ mockGoBack.mockReset()
+ mockNavigate.mockReset()
+ mockReset.mockReset()
+ Auth.handle_new_login.mockReset()
+ Auth.is_logged_in.mockReset()
+ Auth.is_logged_in.mockReturnValue(false)
+ Auth.is_selecting_user = true
+ Auth.selected_user = null
+ Auth.users = []
+ mockCanGoBack.mockReturnValue(true)
+ App.reset_to_tabs.mockResolvedValue(true)
+ App.bump_web_view_epoch.mockResolvedValue(true)
+ App.close_sheet.mockResolvedValue(true)
+ WebBrowser.openAuthSessionAsync.mockReset()
+ WebBrowser.dismissAuthSession.mockReset()
+ create_oauth_state.mockReset()
+ create_oauth_state.mockReturnValue('oauth-state')
+ exchange_micro_blog_code.mockReset()
+ build_micro_blog_auth_url.mockClear()
+ get_micro_blog_redirect_uri.mockClear()
+ jest.spyOn(Alert, 'alert').mockImplementation(() => {})
+ })
+
+ afterEach(() => {
+ Alert.alert.mockRestore()
+ })
+
+ test('keeps Apple credentials and opens username screen when a new account needs a username', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue(APPLE_USERNAME_REQUIRED)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token',
+ email: 'vincent@example.com',
+ full_name: 'Vincent Ritter'
+ })
+
+ expect(Login.apple_user_id).toBe('apple-user-id')
+ expect(Login.apple_identity_token).toBe('apple-identity-token')
+ expect(App.navigate_to_screen).toHaveBeenCalledWith('AppleUsername')
+ expect(Login.is_loading).toBe(false)
+ })
+
+ test('clears stale Apple username when starting a new Apple sign in', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue(APPLE_USERNAME_REQUIRED)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'old-apple-user-id',
+ identity_token: 'old-apple-identity-token'
+ })
+ Login.set_apple_username('oldusername')
+
+ await Login.login_with_apple_credentials({
+ user_id: 'new-apple-user-id',
+ identity_token: 'new-apple-identity-token'
+ })
+
+ expect(Login.apple_user_id).toBe('new-apple-user-id')
+ expect(Login.apple_identity_token).toBe('new-apple-identity-token')
+ expect(Login.apple_username).toBe('')
+ })
+
+ test('does not submit Apple username again while registration is loading', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValueOnce(APPLE_USERNAME_REQUIRED)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token'
+ })
+ Login.set_apple_username('vincent')
+
+ let resolve_register
+ const register_promise = new Promise(resolve => {
+ resolve_register = resolve
+ })
+ MicroBlogApi.login_with_apple.mockReturnValueOnce(register_promise)
+
+ const first_register = Login.register_apple_username()
+
+ expect(Login.is_loading).toBe(true)
+ expect(Login.can_submit_apple_username()).toBe(false)
+
+ const second_register = await Login.register_apple_username()
+
+ expect(second_register).toBe(false)
+ expect(MicroBlogApi.login_with_apple).toHaveBeenCalledTimes(2)
+
+ resolve_register({ error: 'That username is not available.' })
+ await first_register
+ })
+
+ test('can clear Apple sign in scratch state', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue(APPLE_USERNAME_REQUIRED)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token',
+ email: 'vincent@example.com',
+ full_name: 'Vincent Ritter'
+ })
+ Login.set_apple_username('vincent')
+
+ Login.reset_apple_credentials()
+
+ expect(Login.apple_user_id).toBeNull()
+ expect(Login.apple_identity_token).toBeNull()
+ expect(Login.apple_email).toBeNull()
+ expect(Login.apple_full_name).toBeNull()
+ expect(Login.apple_username).toBe('')
+ })
+
+ test('shows Apple account error messages returned by Micro.blog', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue({
+ error: 'That username is not available.'
+ })
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token'
+ })
+
+ expect(Alert.alert).toHaveBeenCalledWith(
+ 'Unable to sign in with Apple',
+ 'That username is not available.'
+ )
+ expect(Login.is_loading).toBe(false)
+ })
+
+ test('bumps web view epoch after successful first-time Apple sign in without stack navigation', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+ Auth.is_logged_in.mockReturnValue(false)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token'
+ })
+
+ expect(Auth.handle_new_login).toHaveBeenCalledWith({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ expect(App.bump_web_view_epoch).toHaveBeenCalledTimes(1)
+ expect(App.close_sheet).toHaveBeenCalledWith('main_sheet')
+ expect(mockGoBack).not.toHaveBeenCalled()
+ expect(App.reset_to_tabs).not.toHaveBeenCalled()
+ })
+
+ test('goes back after successful Apple sign in when adding another account', async () => {
+ MicroBlogApi.login_with_apple.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+ Auth.is_logged_in.mockReturnValue(true)
+
+ await Login.login_with_apple_credentials({
+ user_id: 'apple-user-id',
+ identity_token: 'apple-identity-token'
+ })
+
+ expect(App.bump_web_view_epoch).toHaveBeenCalledTimes(1)
+ expect(mockGoBack).toHaveBeenCalled()
+ expect(App.reset_to_tabs).not.toHaveBeenCalled()
+ })
+
+ test('completes first-time sign in from a microblog URL without stack navigation', async () => {
+ const signin_token = '12345678901234567890'
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+ Auth.is_logged_in.mockReturnValue(false)
+
+ await Login.trigger_login_from_url(`microblog://signin/${signin_token}`)
+
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith(signin_token)
+ expect(Auth.handle_new_login).toHaveBeenCalledWith({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ expect(App.close_sheet).toHaveBeenCalledWith('main_sheet')
+ expect(App.close_sheet).toHaveBeenCalledWith('login-message-sheet')
+ expect(App.reset_to_tabs).not.toHaveBeenCalled()
+ expect(mockReset).not.toHaveBeenCalled()
+ expect(mockGoBack).not.toHaveBeenCalled()
+ })
+
+ test('resets to tabs after microblog URL sign in when already signed in', async () => {
+ const signin_token = '12345678901234567890'
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+ Auth.is_logged_in.mockReturnValue(true)
+
+ await Login.trigger_login_from_url(`microblog://signin/${signin_token}`)
+
+ expect(App.reset_to_tabs).toHaveBeenCalledTimes(1)
+ expect(mockGoBack).not.toHaveBeenCalled()
+ })
+
+ test('does not copy a microblog URL sign in token into the visible input', async () => {
+ const signin_token = '12345678901234567890'
+ let resolve_login
+ MicroBlogApi.login_with_token.mockReturnValue(new Promise(resolve => {
+ resolve_login = resolve
+ }))
+ Auth.handle_new_login.mockResolvedValue(true)
+
+ const login_promise = Login.trigger_login_from_url(`microblog://signin/${signin_token}`)
+ await Promise.resolve()
+
+ expect(Login.is_loading).toBe(true)
+ expect(Login.input_value).toBe('')
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith(signin_token)
+
+ resolve_login({
+ username: 'vincent',
+ token: 'app-token'
+ })
+ await login_promise
+ })
+
+ test('signs in with Micro.blog through the IndieAuth browser session', async () => {
+ WebBrowser.openAuthSessionAsync.mockResolvedValue({
+ type: 'success',
+ url: 'microblog://auth/callback?code=AUTHCODE&state=oauth-state',
+ })
+ exchange_micro_blog_code.mockResolvedValue({
+ access_token: 'access-token',
+ })
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+
+ const did_sign_in = await Login.sign_in_with_micro_blog()
+
+ expect(did_sign_in).toBe(true)
+ expect(WebBrowser.openAuthSessionAsync).toHaveBeenCalled()
+ expect(exchange_micro_blog_code).toHaveBeenCalledWith({ code: 'AUTHCODE' })
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith('access-token')
+ expect(Auth.handle_new_login).toHaveBeenCalledWith({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ expect(Login.pending_oauth_state).toBeNull()
+ expect(Login.is_loading).toBe(false)
+ })
+
+ test('clears pending oauth state when the browser session is cancelled', async () => {
+ WebBrowser.openAuthSessionAsync.mockResolvedValue({
+ type: 'cancel',
+ })
+
+ const did_sign_in = await Login.sign_in_with_micro_blog()
+
+ expect(did_sign_in).toBe(false)
+ expect(Login.pending_oauth_state).toBeNull()
+ expect(Login.show_error).toBe(false)
+ expect(exchange_micro_blog_code).not.toHaveBeenCalled()
+ })
+
+ test('completes Micro.blog sign in from a callback URL while the auth sheet is still open', async () => {
+ let resolve_auth_session
+ WebBrowser.openAuthSessionAsync.mockReturnValue(new Promise(resolve => {
+ resolve_auth_session = resolve
+ }))
+ exchange_micro_blog_code.mockResolvedValue({
+ access_token: 'access-token',
+ })
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+
+ const sign_in_promise = Login.sign_in_with_micro_blog()
+ await Promise.resolve()
+
+ expect(Login.is_loading).toBe(true)
+ expect(Login.pending_oauth_state).toBe('oauth-state')
+
+ const did_handle = await Login.trigger_login_from_url(
+ 'microblog://auth/callback?code=AUTHCODE&state=oauth-state'
+ )
+
+ expect(did_handle).toBe(true)
+ expect(exchange_micro_blog_code).toHaveBeenCalledWith({ code: 'AUTHCODE' })
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith('access-token')
+ expect(WebBrowser.dismissAuthSession).toHaveBeenCalled()
+ expect(Auth.handle_new_login).toHaveBeenCalledWith({
+ username: 'vincent',
+ token: 'app-token',
+ })
+
+ resolve_auth_session({ type: 'dismiss' })
+ const did_sign_in = await sign_in_promise
+
+ expect(did_sign_in).toBe(true)
+ expect(Login.show_error).toBe(false)
+ expect(Login.pending_oauth_state).toBeNull()
+ expect(Login.is_loading).toBe(false)
+ })
+
+ test('ignores a mismatched auth callback while the auth sheet is still open', async () => {
+ let resolve_auth_session
+ WebBrowser.openAuthSessionAsync.mockReturnValue(new Promise(resolve => {
+ resolve_auth_session = resolve
+ }))
+
+ const sign_in_promise = Login.sign_in_with_micro_blog()
+ await Promise.resolve()
+
+ const did_handle = await Login.trigger_login_from_url(
+ 'microblog://auth/callback?code=STALE&state=other-state'
+ )
+
+ expect(did_handle).toBe(false)
+ expect(Login.pending_oauth_state).toBe('oauth-state')
+ expect(WebBrowser.dismissAuthSession).not.toHaveBeenCalled()
+ expect(exchange_micro_blog_code).not.toHaveBeenCalled()
+
+ resolve_auth_session({ type: 'cancel' })
+ const did_sign_in = await sign_in_promise
+
+ expect(did_sign_in).toBe(false)
+ expect(Login.pending_oauth_state).toBeNull()
+ })
+
+ test('exchanges an auth code once when Linking and the auth session both deliver the callback', async () => {
+ let resolve_auth_session
+ WebBrowser.openAuthSessionAsync.mockReturnValue(new Promise(resolve => {
+ resolve_auth_session = resolve
+ }))
+ exchange_micro_blog_code.mockResolvedValue({
+ access_token: 'access-token',
+ })
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+
+ const callback_url = 'microblog://auth/callback?code=AUTHCODE&state=oauth-state'
+ const sign_in_promise = Login.sign_in_with_micro_blog()
+ await Promise.resolve()
+
+ const did_handle = await Login.trigger_login_from_url(callback_url)
+ resolve_auth_session({
+ type: 'success',
+ url: callback_url,
+ })
+ const did_sign_in = await sign_in_promise
+
+ expect(did_handle).toBe(true)
+ expect(did_sign_in).toBe(true)
+ expect(exchange_micro_blog_code).toHaveBeenCalledTimes(1)
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledTimes(1)
+ expect(Login.show_error).toBe(false)
+ })
+
+ test('rejects oauth callbacks with a mismatched state', async () => {
+ create_oauth_state.mockReturnValue('expected-state')
+ WebBrowser.openAuthSessionAsync.mockResolvedValue({
+ type: 'success',
+ url: 'microblog://auth/callback?code=AUTHCODE&state=other-state',
+ })
+
+ const did_sign_in = await Login.sign_in_with_micro_blog()
+
+ expect(did_sign_in).toBe(false)
+ expect(Login.error_message).toBe('Micro.blog sign in could not be verified. Please try again.')
+ expect(Login.pending_oauth_state).toBeNull()
+ expect(exchange_micro_blog_code).not.toHaveBeenCalled()
+ })
+
+ test('signs in with a pasted app token', async () => {
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+
+ const did_sign_in = await Login.login_with_token(false, ' pasted-token ')
+
+ expect(did_sign_in).toBe(true)
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith('pasted-token')
+ expect(Auth.handle_new_login).toHaveBeenCalledWith({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ })
+
+ test('routes email input to the email sign-in flow', async () => {
+ MicroBlogApi.login_with_email.mockResolvedValue(3)
+ await Login.set_input_value('vincent@example.com')
+
+ expect(Login.submit_button_label()).toBe('Continue')
+
+ const did_complete = await Login.submit_email_or_token()
+
+ expect(did_complete).toBe(true)
+ expect(MicroBlogApi.login_with_email).toHaveBeenCalledWith('vincent@example.com')
+ expect(MicroBlogApi.login_with_token).not.toHaveBeenCalled()
+ expect(App.open_sheet).toHaveBeenCalledWith('login-message-sheet')
+ })
+
+ test('routes token input to the token sign-in flow', async () => {
+ const token = '12345678901234567890'
+ MicroBlogApi.login_with_token.mockResolvedValue({
+ username: 'vincent',
+ token: 'app-token',
+ })
+ Auth.handle_new_login.mockResolvedValue(true)
+ await Login.set_input_value(token)
+
+ expect(Login.submit_button_label()).toBe('Sign in with token')
+
+ const did_complete = await Login.submit_email_or_token()
+
+ expect(did_complete).toBe(true)
+ expect(MicroBlogApi.login_with_token).toHaveBeenCalledWith(token)
+ expect(MicroBlogApi.login_with_email).not.toHaveBeenCalled()
+ })
+})
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d4ca0e3d..ac854b71 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -3,11 +3,24 @@ apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: 'com.google.gms.google-services'
+def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
+
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
+ entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
+ reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+ hermesCommand = new File(["node", "--print", "require.resolve('hermes-compiler/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/hermesc/%OS-BIN%/hermesc"
+ codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+
+ enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
+ // Use Expo CLI to bundle the app, this ensures the Metro config
+ // works correctly with Expo projects.
+ cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
+ bundleCommand = "export:embed"
+
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
@@ -53,11 +66,6 @@ react {
/* Autolinking */
autolinkLibrariesWithApp()
- //
- // Added by install-expo-modules
- entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", rootDir.getAbsoluteFile().getParentFile().getAbsolutePath(), "android", "absolute"].execute(null, rootDir).text.trim())
- cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim())
- bundleCommand = "export:embed"
}
/**
@@ -78,6 +86,12 @@ def enableProguardInReleaseBuilds = false
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
+def releaseKeystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
+def releaseKeystorePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
+def releaseKeyAlias = System.getenv("ANDROID_KEY_ALIAS")
+def releaseKeyPassword = System.getenv("ANDROID_KEY_PASSWORD")
+def hasReleaseSigning = releaseKeystoreFile && releaseKeystorePassword && releaseKeyAlias && releaseKeyPassword
+
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
@@ -88,8 +102,10 @@ android {
applicationId "blog.micro.android"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 85
- versionName "2.6.5"
+ versionCode 97
+ versionName "3.0.1"
+
+ buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
signingConfigs {
@@ -99,20 +115,36 @@ android {
keyAlias 'androiddebugkey'
keyPassword 'android'
}
+ release {
+ if (hasReleaseSigning) {
+ storeFile file(releaseKeystoreFile)
+ storePassword releaseKeystorePassword
+ keyAlias releaseKeyAlias
+ keyPassword releaseKeyPassword
+ }
+ }
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
- // Caution! In production, you need to generate your own keystore file.
- // see https://reactnative.dev/docs/signed-apk-android.
- signingConfig signingConfigs.debug
+ signingConfig hasReleaseSigning ? signingConfigs.release : signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ packagingOptions {
+ jniLibs {
+ def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
+ useLegacyPackaging enableLegacyPackaging.toBoolean()
+ }
+ }
+
+ androidResources {
+ ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
+ }
}
dependencies {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 771ce6f8..9329148d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
-
+
@@ -19,7 +19,7 @@
-
+
@@ -33,7 +33,11 @@
-
+
+
+
+
+
@@ -47,4 +51,4 @@
-
\ No newline at end of file
+
diff --git a/android/app/src/main/java/blog/micro/android/MainActivity.kt b/android/app/src/main/java/blog/micro/android/MainActivity.kt
index ad13d1ea..4916fa8e 100644
--- a/android/app/src/main/java/blog/micro/android/MainActivity.kt
+++ b/android/app/src/main/java/blog/micro/android/MainActivity.kt
@@ -1,32 +1,65 @@
-package blog.micro.android;
-import expo.modules.ReactActivityDelegateWrapper
-import android.os.Bundle;
-import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory;
+package blog.micro.android
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
+import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory
+import expo.modules.ReactActivityDelegateWrapper
class MainActivity : ReactActivity() {
- /**
- * Returns the name of the main component registered from JavaScript. This is used to schedule
- * rendering of the component.
- */
- override fun getMainComponentName(): String = "Micro.blog"
-
- /**
- * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
- * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
- */
- override fun createReactActivityDelegate(): ReactActivityDelegate =
- ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled))
-
- //react-native-screens override
- override fun onCreate(savedInstanceState: Bundle?) {
- supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
- super.onCreate(savedInstanceState);
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ override fun getMainComponentName(): String = "Micro.blog"
+
+ /**
+ * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
+ * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
+ */
+ override fun createReactActivityDelegate(): ReactActivityDelegate {
+ return ReactActivityDelegateWrapper(
+ this,
+ BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
+ object : DefaultReactActivityDelegate(
+ this,
+ mainComponentName,
+ fabricEnabled
+ ) {}
+ )
+ }
+
+ // react-native-screens override
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(R.style.AppTheme)
+ supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
+ super.onCreate(null)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ }
+
+ /**
+ * Align the back button behavior with Android S
+ * where moving root activities to background instead of finishing activities.
+ */
+ override fun invokeDefaultOnBackPressed() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+ if (!moveTaskToBack(false)) {
+ super.invokeDefaultOnBackPressed()
+ }
+ return
}
+
+ super.invokeDefaultOnBackPressed()
+ }
}
diff --git a/android/app/src/main/java/blog/micro/android/MainApplication.kt b/android/app/src/main/java/blog/micro/android/MainApplication.kt
index ebac1d96..07949587 100644
--- a/android/app/src/main/java/blog/micro/android/MainApplication.kt
+++ b/android/app/src/main/java/blog/micro/android/MainApplication.kt
@@ -1,48 +1,40 @@
-package blog.micro.android;
-import android.content.res.Configuration
-import expo.modules.ApplicationLifecycleDispatcher
-import expo.modules.ReactNativeHostWrapper
+package blog.micro.android
import android.app.Application
+import android.content.res.Configuration
+
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
-import com.facebook.react.ReactNativeHost
-import com.facebook.react.ReactPackage
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
-import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
-import com.facebook.react.defaults.DefaultReactNativeHost
-import com.facebook.react.soloader.OpenSourceMergedSoMapping
-import com.facebook.soloader.SoLoader
-
-class MainApplication : Application(), ReactApplication {
+import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
+import com.facebook.react.common.ReleaseLevel
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
- override val reactNativeHost: ReactNativeHost =
- ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
- override fun getPackages(): List =
- PackageList(this).packages.apply {
- // Packages that cannot be autolinked yet can be added manually here, for example:
- // add(MyReactNativePackage())
- }
-
- override fun getJSMainModuleName(): String = "index"
-
- override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
+import blog.micro.android.theme.MBAndroidThemePackage
+import expo.modules.ApplicationLifecycleDispatcher
+import expo.modules.ExpoReactHostFactory
- override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
- override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
- })
+class MainApplication : Application(), ReactApplication {
- override val reactHost: ReactHost
- get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
+ override val reactHost: ReactHost by lazy {
+ ExpoReactHostFactory.getDefaultReactHost(
+ context = applicationContext,
+ packageList =
+ PackageList(this).packages.apply {
+ // Packages that cannot be autolinked yet can be added manually here, for example:
+ add(MBAndroidThemePackage())
+ }
+ )
+ }
override fun onCreate() {
super.onCreate()
- SoLoader.init(this, OpenSourceMergedSoMapping)
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
- // If you opted-in for the New Architecture, we load the native entry point for this app.
- load()
+ DefaultNewArchitectureEntryPoint.releaseLevel = try {
+ ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
+ } catch (e: IllegalArgumentException) {
+ ReleaseLevel.STABLE
}
+ loadReactNative(this)
ApplicationLifecycleDispatcher.onApplicationCreate(this)
}
diff --git a/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemeModule.kt b/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemeModule.kt
new file mode 100644
index 00000000..cae33e4a
--- /dev/null
+++ b/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemeModule.kt
@@ -0,0 +1,51 @@
+package blog.micro.android.theme
+
+import android.graphics.Color
+import android.os.Build
+
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactContextBaseJavaModule
+import com.facebook.react.bridge.ReactMethod
+
+import java.util.Locale
+
+class MBAndroidThemeModule(
+ reactContext: ReactApplicationContext
+) : ReactContextBaseJavaModule(reactContext) {
+
+ override fun getName(): String = "MBAndroidTheme"
+
+ @ReactMethod
+ fun getAccentColor(colorScheme: String?, promise: Promise) {
+ try {
+ promise.resolve(colorToHex(resolveAccentColor(colorScheme)))
+ } catch (error: Exception) {
+ promise.resolve(MICRO_BLOG_ACCENT_COLOR)
+ }
+ }
+
+ private fun resolveAccentColor(colorScheme: String?): Int {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val accentResource = if (colorScheme == "dark") {
+ android.R.color.system_accent1_400
+ } else {
+ android.R.color.system_accent1_600
+ }
+
+ return reactApplicationContext.resources.getColor(
+ accentResource,
+ reactApplicationContext.theme
+ )
+ }
+
+ return Color.parseColor(MICRO_BLOG_ACCENT_COLOR)
+ }
+
+ private fun colorToHex(color: Int): String =
+ String.format(Locale.US, "#%06X", 0xFFFFFF and color)
+
+ companion object {
+ private const val MICRO_BLOG_ACCENT_COLOR = "#ff8800"
+ }
+}
diff --git a/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemePackage.kt b/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemePackage.kt
new file mode 100644
index 00000000..eb436997
--- /dev/null
+++ b/android/app/src/main/java/blog/micro/android/theme/MBAndroidThemePackage.kt
@@ -0,0 +1,17 @@
+package blog.micro.android.theme
+
+import com.facebook.react.ReactPackage
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.uimanager.ViewManager
+
+class MBAndroidThemePackage : ReactPackage {
+ @Deprecated("ReactPackage.createNativeModules is deprecated in React Native new architecture.")
+ override fun createNativeModules(reactContext: ReactApplicationContext): List =
+ listOf(MBAndroidThemeModule(reactContext))
+
+ override fun createViewManagers(
+ reactContext: ReactApplicationContext
+ ): List> =
+ emptyList()
+}
diff --git a/android/app/src/main/res/drawable-nodpi/ic_launcher_monochrome.png b/android/app/src/main/res/drawable-nodpi/ic_launcher_monochrome.png
new file mode 100644
index 00000000..bb6e18fd
Binary files /dev/null and b/android/app/src/main/res/drawable-nodpi/ic_launcher_monochrome.png differ
diff --git a/android/app/src/main/res/drawable/splash_background.xml b/android/app/src/main/res/drawable/splash_background.xml
index b57bb7c4..65aa92a8 100644
--- a/android/app/src/main/res/drawable/splash_background.xml
+++ b/android/app/src/main/res/drawable/splash_background.xml
@@ -3,10 +3,10 @@
- -
-
-
+
-
\ No newline at end of file
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 90f95809..aa1423d0 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,5 @@
-
\ No newline at end of file
+
+
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
index 9a111147..8597a803 100644
--- a/android/app/src/main/res/values-night/styles.xml
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -1,5 +1,7 @@
+
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index 11ad8fcc..bc168c11 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -1,4 +1,5 @@
#f80
-
\ No newline at end of file
+ #fff
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 917efb92..ad7da5db 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,3 @@
Micro.blog
- #fff
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index b1c6dd72..027777a1 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -6,11 +6,18 @@
- #000000
- @drawable/rn_edit_text_material
-
+
+ - @android:color/transparent
+ - true
+ - @android:color/transparent
+ - true
+ - false
-
diff --git a/android/build.gradle b/android/build.gradle
index b8f1e352..a1ab7b50 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
- buildToolsVersion = "35.0.0"
+ buildToolsVersion = "36.0.0"
minSdkVersion = 24
- compileSdkVersion = 35
- targetSdkVersion = 35
+ compileSdkVersion = 36
+ targetSdkVersion = 36
ndkVersion = "27.1.12297006"
- kotlinVersion = "2.0.21"
+ kotlinVersion = "2.1.20"
androidXBrowser = "1.8.0"
}
repositories {
@@ -17,12 +17,20 @@ buildscript {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
- classpath 'com.google.gms:google-services:4.3.14'
+ classpath 'com.google.gms:google-services:4.4.4'
}
gradle.startParameter.excludedTaskNames.addAll(
gradle.startParameter.taskNames.findAll { it.contains("testClasses") }
)
}
-apply plugin: "com.facebook.react.rootproject"
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://www.jitpack.io' }
+ }
+}
+
apply plugin: "expo-root-project"
+apply plugin: "com.facebook.react.rootproject"
diff --git a/android/gradle.properties b/android/gradle.properties
index 12b04542..d5c3ef8c 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -39,3 +39,8 @@ newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
+
+# Use this property to enable edge-to-edge display support.
+# This allows your app to draw behind system bars for an immersive UI.
+# Note: Only works with ReactActivity and should not be used with custom Activity.
+edgeToEdgeEnabled=false
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 37f853b1..2a84e188 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/android/settings.gradle b/android/settings.gradle
index 5643e70c..ec80d26e 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,4 +1,12 @@
-pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin")
+pluginManagement {
+ def reactNativeGradlePlugin = new File(
+ providers.exec {
+ workingDir(rootDir)
+ commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
+ }.standardOutput.asText.get().trim()
+ ).getParentFile().absolutePath
+ includeBuild(reactNativeGradlePlugin)
+
def expoPluginsPath = new File(
providers.exec {
workingDir(rootDir)
@@ -8,27 +16,24 @@ pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin")
).absolutePath
includeBuild(expoPluginsPath)
}
-plugins { id("com.facebook.react.settings")
-id("expo-autolinking-settings")
+
+plugins {
+ id("com.facebook.react.settings")
+ id("expo-autolinking-settings")
}
-extensions.configure(com.facebook.react.ReactSettingsExtension){ ex ->
- def command = [
- 'node',
- '--no-warnings',
- '--eval',
- 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
- 'react-native-config',
- '--json',
- '--platform',
- 'android'
- ].toList()
- ex.autolinkLibrariesFromCommand(command)
+
+extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
+ if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
+ ex.autolinkLibrariesFromCommand()
+ } else {
+ ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
+ }
}
+expoAutolinking.useExpoModules()
+
rootProject.name = 'Micro.blog'
-include ':app'
-includeBuild('../node_modules/@react-native/gradle-plugin')
-apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
-useExpoModules()
expoAutolinking.useExpoVersionCatalog()
+
+include ':app'
includeBuild(expoAutolinking.reactNativeGradlePlugin)
diff --git a/babel.config.js b/babel.config.js
index dea06b48..6a5d9c57 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,12 +1,11 @@
module.exports = {
presets: ['babel-preset-expo'],
plugins: [
- ["@babel/plugin-proposal-decorators", { "legacy": true }],
- 'react-native-reanimated/plugin'
+ ["@babel/plugin-proposal-decorators", { "legacy": true }]
],
env: {
production: {
plugins: ['transform-remove-console']
}
}
-};
+}
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 00000000..2ba63365
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,2472 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "microblog_rn",
+ "dependencies": {
+ "@invertase/react-native-apple-authentication": "^2.4.1",
+ "@likashefqet/react-native-image-zoom": "likashefqet/react-native-image-zoom",
+ "@react-native-async-storage/async-storage": "2.2.0",
+ "@react-native-clipboard/clipboard": "^1.16.3",
+ "@react-native-community/push-notification-ios": "^1.12.0",
+ "@react-native-cookies/cookies": "^6.2.1",
+ "@react-native-documents/picker": "^10.1.7",
+ "@react-native-menu/menu": "^2.0.0",
+ "@react-navigation/bottom-tabs": "7.8.8",
+ "@react-navigation/native": "^7.1.22",
+ "@react-navigation/native-stack": "^7.8.2",
+ "@xmldom/xmldom": "^0.9.8",
+ "axios": "^0.22.0",
+ "expo": "~55.0.17",
+ "expo-image": "~55.0.9",
+ "expo-secure-store": "~55.0.13",
+ "expo-web-browser": "~55.0.14",
+ "fast-xml-parser": "^5.3.2",
+ "markdown-it": "^14.1.0",
+ "mobx": "^6.15.0",
+ "mobx-react": "^7.6.0",
+ "mobx-state-tree": "^7.0.2",
+ "react": "19.2.0",
+ "react-native": "0.83.6",
+ "react-native-actions-sheet": "^0.9.8",
+ "react-native-context-menu-view": "^1.21.0",
+ "react-native-device-info": "^8.7.1",
+ "react-native-fs": "^2.20.0",
+ "react-native-gesture-handler": "~2.30.0",
+ "react-native-hyperlink": "^0.0.22",
+ "react-native-image-picker": "7.2.3",
+ "react-native-keyboard-controller": "1.20.7",
+ "react-native-push-notification": "^8.1.1",
+ "react-native-reanimated": "4.2.1",
+ "react-native-safe-area-context": "~5.6.2",
+ "react-native-screens": "~4.23.0",
+ "react-native-sfsymbols": "^1.2.2",
+ "react-native-share-menu": "microdotblog/react-native-share-menu",
+ "react-native-simple-toast": "^3.3.2",
+ "react-native-svg": "15.15.3",
+ "react-native-url-polyfill": "^2.0.0",
+ "react-native-video": "^6.18.0",
+ "react-native-webview": "13.16.0",
+ "react-native-worklets": "0.7.4",
+ },
+ "devDependencies": {
+ "@babel/core": "^7.28.5",
+ "@babel/plugin-proposal-decorators": "^7.28.0",
+ "@babel/plugin-transform-react-jsx": "^7.27.1",
+ "@babel/preset-env": "^7.28.5",
+ "@babel/runtime": "^7.28.4",
+ "@react-native-community/cli": "20.0.0",
+ "@react-native-community/cli-platform-android": "20.0.0",
+ "@react-native-community/cli-platform-ios": "20.0.0",
+ "@react-native-community/eslint-config": "^3.2.0",
+ "@react-native/babel-preset": "0.83.6",
+ "@react-native/eslint-config": "0.83.6",
+ "@react-native/metro-config": "0.83.6",
+ "@react-native/typescript-config": "0.83.6",
+ "@tsconfig/react-native": "^2.0.3",
+ "@types/jest": "^29.5.14",
+ "@types/react": "^19.2.0",
+ "@types/react-test-renderer": "^19.1.0",
+ "babel-jest": "^29.7.0",
+ "babel-plugin-transform-remove-console": "^6.9.4",
+ "babel-preset-expo": "~55.0.18",
+ "eslint": "^8.57.1",
+ "jest": "^29.7.0",
+ "prettier": "2.8.8",
+ "react-test-renderer": "19.2.0",
+ "typescript": "~5.8.3",
+ },
+ },
+ },
+ "patchedDependencies": {
+ "react-native-screens@4.23.0": "patches/react-native-screens+4.23.0.patch",
+ "react-native-fs@2.20.0": "patches/react-native-fs+2.20.0.patch",
+ "@react-native-cookies/cookies@6.2.1": "patches/@react-native-cookies+cookies+6.2.1.patch",
+ "@react-navigation/bottom-tabs@7.8.8": "patches/@react-navigation+bottom-tabs+7.8.8.patch",
+ "react-native-push-notification@8.1.1": "patches/react-native-push-notification+8.1.1.patch",
+ },
+ "packages": {
+ "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
+
+ "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
+
+ "@babel/eslint-parser": ["@babel/eslint-parser@7.29.7", "", { "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.11.0", "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw=="],
+
+ "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
+
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
+
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
+
+ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="],
+
+ "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
+
+ "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
+
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
+
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
+
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+
+ "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="],
+
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
+
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
+
+ "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="],
+
+ "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
+
+ "@babel/highlight": ["@babel/highlight@7.25.9", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw=="],
+
+ "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
+
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="],
+
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="],
+
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="],
+
+ "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="],
+
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="],
+
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="],
+
+ "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-decorators": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg=="],
+
+ "@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ=="],
+
+ "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="],
+
+ "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
+
+ "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
+
+ "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
+
+ "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
+
+ "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg=="],
+
+ "@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="],
+
+ "@babel/plugin-syntax-export-default-from": ["@babel/plugin-syntax-export-default-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw=="],
+
+ "@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ=="],
+
+ "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="],
+
+ "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="],
+
+ "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
+
+ "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
+
+ "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
+
+ "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
+
+ "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
+
+ "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
+
+ "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
+
+ "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
+
+ "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
+
+ "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
+
+ "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
+
+ "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
+
+ "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="],
+
+ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="],
+
+ "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="],
+
+ "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="],
+
+ "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="],
+
+ "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="],
+
+ "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="],
+
+ "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="],
+
+ "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="],
+
+ "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="],
+
+ "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="],
+
+ "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="],
+
+ "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="],
+
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="],
+
+ "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="],
+
+ "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="],
+
+ "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="],
+
+ "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="],
+
+ "@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-flow": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ=="],
+
+ "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="],
+
+ "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="],
+
+ "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="],
+
+ "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="],
+
+ "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="],
+
+ "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="],
+
+ "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="],
+
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
+
+ "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ=="],
+
+ "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="],
+
+ "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="],
+
+ "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="],
+
+ "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="],
+
+ "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="],
+
+ "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="],
+
+ "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="],
+
+ "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="],
+
+ "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="],
+
+ "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="],
+
+ "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="],
+
+ "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="],
+
+ "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="],
+
+ "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q=="],
+
+ "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A=="],
+
+ "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.29.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g=="],
+
+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
+
+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
+
+ "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA=="],
+
+ "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw=="],
+
+ "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="],
+
+ "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="],
+
+ "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q=="],
+
+ "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="],
+
+ "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ=="],
+
+ "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="],
+
+ "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="],
+
+ "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="],
+
+ "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
+
+ "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="],
+
+ "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="],
+
+ "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="],
+
+ "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="],
+
+ "@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="],
+
+ "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="],
+
+ "@babel/preset-react": ["@babel/preset-react@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-transform-react-display-name": "^7.29.7", "@babel/plugin-transform-react-jsx": "^7.29.7", "@babel/plugin-transform-react-jsx-development": "^7.29.7", "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA=="],
+
+ "@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
+
+ "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
+
+ "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
+
+ "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
+
+ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
+
+ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
+
+ "@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
+
+ "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
+
+ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
+
+ "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
+
+ "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
+
+ "@expo/cli": ["@expo/cli@55.0.33", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~55.0.18", "@expo/config-plugins": "~55.0.10", "@expo/devcert": "^1.2.1", "@expo/env": "~2.1.2", "@expo/image-utils": "^0.8.14", "@expo/json-file": "^10.0.15", "@expo/log-box": "55.0.12", "@expo/metro": "~55.1.1", "@expo/metro-config": "~55.0.24", "@expo/osascript": "^2.4.4", "@expo/package-manager": "^1.10.6", "@expo/plist": "^0.5.4", "@expo/prebuild-config": "^55.0.19", "@expo/require-utils": "^55.0.5", "@expo/router-server": "^55.0.18", "@expo/schema-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.4.0", "@react-native/dev-middleware": "0.83.6", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.6", "expo-server": "^55.0.11", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.3", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "build/bin/cli" } }, "sha512-mCpv91YrgiQUCXEHbNOQNPeStO4NKvYaXgKuzzLjklrCUPf3f+u0oYcS5rl3KoFvTEifhOiuA0VkLDR8U0GGPg=="],
+
+ "@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
+
+ "@expo/config": ["@expo/config@55.0.18", "", { "dependencies": { "@expo/config-plugins": "~55.0.10", "@expo/config-types": "^55.0.5", "@expo/json-file": "^10.0.15", "@expo/require-utils": "^55.0.5", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-yezuyUk1VvVuwVyvKQrMHGosoC0WTh7HFAycpISMyeinSUHDjEBGvHUSQxtQ/71MR4g9lPJkEhFKeRjGx7I/Yg=="],
+
+ "@expo/config-plugins": ["@expo/config-plugins@55.0.10", "", { "dependencies": { "@expo/config-types": "^55.0.5", "@expo/json-file": "~10.0.15", "@expo/plist": "^0.5.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-1txnRnMLIO5lM/Of/VyvDkCwZap0YFvCyfSTIlUQamhwhx6Rh7r8TXfcIstaDYUQ7X6GTMkNxLXWbcYS6ZAFDw=="],
+
+ "@expo/config-types": ["@expo/config-types@55.0.5", "", {}, "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg=="],
+
+ "@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
+
+ "@expo/devtools": ["@expo/devtools@55.0.3", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" }, "optionalPeers": ["react", "react-native"] }, "sha512-KoIDgo0NoXeWLsIcOdZqtAG/1LlsM+JL0DA3bo0vCYaOYTBLXi/ZvRBqa20Ub8D2vKLNa+FgRQW0gRg04Ps1Pg=="],
+
+ "@expo/dom-webview": ["@expo/dom-webview@55.0.6", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZNm8tiNEZysxrr36J0x4mOCGyJDcaIvL/3tMxBz0VJIJDcV19xjuJAhJQxHovu+jKx6s9tRyEAINa1mdrzV39g=="],
+
+ "@expo/env": ["@expo/env@2.1.2", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-RJtGFfj/ygO/6zcVbV3cckHf4THcEkv5IZft1GjCB3dfT6axvzvIwXE9EiQqQYmGHcQ+ZrvC8xZcIhiHba0pYg=="],
+
+ "@expo/fingerprint": ["@expo/fingerprint@0.16.7", "", { "dependencies": { "@expo/env": "^2.1.2", "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-BH8sicYOqZ1iBMwCVEGIz6uTTfylosjc49FoMmCYIzKOiYdiVehsfoYBwyfxwWIiya1VMhm1gv0cgOP8fxHpDw=="],
+
+ "@expo/image-utils": ["@expo/image-utils@0.8.14", "", { "dependencies": { "@expo/require-utils": "^55.0.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-5Sn+jG4Cw+shC2wDMXoqSAJnvERbiwzHn05FpWtD5IBflfTIs5gUmjzwiGVyjOdlMSQhgRrw/AymPbmO9h9mpQ=="],
+
+ "@expo/json-file": ["@expo/json-file@10.2.0", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ=="],
+
+ "@expo/local-build-cache-provider": ["@expo/local-build-cache-provider@55.0.14", "", { "dependencies": { "@expo/config": "~55.0.18", "chalk": "^4.1.2" } }, "sha512-Itv/Wm8wBuq2QoJeda7N0Ys2ciq3ZQW+JLL8XijWMYOO/llieKeZue95FsuaREp3Ijdo0pSw0/cu+EkrwmU0vA=="],
+
+ "@expo/log-box": ["@expo/log-box@55.0.12", "", { "dependencies": { "@expo/dom-webview": "^55.0.6", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-f9ARS8J60cq3LLNdIqmUjYwyerBzVS5Ecp7KjIf3GOIPjW0571rkcwLz4/U18l/1DeSkSzIkYsNl2TC9oTdWaQ=="],
+
+ "@expo/metro": ["@expo/metro@55.1.1", "", { "dependencies": { "metro": "0.83.7", "metro-babel-transformer": "0.83.7", "metro-cache": "0.83.7", "metro-cache-key": "0.83.7", "metro-config": "0.83.7", "metro-core": "0.83.7", "metro-file-map": "0.83.7", "metro-minify-terser": "0.83.7", "metro-resolver": "0.83.7", "metro-runtime": "0.83.7", "metro-source-map": "0.83.7", "metro-symbolicate": "0.83.7", "metro-transform-plugins": "0.83.7", "metro-transform-worker": "0.83.7" } }, "sha512-/wfXo5hTuAVpVLG/4hzlmD9NBGJkzkmBEMm/4VICajYRbj7y8OmqqPWbbymzHiBiHB6tI9BnsyXpQM6zVZEECg=="],
+
+ "@expo/metro-config": ["@expo/metro-config@55.0.24", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~55.0.18", "@expo/env": "~2.1.2", "@expo/json-file": "~10.0.15", "@expo/metro": "~55.1.1", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.14", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-GQ8vernAY3BClpntFj+DDm4rmTpuFZYKxZ/82Q0XTZAOHnum68WIgRbgG2aRpAyVVs1wRBToKlLiSFCkpSS41w=="],
+
+ "@expo/osascript": ["@expo/osascript@2.7.0", "", { "dependencies": { "@expo/spawn-async": "^1.8.0" } }, "sha512-wKIXL8UtbuX4KwavPasIW3CUcgTbYfjzLcgUhjyKUAYDEqMaf6gmU1bqz3ffBPTokmX+G8/vFG1ZuI9etQWukA=="],
+
+ "@expo/package-manager": ["@expo/package-manager@1.13.0", "", { "dependencies": { "@expo/json-file": "^11.0.0", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-s3W3eZafJDEyVL7W/jxj2Nz3eONKxSCU604S5xj8ijrVaRz83x0DnZznLf/UXQEI1w+FyibH68nHeQyk767b1A=="],
+
+ "@expo/plist": ["@expo/plist@0.5.4", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-Jqppj0FULNq6Zp5JtQrFICl8TtpMjwwUbxEcEC2T3z7m+TOrTQEHZXz3D3Ay7vhbmvD+VMgfWJ4ARclJXeN8Eg=="],
+
+ "@expo/prebuild-config": ["@expo/prebuild-config@55.0.19", "", { "dependencies": { "@expo/config": "~55.0.18", "@expo/config-plugins": "~55.0.10", "@expo/config-types": "^55.0.5", "@expo/image-utils": "^0.8.14", "@expo/json-file": "^10.0.15", "@react-native/normalize-colors": "0.83.6", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-rLGjUXW0EUoxu/PJUOC0Lz5PEs27DmBA9Es3t2xV2ftI3kGZJK6oNt1vVq5D/Rn6MhZ5SFxqd3kziyP8ELoN6g=="],
+
+ "@expo/require-utils": ["@expo/require-utils@55.0.5", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-U4K/CQ2VpXuwfNGsN+daKmYOt15hCP8v/pXaYH6eut7kdYZo6SfJ1yr67BIcJ+1Gzzs+QzTxswAZChKpXmceyw=="],
+
+ "@expo/router-server": ["@expo/router-server@55.0.18", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^55.0.11", "expo": "*", "expo-constants": "^55.0.16", "expo-font": "^55.0.8", "expo-router": "*", "expo-server": "^55.0.11", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-W0VsvIiR48OvdlAOUlag4qspGYT/DV4srfYowlbYxwZh5Qw0MjiZAID4Zt7F0qynGZZxx8OZPpFhIX7XsqtRmg=="],
+
+ "@expo/schema-utils": ["@expo/schema-utils@55.0.4", "", {}, "sha512-65IdeeE8dAZR3n3J5Eq7LYiQ8BFGeEYCWPBCzycvafL7PkskbCyIclTQarRwf/HXFoRvezKCjaLwy/8v9Prk6g=="],
+
+ "@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
+
+ "@expo/spawn-async": ["@expo/spawn-async@1.8.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw=="],
+
+ "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
+
+ "@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
+
+ "@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="],
+
+ "@expo/xcpretty": ["@expo/xcpretty@4.4.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw=="],
+
+ "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="],
+
+ "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="],
+
+ "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
+
+ "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
+
+ "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
+
+ "@invertase/react-native-apple-authentication": ["@invertase/react-native-apple-authentication@2.5.1", "", {}, "sha512-KPKKc0wtRYpH4J0RkDfHzoKSZ+dYPEHmCXhpPdgHsT2MbK5AIkW1cYurjhAgev/kL7nOHFmWITjY1At5WhFEMQ=="],
+
+ "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
+
+ "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
+
+ "@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="],
+
+ "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="],
+
+ "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="],
+
+ "@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="],
+
+ "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
+
+ "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="],
+
+ "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
+
+ "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
+
+ "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="],
+
+ "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="],
+
+ "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
+
+ "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="],
+
+ "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="],
+
+ "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="],
+
+ "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
+
+ "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@likashefqet/react-native-image-zoom": ["@likashefqet/react-native-image-zoom@github:likashefqet/react-native-image-zoom#217981b", { "peerDependencies": { "react": ">=16.x.x", "react-native": ">=0.62.x", "react-native-gesture-handler": ">=2.x.x", "react-native-reanimated": ">=2.x.x" } }, "likashefqet-react-native-image-zoom-217981b", "sha512-tBdL2nnsCGPfyNu5RG8YVW1IMg+P9Hs5ffNz5lCX3X9nS62xYLnIZVxy2pIT4PU4bMkCLxzpoFEUCMZaQWd1bQ=="],
+
+ "@nicolo-ribaudo/eslint-scope-5-internals": ["@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1", "", { "dependencies": { "eslint-scope": "5.1.1" } }, "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg=="],
+
+ "@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+
+ "@react-native-async-storage/async-storage": ["@react-native-async-storage/async-storage@2.2.0", "", { "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw=="],
+
+ "@react-native-clipboard/clipboard": ["@react-native-clipboard/clipboard@1.16.3", "", { "peerDependencies": { "react": ">= 16.9.0", "react-native": ">= 0.61.5", "react-native-macos": ">= 0.61.0", "react-native-windows": ">= 0.61.0" }, "optionalPeers": ["react-native-macos", "react-native-windows"] }, "sha512-cMIcvoZKIrShzJHEaHbTAp458R9WOv0fB6UyC7Ek4Qk561Ow/DrzmmJmH/rAZg21Z6ixJ4YSdFDC14crqIBmCQ=="],
+
+ "@react-native-community/cli": ["@react-native-community/cli@20.0.0", "", { "dependencies": { "@react-native-community/cli-clean": "20.0.0", "@react-native-community/cli-config": "20.0.0", "@react-native-community/cli-doctor": "20.0.0", "@react-native-community/cli-server-api": "20.0.0", "@react-native-community/cli-tools": "20.0.0", "@react-native-community/cli-types": "20.0.0", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", "execa": "^5.0.0", "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.2", "semver": "^7.5.2" }, "bin": { "rnc-cli": "build/bin.js" } }, "sha512-/cMnGl5V1rqnbElY1Fvga1vfw0d3bnqiJLx2+2oh7l9ulnXfVRWb5tU2kgBqiMxuDOKA+DQoifC9q/tvkj5K2w=="],
+
+ "@react-native-community/cli-clean": ["@react-native-community/cli-clean@20.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" } }, "sha512-YmdNRcT+Dp8lC7CfxSDIfPMbVPEXVFzBH62VZNbYGxjyakqAvoQUFTYPgM2AyFusAr4wDFbDOsEv88gCDwR3ig=="],
+
+ "@react-native-community/cli-config": ["@react-native-community/cli-config@20.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "cosmiconfig": "^9.0.0", "deepmerge": "^4.3.0", "fast-glob": "^3.3.2", "joi": "^17.2.1" } }, "sha512-5Ky9ceYuDqG62VIIpbOmkg8Lybj2fUjf/5wK4UO107uRqejBgNgKsbGnIZgEhREcaSEOkujWrroJ9gweueLfBg=="],
+
+ "@react-native-community/cli-config-android": ["@react-native-community/cli-config-android@20.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "fast-glob": "^3.3.2", "fast-xml-parser": "^4.4.1" } }, "sha512-asv60qYCnL1v0QFWcG9r1zckeFlKG+14GGNyPXY72Eea7RX5Cxdx8Pb6fIPKroWH1HEWjYH9KKHksMSnf9FMKw=="],
+
+ "@react-native-community/cli-config-apple": ["@react-native-community/cli-config-apple@20.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" } }, "sha512-PS1gNOdpeQ6w7dVu1zi++E+ix2D0ZkGC2SQP6Y/Qp002wG4se56esLXItYiiLrJkhH21P28fXdmYvTEkjSm9/Q=="],
+
+ "@react-native-community/cli-doctor": ["@react-native-community/cli-doctor@20.0.0", "", { "dependencies": { "@react-native-community/cli-config": "20.0.0", "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-apple": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", "envinfo": "^7.13.0", "execa": "^5.0.0", "node-stream-zip": "^1.9.1", "ora": "^5.4.1", "semver": "^7.5.2", "wcwidth": "^1.0.1", "yaml": "^2.2.1" } }, "sha512-cPHspi59+Fy41FDVxt62ZWoicCZ1o34k8LAl64NVSY0lwPl+CEi78jipXJhtfkVqSTetloA8zexa/vSAcJy57Q=="],
+
+ "@react-native-community/cli-platform-android": ["@react-native-community/cli-platform-android@20.0.0", "", { "dependencies": { "@react-native-community/cli-config-android": "20.0.0", "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "logkitty": "^0.7.1" } }, "sha512-th3ji1GRcV6ACelgC0wJtt9daDZ+63/52KTwL39xXGoqczFjml4qERK90/ppcXU0Ilgq55ANF8Pr+UotQ2AB/A=="],
+
+ "@react-native-community/cli-platform-apple": ["@react-native-community/cli-platform-apple@20.0.0", "", { "dependencies": { "@react-native-community/cli-config-apple": "20.0.0", "@react-native-community/cli-tools": "20.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.4.1" } }, "sha512-rZZCnAjUHN1XBgiWTAMwEKpbVTO4IHBSecdd1VxJFeTZ7WjmstqA6L/HXcnueBgxrzTCRqvkRIyEQXxC1OfhGw=="],
+
+ "@react-native-community/cli-platform-ios": ["@react-native-community/cli-platform-ios@20.0.0", "", { "dependencies": { "@react-native-community/cli-platform-apple": "20.0.0" } }, "sha512-Z35M+4gUJgtS4WqgpKU9/XYur70nmj3Q65c9USyTq6v/7YJ4VmBkmhC9BticPs6wuQ9Jcv0NyVCY0Wmh6kMMYw=="],
+
+ "@react-native-community/cli-server-api": ["@react-native-community/cli-server-api@20.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "20.0.0", "body-parser": "^1.20.3", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", "nocache": "^3.0.1", "open": "^6.2.0", "pretty-format": "^29.7.0", "serve-static": "^1.13.1", "ws": "^6.2.3" } }, "sha512-Ves21bXtjUK3tQbtqw/NdzpMW1vR2HvYCkUQ/MXKrJcPjgJnXQpSnTqHXz6ZdBlMbbwLJXOhSPiYzxb5/v4CDg=="],
+
+ "@react-native-community/cli-tools": ["@react-native-community/cli-tools@20.0.0", "", { "dependencies": { "@vscode/sudo-prompt": "^9.0.0", "appdirsjs": "^1.2.4", "chalk": "^4.1.2", "execa": "^5.0.0", "find-up": "^5.0.0", "launch-editor": "^2.9.1", "mime": "^2.4.1", "ora": "^5.4.1", "prompts": "^2.4.2", "semver": "^7.5.2" } }, "sha512-akSZGxr1IajJ8n0YCwQoA3DI0HttJ0WB7M3nVpb0lOM+rJpsBN7WG5Ft+8ozb6HyIPX+O+lLeYazxn5VNG/Xhw=="],
+
+ "@react-native-community/cli-types": ["@react-native-community/cli-types@20.0.0", "", { "dependencies": { "joi": "^17.2.1" } }, "sha512-7J4hzGWOPTBV1d30Pf2NidV+bfCWpjfCOiGO3HUhz1fH4MvBM0FbbBmE9LE5NnMz7M8XSRSi68ZGYQXgLBB2Qw=="],
+
+ "@react-native-community/eslint-config": ["@react-native-community/eslint-config@3.2.0", "", { "dependencies": { "@babel/core": "^7.14.0", "@babel/eslint-parser": "^7.18.2", "@react-native-community/eslint-plugin": "^1.1.0", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.5", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^26.5.3", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-ZjGvoeiBtCbd506hQqwjKmkWPgynGUoJspG8/MuV/EfKnkjCtBmeJvq2n+sWbWEvL9LWXDp2GJmPzmvU5RSvKQ=="],
+
+ "@react-native-community/eslint-plugin": ["@react-native-community/eslint-plugin@1.3.0", "", {}, "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg=="],
+
+ "@react-native-community/push-notification-ios": ["@react-native-community/push-notification-ios@1.12.0", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": ">=16.6.3", "react-native": ">=0.58.4" } }, "sha512-nu6ctimVlK4E3id3QHs9yZJ4vCP5UG3lRaBvSboJZ2IWU0XROcIJnNVIXsSH7C3hTlb84lNwPWir3y/TxRj0oA=="],
+
+ "@react-native-cookies/cookies": ["@react-native-cookies/cookies@6.2.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react-native": ">= 0.60.2" } }, "sha512-D17wCA0DXJkGJIxkL74Qs9sZ3sA+c+kCoGmXVknW7bVw/W+Vv1m/7mWTNi9DLBZSRddhzYw8SU0aJapIaM/g5w=="],
+
+ "@react-native-documents/picker": ["@react-native-documents/picker@10.1.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Tb8SPU+pHxrSJmDHBozSUStIPeyFHTHLrU3MW0N3sUAioLd5z+nmUdypfg5fs+Yzp7KTxVW06APe2HLB1ysLww=="],
+
+ "@react-native-menu/menu": ["@react-native-menu/menu@2.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-hb8Mirw6aKPGONhgo52IiNpwHtISVrgCT3rMdFX1qS7eOFNzOcQh8d2UDnaH5zVpxN+QuvWtaaiRMGFpIjzdtA=="],
+
+ "@react-native/assets-registry": ["@react-native/assets-registry@0.83.6", "", {}, "sha512-iljb4ue1yWJ3EhySz7EjV6CzSVrI2uNtR8BI2jzP5+QS5E4Cl3fdIJRmVwDEx1pu8uE97PGEusGRHnoaZ9Q3jg=="],
+
+ "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.6", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.6" } }, "sha512-qfRXsHGeucT5c6mK+8Q7v4Ly3zmygfVmFlEtkiq7q07W1OTreld6nib4rJ/DBEeNiKBoBTuHjWliYGNuDjLFQA=="],
+
+ "@react-native/babel-preset": ["@react-native/babel-preset@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.6", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-4/fXFDUvGOObETZq4+SUFkafld6OGgQWut5cQiqVghlhCB5z/p2lVhPgEUr/aTxTzeS3AmN+ztC+GpYPQ7tsTw=="],
+
+ "@react-native/codegen": ["@react-native/codegen@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-doB/Pq6Cf6IjF3wlQXTIiZOnsX9X8mEEk+CdGfyuCwZjWrf7IB8KaZEXXckJmfUcIwvJ9u/a72ZoTTCIoxAc9A=="],
+
+ "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.83.6", "", { "dependencies": { "@react-native/dev-middleware": "0.83.6", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.6", "metro-config": "^0.83.6", "metro-core": "^0.83.6", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-Mko6mywoHYJmpBnjwAC95vQWaUUh//71knFadH0BrhHDq2m7i/IrpLwcQsPAy8855ucXflBs5zQyGTpNbPBAaw=="],
+
+ "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.6", "", {}, "sha512-TyWXEpAjVundrc87fPWg91piOUg75+X9iutcfDe7cO3NrAEYCsl7Z09rKHuiAGkxfG9/rFD13dPsYIixUFkSFA=="],
+
+ "@react-native/debugger-shell": ["@react-native/debugger-shell@0.83.6", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-684TJMBCU0l0ZjJWzrnK0HH+ERaM9KLyxyArE1k7BrP+gVl4X9GO0Pi94RoInOxvW/nyV65sOU6Ip1F3ygS0cg=="],
+
+ "@react-native/dev-middleware": ["@react-native/dev-middleware@0.83.6", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.83.6", "@react-native/debugger-shell": "0.83.6", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-22xoddLTelpcVnF385SNH2hdP7X2av5pu7yRl/WnM5jBznbcl0+M9Ce94cj+WVeomsoUF/vlfuB0Ooy+RMlRiA=="],
+
+ "@react-native/eslint-config": ["@react-native/eslint-config@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.83.6", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-LeObz64FgdeheJqSRJVlpRLX88PFOLHxwnXxqzHkFIWresqd/ipk0I7pwCew6VFDTmeGB17I9zYigvjDSYPm9A=="],
+
+ "@react-native/eslint-plugin": ["@react-native/eslint-plugin@0.83.6", "", {}, "sha512-uYKCnlSGL3VJArldWDRk61H4S7JAYCbQy14vekoYDnf0oItmIyFDSlZr9p1Yr+eRKJLSSOktozsB1NHa7CBTvA=="],
+
+ "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.6", "", {}, "sha512-5prXv7WWR1RgZ/kWGZP+mi7/y/IE2ymfOHIZO5Pv14tMOmRAcQSgSYogcRmOiWw5mJs2K0UFeMiQD49ZO9oCug=="],
+
+ "@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.6", "", {}, "sha512-VSev0LV2i5X0ibduHBSLqKj0YU2F+waCgjl2uvaGHMGCSV1ZRKNFX/vJFqvLwjvdzLbkAZoFT1Rg7k7jDv44UA=="],
+
+ "@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.83.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.83.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-bi3tvpCrLvN46C4UfKStgHhGOdaZXvEewyHb/ms0n9OjWJBtGePk5Fp/XtdFhsoYu4JjSUvfWPnHH/7zlQGUzw=="],
+
+ "@react-native/metro-config": ["@react-native/metro-config@0.83.6", "", { "dependencies": { "@react-native/js-polyfills": "0.83.6", "@react-native/metro-babel-transformer": "0.83.6", "metro-config": "^0.83.6", "metro-runtime": "^0.83.6" } }, "sha512-n0FrFPrvKb4/BFIVk5ty7hGYn+JPi55wS6sqQTwos4PqpN88qc5sESPqFrrQnO6EzJBmr4Z61Pk9xct69RnPnQ=="],
+
+ "@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.6", "", {}, "sha512-bTM24b5v4qN3h52oflnv+OujFORn/kVi06WaWhnQQw14/ycilPqIsqsa+DpIBqdBrXxvLa9fXtCRrQtGATZCEw=="],
+
+ "@react-native/typescript-config": ["@react-native/typescript-config@0.83.6", "", {}, "sha512-8+sMNBE391MBZjAHGI5EAV14IHbREPyOEZe9yc1PmO/t2nirMPohe+HXRHkl/ANQusCE30EVVu4JGv76jw2WBw=="],
+
+ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.6", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-gNSFXeb4P7qHtauLvl+zESroULIyX6Ltpvau3dhwy/QmfanBv0KUcrIU/7aVXxtWcXgp+54oWJyu2LIrsZ9+LQ=="],
+
+ "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.8.8", "", { "dependencies": { "@react-navigation/elements": "^2.8.4", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.22", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-WS84QCOEdARICYnpu4OSIOeCNsWuWuHi+WO1FUw2rBwKZnrmTT6g+Mv3wL+YqtnRGv5FuLexysVgOFouHrJCpQ=="],
+
+ "@react-navigation/core": ["@react-navigation/core@7.21.5", "", { "dependencies": { "@react-navigation/routers": "^7.6.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-3hpV7uR41LBW+GHDoLhztZCb/i5ySRJISZ/rez4d7DCHSZo6ej4gNxYclaS6LRguoLiKG7SOCNa6O390AQklZQ=="],
+
+ "@react-navigation/elements": ["@react-navigation/elements@2.9.30", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-2isleieiRMmP4WNMV2Q1u3qP1M47ZqsJ2hJ/Og11FeKXK8YmUTHya7PW7ecsgAh2CXKTxAcbfJDFTSvq2D++Tw=="],
+
+ "@react-navigation/native": ["@react-navigation/native@7.3.8", "", { "dependencies": { "@react-navigation/core": "^7.21.5", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.7", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-zHmQcxWBT8GOwsofEOmHqpdM5twkwE/esa9JFGlW4hpXeQTTe/dRcPSLjwsvePtolbDKz0YZbK+I5KrW3j63LQ=="],
+
+ "@react-navigation/native-stack": ["@react-navigation/native-stack@7.17.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.30", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.3.8", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-m1BWVEaOPX9k30DbmhsD7IlrUdl4J7Ogmo71rcNlbZQPMNB126av/nfwqB+qN7DIsiwTAnORnT+SwzD56qqD0Q=="],
+
+ "@react-navigation/routers": ["@react-navigation/routers@7.6.0", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow=="],
+
+ "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
+
+ "@sideway/formula": ["@sideway/formula@3.0.1", "", {}, "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="],
+
+ "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="],
+
+ "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
+
+ "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
+
+ "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
+
+ "@tsconfig/react-native": ["@tsconfig/react-native@2.0.3", "", {}, "sha512-jE58snEKBd9DXfyR4+ssZmYJ/W2mOSnNrvljR0aLyQJL9JKX6vlWELHkRjb3HBbcM9Uy0hZGijXbqEAjOERW2A=="],
+
+ "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
+
+ "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
+
+ "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
+
+ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
+
+ "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
+
+ "@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="],
+
+ "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
+
+ "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
+
+ "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
+
+ "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
+
+ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+
+ "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
+
+ "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
+
+ "@types/react-test-renderer": ["@types/react-test-renderer@19.1.0", "", { "dependencies": { "@types/react": "*" } }, "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ=="],
+
+ "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
+
+ "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
+
+ "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
+
+ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
+
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@5.62.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/type-utils": "5.62.0", "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, "peerDependencies": { "@typescript-eslint/parser": "^5.0.0", "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0", "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag=="],
+
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@5.62.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0", "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA=="],
+
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="],
+
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" } }, "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w=="],
+
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="],
+
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@5.62.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, "peerDependencies": { "eslint": "*", "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew=="],
+
+ "@typescript-eslint/types": ["@typescript-eslint/types@5.62.0", "", {}, "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ=="],
+
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "semver": "^7.3.7", "tsutils": "^3.21.0" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA=="],
+
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@5.62.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ=="],
+
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="],
+
+ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.2", "", {}, "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA=="],
+
+ "@vscode/sudo-prompt": ["@vscode/sudo-prompt@9.3.2", "", {}, "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw=="],
+
+ "@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
+
+ "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
+
+ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
+
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
+
+ "anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
+
+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
+
+ "ansi-fragments": ["ansi-fragments@0.2.1", "", { "dependencies": { "colorette": "^1.0.7", "slice-ansi": "^2.0.0", "strip-ansi": "^5.0.0" } }, "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+
+ "anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="],
+
+ "appdirsjs": ["appdirsjs@1.2.7", "", {}, "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw=="],
+
+ "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
+
+ "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
+
+ "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
+
+ "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
+
+ "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
+
+ "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
+
+ "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="],
+
+ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
+
+ "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
+
+ "astral-regex": ["astral-regex@1.0.0", "", {}, "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg=="],
+
+ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
+
+ "async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="],
+
+ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
+
+ "axios": ["axios@0.22.0", "", { "dependencies": { "follow-redirects": "^1.14.4" } }, "sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w=="],
+
+ "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
+
+ "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
+
+ "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
+
+ "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
+
+ "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="],
+
+ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
+
+ "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
+
+ "babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.2", "", {}, "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA=="],
+
+ "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="],
+
+ "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
+
+ "babel-plugin-transform-remove-console": ["babel-plugin-transform-remove-console@6.9.4", "", {}, "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg=="],
+
+ "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
+
+ "babel-preset-expo": ["babel-preset-expo@55.0.23", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.83.6", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^55.0.20", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-oY+UFcXRLtidYp0JcttzxV5Qxx7KBjBJIShalb+hk4TQoLYafJr4ubpYajLwt7BBgAIVwBdxltKeYeNYXYf02A=="],
+
+ "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "base-64": ["base-64@0.1.0", "", {}, "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="],
+
+ "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],
+
+ "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
+
+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
+ "body-parser": ["body-parser@1.20.6", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g=="],
+
+ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+
+ "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="],
+
+ "bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
+
+ "brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="],
+
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+
+ "browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="],
+
+ "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
+
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
+
+ "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
+
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
+
+ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+
+ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001803", "", {}, "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
+
+ "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
+
+ "chromium-edge-launcher": ["chromium-edge-launcher@0.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg=="],
+
+ "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+
+ "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
+
+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
+
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
+
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
+
+ "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
+
+ "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="],
+
+ "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
+
+ "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="],
+
+ "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="],
+
+ "commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
+
+ "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
+
+ "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+ "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="],
+
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="],
+
+ "cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
+
+ "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
+
+ "css-tree": ["css-tree@1.1.3", "", { "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" } }, "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q=="],
+
+ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+
+ "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
+
+ "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
+
+ "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
+
+ "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
+
+ "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
+
+ "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
+
+ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
+
+ "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
+
+ "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
+
+ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
+
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
+
+ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
+
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="],
+
+ "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
+
+ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
+
+ "dnssd-advertise": ["dnssd-advertise@1.1.6", "", {}, "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg=="],
+
+ "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
+
+ "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
+
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
+
+ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
+
+ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
+
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="],
+
+ "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
+
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+ "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
+
+ "envinfo": ["envinfo@7.21.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow=="],
+
+ "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
+
+ "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="],
+
+ "errorhandler": ["errorhandler@1.5.2", "", { "dependencies": { "accepts": "~1.3.8", "escape-html": "~1.0.3" } }, "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw=="],
+
+ "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="],
+
+ "es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="],
+
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-iterator-helpers": ["es-iterator-helpers@1.3.3", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0" } }, "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g=="],
+
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
+
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
+ "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
+
+ "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
+
+ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
+ "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
+
+ "eslint-config-prettier": ["eslint-config-prettier@8.10.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A=="],
+
+ "eslint-plugin-eslint-comments": ["eslint-plugin-eslint-comments@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5", "ignore": "^5.0.5" }, "peerDependencies": { "eslint": ">=4.19.1" } }, "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ=="],
+
+ "eslint-plugin-ft-flow": ["eslint-plugin-ft-flow@2.0.3", "", { "dependencies": { "lodash": "^4.17.21", "string-natural-compare": "^3.0.1" }, "peerDependencies": { "@babel/eslint-parser": "^7.12.0", "eslint": "^8.1.0" } }, "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg=="],
+
+ "eslint-plugin-jest": ["eslint-plugin-jest@26.9.0", "", { "dependencies": { "@typescript-eslint/utils": "^5.10.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^5.0.0", "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0", "jest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "jest"] }, "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng=="],
+
+ "eslint-plugin-prettier": ["eslint-plugin-prettier@4.2.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0" }, "peerDependencies": { "eslint": ">=7.28.0", "eslint-config-prettier": "*", "prettier": ">=2.0.0" }, "optionalPeers": ["eslint-config-prettier"] }, "sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg=="],
+
+ "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
+
+ "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@4.6.2", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ=="],
+
+ "eslint-plugin-react-native": ["eslint-plugin-react-native@4.1.0", "", { "dependencies": { "eslint-plugin-react-native-globals": "^0.1.1" }, "peerDependencies": { "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q=="],
+
+ "eslint-plugin-react-native-globals": ["eslint-plugin-react-native-globals@0.1.2", "", {}, "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g=="],
+
+ "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
+
+ "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+ "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
+
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+ "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
+
+ "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
+
+ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+
+ "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="],
+
+ "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
+
+ "expo": ["expo@55.0.27", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "55.0.33", "@expo/config": "~55.0.18", "@expo/config-plugins": "~55.0.10", "@expo/devtools": "55.0.3", "@expo/fingerprint": "0.16.7", "@expo/local-build-cache-provider": "55.0.14", "@expo/log-box": "55.0.12", "@expo/metro": "~55.1.1", "@expo/metro-config": "55.0.24", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~55.0.23", "expo-asset": "~55.0.17", "expo-constants": "~55.0.16", "expo-file-system": "~55.0.23", "expo-font": "~55.0.8", "expo-keep-awake": "~55.0.8", "expo-modules-autolinking": "55.0.24", "expo-modules-core": "55.0.25", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-uB8fbowkb/Xel2/x4b+cFfg76GGb2lhgaPVdm/ago+QL/852VJP+bUU6LzQVszQFmCTMHCJyrQ4l8enThCnfxw=="],
+
+ "expo-asset": ["expo-asset@55.0.17", "", { "dependencies": { "@expo/image-utils": "^0.8.14", "expo-constants": "~55.0.16" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-pK9HHJuFqjE8kDUcbMFsZj3Cz8WdXpvZHZmYl7ouFQp59P83BvHln6VnqPDGlO+/4929G0Lm8ZUzbONuNRhi9w=="],
+
+ "expo-constants": ["expo-constants@55.0.16", "", { "dependencies": { "@expo/env": "~2.1.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Z15/No94UHoogD+pulxjudGAeOHTEIWZgb/vnX48Wx5D+apWTeCbnKxQZZtGQlosvduYL5kaic2/W8U+NHfBQQ=="],
+
+ "expo-file-system": ["expo-file-system@55.0.23", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-XNV/NaPc7Tvd2JIsWkPW8s2vfJUI/Oe4UMX9TmOGpc6cn5BckDw/nn2Qre9lq4btT4FWHJw4SAOHmATShLC0og=="],
+
+ "expo-font": ["expo-font@55.0.8", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-WyP75pnKqhLNktYwDn3xKAUNt5rLihRDv8XWGhhz6VEhVqypixpT86NA3uGtiDTlM3gGjhrYCY7o7ypXgCUOZg=="],
+
+ "expo-image": ["expo-image@55.0.11", "", { "dependencies": { "sf-symbols-typescript": "^2.2.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-PVIBYQJW/h1f6Zb9xnoWlgfqyOPVm2yb6eo6ZogaKbvMrhb/Q/fiERbagi4oqmR6IPljWPEpkXXQyFBUh7TjpQ=="],
+
+ "expo-keep-awake": ["expo-keep-awake@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-PfIpMfM+STOBwkR5XOE+yVtER86c44MD+W8QD8JxuO0sT9pF7Y1SJYakWlpvX8xsGA+bjKLxftm9403s9kQhKA=="],
+
+ "expo-modules-autolinking": ["expo-modules-autolinking@55.0.24", "", { "dependencies": { "@expo/require-utils": "^55.0.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-A0OyMbTPZqibYrwqj98HFYTNSvl4NSS4Zt+R5A8qiAx3nM0mc81e6Iqw7Wl4J8M/t36lJ+cT3WuVTz5Oszj6Hw=="],
+
+ "expo-modules-core": ["expo-modules-core@55.0.25", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-yXpfg7aHLbuqoXocK34Vua6Aey5SCyqLygAsXAMbul9P8vfBjLpaOPiTJ5cLVF7Drfq8ownqVJO6qpGEtZ6GOw=="],
+
+ "expo-secure-store": ["expo-secure-store@55.0.15", "", { "peerDependencies": { "expo": "*" } }, "sha512-w2T76G6U6A7NRDSTXn/9CarXMCDw+YFP4aEux+vskA0SrJGyMQXP+onJVAu2nGNaUvvW0RqIqxkxdW1bgSSjvQ=="],
+
+ "expo-server": ["expo-server@55.0.11", "", {}, "sha512-AxRdHqcv0H1g4s923vu+5n1Nrhne23bjXbP+Vl7+Lwfpe7MG9PuU1IS95IJK6a+7BVV1mRN6QlZvs8Yv7EEXNQ=="],
+
+ "expo-web-browser": ["expo-web-browser@55.0.17", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-iquFeEBGQMB10lfg0DtXMOGlBt0D3zCQsOTn9fJJXpQ3lHT6U2sRg8/Ed91OJGMkNSpr74TNRYO8XPJWmnjJ6Q=="],
+
+ "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
+
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+
+ "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
+
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
+ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+
+ "fast-xml-builder": ["fast-xml-builder@1.2.1", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-tPb5TTWfgfVx5BNSi2xV0eLr89POeXXn0dXIsCJ9m1narrWxeIyx6je9d7Rce/3NyXLbvuQmLkxq+RuxMWejvw=="],
+
+ "fast-xml-parser": ["fast-xml-parser@5.9.3", "", { "dependencies": { "@nodable/entities": "^2.2.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^1.0.1", "path-expression-matcher": "^1.5.0", "strnum": "^2.4.1", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g=="],
+
+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
+
+ "fb-dotslash": ["fb-dotslash@0.5.8", "", { "bin": { "dotslash": "bin/dotslash" } }, "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA=="],
+
+ "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "fetch-nodeshim": ["fetch-nodeshim@0.4.10", "", {}, "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w=="],
+
+ "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
+
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
+ "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="],
+
+ "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="],
+
+ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
+
+ "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
+
+ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
+
+ "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="],
+
+ "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
+
+ "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
+
+ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
+
+ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+
+ "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
+
+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="],
+
+ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
+
+ "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+ "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
+
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
+ "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
+
+ "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
+
+ "getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
+
+ "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+
+ "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
+
+ "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
+
+ "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
+
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
+
+ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
+
+ "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
+
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
+
+ "hermes-compiler": ["hermes-compiler@0.14.1", "", {}, "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA=="],
+
+ "hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
+
+ "hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
+
+ "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
+
+ "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
+
+ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
+
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
+
+ "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+ "image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
+
+ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
+
+ "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
+
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
+
+ "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
+
+ "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
+
+ "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
+
+ "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
+
+ "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
+
+ "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
+
+ "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
+
+ "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
+
+ "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
+
+ "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
+
+ "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
+
+ "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="],
+
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+ "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="],
+
+ "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
+
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
+
+ "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
+
+ "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
+
+ "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
+
+ "is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
+
+ "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
+
+ "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
+
+ "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
+
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
+ "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
+
+ "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
+
+ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
+
+ "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
+
+ "is-unsafe": ["is-unsafe@1.0.1", "", {}, "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA=="],
+
+ "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
+
+ "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
+
+ "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
+
+ "is-wsl": ["is-wsl@1.1.0", "", {}, "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw=="],
+
+ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
+
+ "istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
+
+ "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
+
+ "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="],
+
+ "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
+
+ "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
+
+ "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="],
+
+ "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="],
+
+ "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="],
+
+ "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="],
+
+ "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="],
+
+ "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
+
+ "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="],
+
+ "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="],
+
+ "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
+
+ "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
+
+ "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
+
+ "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="],
+
+ "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
+
+ "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
+
+ "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
+
+ "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="],
+
+ "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
+
+ "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="],
+
+ "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="],
+
+ "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="],
+
+ "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="],
+
+ "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="],
+
+ "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
+
+ "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
+
+ "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="],
+
+ "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
+
+ "jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="],
+
+ "joi": ["joi@17.13.4", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
+
+ "jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+
+ "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
+
+ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
+ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
+
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+ "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
+
+ "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+
+ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+
+ "lan-network": ["lan-network@0.2.1", "", { "bin": { "lan-network": "dist/lan-network-cli.js" } }, "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A=="],
+
+ "launch-editor": ["launch-editor@2.14.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.4" } }, "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA=="],
+
+ "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
+
+ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+
+ "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
+
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
+
+ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
+ "linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="],
+
+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
+
+ "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
+
+ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
+
+ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
+
+ "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
+
+ "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
+
+ "logkitty": ["logkitty@0.7.1", "", { "dependencies": { "ansi-fragments": "^0.2.1", "dayjs": "^1.8.15", "yargs": "^15.1.0" }, "bin": { "logkitty": "bin/logkitty.js" } }, "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ=="],
+
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+
+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
+ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
+
+ "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
+
+ "markdown-it": ["markdown-it@14.3.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw=="],
+
+ "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
+
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
+ "mdn-data": ["mdn-data@2.0.14", "", {}, "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="],
+
+ "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
+
+ "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
+
+ "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
+
+ "merge-options": ["merge-options@3.0.4", "", { "dependencies": { "is-plain-obj": "^2.1.0" } }, "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ=="],
+
+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
+
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+
+ "metro": ["metro@0.83.7", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.35.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.7", "metro-cache": "0.83.7", "metro-cache-key": "0.83.7", "metro-config": "0.83.7", "metro-core": "0.83.7", "metro-file-map": "0.83.7", "metro-resolver": "0.83.7", "metro-runtime": "0.83.7", "metro-source-map": "0.83.7", "metro-symbolicate": "0.83.7", "metro-transform-plugins": "0.83.7", "metro-transform-worker": "0.83.7", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ=="],
+
+ "metro-babel-transformer": ["metro-babel-transformer@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.35.0", "metro-cache-key": "0.83.7", "nullthrows": "^1.1.1" } }, "sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA=="],
+
+ "metro-cache": ["metro-cache@0.83.7", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.7" } }, "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg=="],
+
+ "metro-cache-key": ["metro-cache-key@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg=="],
+
+ "metro-config": ["metro-config@0.83.7", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.7", "metro-cache": "0.83.7", "metro-core": "0.83.7", "metro-runtime": "0.83.7", "yaml": "^2.6.1" } }, "sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q=="],
+
+ "metro-core": ["metro-core@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.7" } }, "sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg=="],
+
+ "metro-file-map": ["metro-file-map@0.83.7", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw=="],
+
+ "metro-minify-terser": ["metro-minify-terser@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ=="],
+
+ "metro-resolver": ["metro-resolver@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A=="],
+
+ "metro-runtime": ["metro-runtime@0.83.7", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ=="],
+
+ "metro-source-map": ["metro-source-map@0.83.7", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.7", "nullthrows": "^1.1.1", "ob1": "0.83.7", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw=="],
+
+ "metro-symbolicate": ["metro-symbolicate@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.7", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw=="],
+
+ "metro-transform-plugins": ["metro-transform-plugins@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA=="],
+
+ "metro-transform-worker": ["metro-transform-worker@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "metro": "0.83.7", "metro-babel-transformer": "0.83.7", "metro-cache": "0.83.7", "metro-cache-key": "0.83.7", "metro-minify-terser": "0.83.7", "metro-source-map": "0.83.7", "metro-transform-plugins": "0.83.7", "nullthrows": "^1.1.1" } }, "sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="],
+
+ "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
+ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+
+ "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
+ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "mobx": ["mobx@6.16.1", "", {}, "sha512-syNcDdX3KT+Jq3je6eGjBhuc24Z68td2VG0zNFqRswaE433D9SNH5VRy/xrGbJsUixfppLLccXhAW9JSf6n+SQ=="],
+
+ "mobx-react": ["mobx-react@7.6.0", "", { "dependencies": { "mobx-react-lite": "^3.4.0" }, "peerDependencies": { "mobx": "^6.1.0", "react": "^16.8.0 || ^17 || ^18", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA=="],
+
+ "mobx-react-lite": ["mobx-react-lite@3.4.3", "", { "peerDependencies": { "mobx": "^6.1.0", "react": "^16.8.0 || ^17 || ^18", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg=="],
+
+ "mobx-state-tree": ["mobx-state-tree@7.3.1", "", { "dependencies": { "ts-essentials": "^9.4.1" }, "peerDependencies": { "mobx": "^6.3.0" } }, "sha512-SxX7BBWOCN7AdzHzjLAJcMsyUoIPlFy9zOtsfLFRUhB7IviIYGTYYccv9OFSOatLjh7nJwzZ5U3XDRlmbCrtfQ=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "multitars": ["multitars@1.0.0", "", {}, "sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg=="],
+
+ "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
+
+ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+
+ "natural-compare-lite": ["natural-compare-lite@1.4.0", "", {}, "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="],
+
+ "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
+
+ "nocache": ["nocache@3.0.4", "", {}, "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw=="],
+
+ "node-exports-info": ["node-exports-info@1.6.2", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag=="],
+
+ "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
+
+ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
+
+ "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
+
+ "node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="],
+
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
+ "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
+
+ "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
+
+ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
+
+ "nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
+
+ "ob1": ["ob1@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg=="],
+
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+
+ "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
+
+ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
+
+ "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
+
+ "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
+
+ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
+
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
+
+ "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
+
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+
+ "open": ["open@6.4.0", "", { "dependencies": { "is-wsl": "^1.1.0" } }, "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg=="],
+
+ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
+
+ "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
+
+ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
+
+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+
+ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
+
+ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+
+ "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
+
+ "parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
+
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
+
+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+
+ "path-expression-matcher": ["path-expression-matcher@1.6.2", "", {}, "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ=="],
+
+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
+ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
+
+ "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
+
+ "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
+
+ "plist": ["plist@3.1.1", "", { "dependencies": { "@xmldom/xmldom": "^0.9.10", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA=="],
+
+ "pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
+
+ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
+
+ "postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="],
+
+ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+
+ "prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
+
+ "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="],
+
+ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
+
+ "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
+
+ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
+
+ "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="],
+
+ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
+
+ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
+
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
+
+ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
+
+ "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
+
+ "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
+
+ "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
+
+ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
+
+ "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+
+ "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
+
+ "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="],
+
+ "react-is": ["react-is@19.2.7", "", {}, "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A=="],
+
+ "react-native": ["react-native@0.83.6", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.83.6", "@react-native/codegen": "0.83.6", "@react-native/community-cli-plugin": "0.83.6", "@react-native/gradle-plugin": "0.83.6", "@react-native/js-polyfills": "0.83.6", "@react-native/normalize-colors": "0.83.6", "@react-native/virtualized-lists": "0.83.6", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "hermes-compiler": "0.14.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.6", "metro-source-map": "^0.83.6", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-H513+8VzviNFXOdPnStRzX9S3/jiJGg++QZ1zd+ROyAvBEKqFqKUPHH0d82y3QyRPct5qKjdOa7J6vNehCvXYA=="],
+
+ "react-native-actions-sheet": ["react-native-actions-sheet@0.9.8", "", { "peerDependencies": { "react-native": "*", "react-native-gesture-handler": "*", "react-native-safe-area-context": "*" } }, "sha512-hoFLKEoknwQEuxANbRxtg/LofvhqqLYbtIp/XiqC2cI+nyfYVra5i62A75ksZdBAqB6iZ3nolOwL8voLfSUXIw=="],
+
+ "react-native-context-menu-view": ["react-native-context-menu-view@1.21.0", "", { "peerDependencies": { "react": "^16.8.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-native": ">=0.60.0-rc.0 <1.0.x" } }, "sha512-GVQckdDxKyM4BYqWqiApnfzzk56vLEMWiVLsvs95mPhJJYvzRXx+Ev3T6DVEasWCZxk6PbgtcZaWi9jnXGdhCQ=="],
+
+ "react-native-device-info": ["react-native-device-info@8.7.1", "", { "peerDependencies": { "react-native": "*" } }, "sha512-cVMZztFa2Qn6qpQa601W61CtUwZQ1KXfqCOeltejAWEXmgIWivC692WGSdtGudj4upSi1UgMSaGcvKjfcpdGjg=="],
+
+ "react-native-fs": ["react-native-fs@2.20.0", "", { "dependencies": { "base-64": "^0.1.0", "utf8": "^3.0.0" }, "peerDependencies": { "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["react-native-windows"] }, "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ=="],
+
+ "react-native-gesture-handler": ["react-native-gesture-handler@2.30.1", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA=="],
+
+ "react-native-hyperlink": ["react-native-hyperlink@0.0.22", "", { "dependencies": { "linkify-it": "^4.0.1", "mdurl": "^1.0.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-IVhG+aP2bAnllUEr08/+rGA3cbpzyl83PtOfe94higfWUR8E7rUGThj21tdhx8SWAyl+4XO1K864tA6ybVbMFA=="],
+
+ "react-native-image-picker": ["react-native-image-picker@7.2.3", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zKIZUlQNU3EtqizsXSH92zPeve4vpUrsqHu2kkpCxWE9TZhJFZBb+irDsBOY8J21k0+Edgt06TMQGJ+iPUIXyA=="],
+
+ "react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.3.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA=="],
+
+ "react-native-keyboard-controller": ["react-native-keyboard-controller@1.20.7", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-reanimated": ">=3.0.0" } }, "sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw=="],
+
+ "react-native-push-notification": ["react-native-push-notification@8.1.1", "", { "peerDependencies": { "@react-native-community/push-notification-ios": "^1.10.1", "react-native": ">=0.33" } }, "sha512-XpBtG/w+a6WXTxu6l1dNYyTiHnbgnvjoc3KxPTxYkaIABRmvuJZkFxqruyGvfCw7ELAlZEAJO+dthdTabCe1XA=="],
+
+ "react-native-reanimated": ["react-native-reanimated@4.2.1", "", { "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" } }, "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg=="],
+
+ "react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
+
+ "react-native-screens": ["react-native-screens@4.23.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw=="],
+
+ "react-native-sfsymbols": ["react-native-sfsymbols@1.2.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/9zshSuIlvCTnD9yfrD7/AbO/Vhlcml8GEri9gLa+sRhZL2ZFvqTOopiRJdC9+eWO+NVtLN+qX8FNXm5y5URSQ=="],
+
+ "react-native-share-menu": ["react-native-share-menu@github:microdotblog/react-native-share-menu#116e508", {}, "microdotblog-react-native-share-menu-116e508", "sha512-+9AuKzqycD0vskxiACKGYwKCnkaembtnjK6U5atoqSeMTSzLedpGw/q0SfY9rhCUe3egMNTFfQThPThFUy+qbA=="],
+
+ "react-native-simple-toast": ["react-native-simple-toast@3.3.2", "", { "peerDependencies": { "react": "*", "react-native": ">=0.71.0" } }, "sha512-jtZa5VXLDc3Rr+1a2eYyviakxOXP8U9AFYp1VWlslApi0wkiNeyAQ4yLZbIY8nayrbHTUSkERiDzKoCsy9v7Dw=="],
+
+ "react-native-svg": ["react-native-svg@15.15.3", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA=="],
+
+ "react-native-url-polyfill": ["react-native-url-polyfill@2.0.0", "", { "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "react-native": "*" } }, "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA=="],
+
+ "react-native-video": ["react-native-video@6.19.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-gh5dBMGSHywKr8+p4azRXrFQgT+QTmS1tI4sz6kVRjSENORfsrGGLQzmyo/7S9m+ZOTLWQJ3h8ONaXxR4F2Fcw=="],
+
+ "react-native-webview": ["react-native-webview@13.16.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA=="],
+
+ "react-native-worklets": ["react-native-worklets@0.7.4", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag=="],
+
+ "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
+
+ "react-test-renderer": ["react-test-renderer@19.2.0", "", { "dependencies": { "react-is": "^19.2.0", "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ=="],
+
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
+
+ "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
+
+ "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
+
+ "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
+
+ "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
+
+ "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
+
+ "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
+
+ "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="],
+
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
+
+ "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="],
+
+ "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
+
+ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+ "resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="],
+
+ "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
+
+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
+
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+ "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
+
+ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
+
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
+
+ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
+
+ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
+
+ "serialize-error": ["serialize-error@2.1.0", "", {}, "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw=="],
+
+ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
+
+ "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
+
+ "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
+
+ "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
+
+ "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
+
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+
+ "sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="],
+
+ "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
+
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
+
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
+
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
+
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="],
+
+ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
+
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
+ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
+ "slice-ansi": ["slice-ansi@2.1.0", "", { "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ=="],
+
+ "slugify": ["slugify@1.6.9", "", {}, "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg=="],
+
+ "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
+ "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
+
+ "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
+ "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
+
+ "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
+
+ "stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
+
+ "standard-navigation": ["standard-navigation@0.0.7", "", {}, "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ=="],
+
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
+
+ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
+
+ "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="],
+
+ "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
+
+ "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
+
+ "string-natural-compare": ["string-natural-compare@3.0.1", "", {}, "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
+
+ "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="],
+
+ "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="],
+
+ "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="],
+
+ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
+
+ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+
+ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
+ "strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
+
+ "structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
+
+ "terser": ["terser@5.49.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA=="],
+
+ "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
+
+ "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
+
+ "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="],
+
+ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
+
+ "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
+
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+
+ "toqr": ["toqr@0.1.1", "", {}, "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA=="],
+
+ "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
+
+ "ts-essentials": ["ts-essentials@9.4.2", "", { "peerDependencies": { "typescript": ">=4.1.0" }, "optionalPeers": ["typescript"] }, "sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ=="],
+
+ "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
+
+ "tsutils": ["tsutils@3.21.0", "", { "dependencies": { "tslib": "^1.8.1" }, "peerDependencies": { "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA=="],
+
+ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+
+ "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
+
+ "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
+
+ "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
+
+ "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
+
+ "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
+
+ "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
+
+ "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
+
+ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
+
+ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
+
+ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
+
+ "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
+
+ "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
+
+ "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
+
+ "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
+
+ "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
+
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+
+ "use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="],
+
+ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
+
+ "utf8": ["utf8@3.0.0", "", {}, "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
+
+ "uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
+
+ "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
+
+ "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
+
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+
+ "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="],
+
+ "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
+
+ "warn-once": ["warn-once@0.1.1", "", {}, "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q=="],
+
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
+
+ "webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="],
+
+ "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
+
+ "whatwg-url-minimum": ["whatwg-url-minimum@0.1.2", "", {}, "sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A=="],
+
+ "whatwg-url-without-unicode": ["whatwg-url-without-unicode@8.0.0-3", "", { "dependencies": { "buffer": "^5.4.3", "punycode": "^2.1.1", "webidl-conversions": "^5.0.0" } }, "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
+
+ "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
+
+ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
+
+ "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
+
+ "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="],
+
+ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+ "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
+
+ "ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="],
+
+ "xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
+
+ "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
+
+ "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
+
+ "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+
+ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
+
+ "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
+
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
+ "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
+
+ "@babel/eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@2.1.0", "", {}, "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="],
+
+ "@babel/helper-define-polyfill-provider/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
+
+ "@babel/highlight/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
+
+ "@expo/cli/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "@expo/cli/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
+
+ "@expo/cli/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@expo/cli/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
+
+ "@expo/config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "@expo/config/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@expo/config-plugins/@expo/json-file": ["@expo/json-file@10.0.16", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw=="],
+
+ "@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "@expo/config-plugins/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+
+ "@expo/fingerprint/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "@expo/fingerprint/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@expo/fingerprint/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@expo/image-utils/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@expo/metro-config/@expo/json-file": ["@expo/json-file@10.0.16", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw=="],
+
+ "@expo/metro-config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "@expo/package-manager/@expo/json-file": ["@expo/json-file@11.0.0", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-pHJCETqFL5x5BzNV6cEPwjwuECgGmnl0bNmfHIJ6LM1tlh2eVXi5HEdit3zby/JO/B8Otk5cgcqtJXgvvUat3A=="],
+
+ "@expo/package-manager/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
+
+ "@expo/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="],
+
+ "@expo/prebuild-config/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
+ "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+ "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.15.0", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog=="],
+
+ "@jest/reporters/istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="],
+
+ "@nicolo-ribaudo/eslint-scope-5-internals/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
+
+ "@react-native-community/cli/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native-community/cli-config-android/fast-xml-parser": ["fast-xml-parser@4.5.7", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ=="],
+
+ "@react-native-community/cli-doctor/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native-community/cli-platform-apple/fast-xml-parser": ["fast-xml-parser@4.5.7", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ=="],
+
+ "@react-native-community/cli-server-api/ws": ["ws@6.2.4", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw=="],
+
+ "@react-native-community/cli-tools/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native/community-cli-plugin/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.63.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/type-utils": "8.63.0", "@typescript-eslint/utils": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.63.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser": ["@typescript-eslint/parser@8.63.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest": ["eslint-plugin-jest@29.15.4", "", { "dependencies": { "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "jest": "*", "typescript": ">=4.8.4 <7.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "jest", "typescript"] }, "sha512-6ln5i9Nkrb27X4w91ZPt/xHDsVQnvxTS2ntgq6r32u+8gymdUrp88TdcBXSveZW0Dl+M5v2H6K75kJhMvUGhjg=="],
+
+ "@react-native/eslint-config/eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
+
+ "@typescript-eslint/eslint-plugin/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
+
+ "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
+
+ "@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@typescript-eslint/utils/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
+
+ "@typescript-eslint/utils/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
+
+ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
+
+ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
+
+ "ansi-fragments/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
+
+ "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
+
+ "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "chrome-launcher/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "chromium-edge-launcher/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
+
+ "eslint-plugin-eslint-comments/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "eslint-plugin-react/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
+
+ "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
+
+ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
+
+ "finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="],
+
+ "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
+
+ "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+
+ "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+
+ "istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "jest-resolve/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
+
+ "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
+
+ "jest-snapshot/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+ "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "logkitty/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
+
+ "make-dir/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "metro/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
+
+ "metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
+
+ "metro/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="],
+
+ "metro-babel-transformer/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="],
+
+ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "npm-package-arg/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
+
+ "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+ "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
+ "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+
+ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+
+ "react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
+
+ "react-native/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "react-native-hyperlink/linkify-it": ["linkify-it@4.0.1", "", { "dependencies": { "uc.micro": "^1.0.1" } }, "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw=="],
+
+ "react-native-hyperlink/mdurl": ["mdurl@1.0.1", "", {}, "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="],
+
+ "react-native-reanimated/react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
+
+ "react-native-reanimated/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+ "react-native-worklets/@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
+
+ "react-native-worklets/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="],
+
+ "react-native-worklets/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="],
+
+ "react-native-worklets/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="],
+
+ "react-native-worklets/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg=="],
+
+ "react-native-worklets/@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="],
+
+ "react-native-worklets/@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="],
+
+ "react-native-worklets/@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="],
+
+ "react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+ "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
+
+ "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
+
+ "slice-ansi/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@2.0.0", "", {}, "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="],
+
+ "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
+
+ "stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
+
+ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
+
+ "type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
+
+ "@babel/highlight/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "@babel/highlight/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "@babel/highlight/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "@expo/cli/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@expo/cli/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "@expo/cli/ora/cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
+
+ "@expo/cli/ora/log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="],
+
+ "@expo/cli/ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
+
+ "@expo/config-plugins/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
+
+ "@expo/config-plugins/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@expo/config/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@expo/fingerprint/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@expo/metro-config/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
+
+ "@expo/metro-config/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@expo/package-manager/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "@expo/package-manager/ora/cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
+
+ "@expo/package-manager/ora/log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="],
+
+ "@expo/package-manager/ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
+
+ "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+ "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+
+ "@jest/reporters/istanbul-lib-instrument/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@nicolo-ribaudo/eslint-scope-5-internals/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
+
+ "@react-native-community/cli-config-android/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
+
+ "@react-native-community/cli-platform-apple/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
+
+ "@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0", "@typescript-eslint/utils": "8.63.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="],
+
+ "@react-native/eslint-config/eslint-plugin-react-hooks/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
+
+ "@typescript-eslint/utils/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
+
+ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "ansi-fragments/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
+
+ "better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "jest-runner/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "logkitty/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
+
+ "logkitty/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+ "logkitty/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
+
+ "logkitty/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
+
+ "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="],
+
+ "metro/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
+
+ "metro/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="],
+
+ "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+ "react-native-hyperlink/linkify-it/uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="],
+
+ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "slice-ansi/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "@babel/highlight/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "@expo/cli/glob/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@expo/cli/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "@expo/cli/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "@expo/cli/ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "@expo/cli/ora/cli-cursor/restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="],
+
+ "@expo/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
+
+ "@expo/config-plugins/glob/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@expo/config/glob/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@expo/fingerprint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@expo/metro-config/glob/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@expo/package-manager/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "@expo/package-manager/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "@expo/package-manager/ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "@expo/package-manager/ora/cli-cursor/restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="],
+
+ "@expo/package-manager/ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
+
+ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="],
+
+ "@react-native/eslint-config/eslint-plugin-react-hooks/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
+
+ "logkitty/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
+
+ "logkitty/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+ "logkitty/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
+ "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "@expo/cli/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@expo/cli/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "@expo/cli/ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "@expo/cli/ora/cli-cursor/restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
+
+ "@expo/config-plugins/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@expo/config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@expo/metro-config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@expo/package-manager/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "@expo/package-manager/ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "@expo/package-manager/ora/cli-cursor/restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
+
+ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "logkitty/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+
+ "@expo/cli/ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "@expo/cli/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
+
+ "@expo/package-manager/ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "@expo/package-manager/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "logkitty/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "@react-native/eslint-config/eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ }
+}
diff --git a/bun.lockb b/bun.lockb
deleted file mode 100755
index 69225f06..00000000
Binary files a/bun.lockb and /dev/null differ
diff --git a/ios/MicroBlog_RN.xcodeproj/project.pbxproj b/ios/MicroBlog_RN.xcodeproj/project.pbxproj
index 6e0fa4b7..19d45ebd 100644
--- a/ios/MicroBlog_RN.xcodeproj/project.pbxproj
+++ b/ios/MicroBlog_RN.xcodeproj/project.pbxproj
@@ -19,20 +19,18 @@
2837C73874E55DEB2797484F /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961AB331816A548D082E9A56 /* ExpoModulesProvider.swift */; };
28E40035B1E151C13639876C /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADCF007C5134030FD80BBAC8 /* ExpoModulesProvider.swift */; };
4B06FCA829FABD0800E97371 /* UIScrollViewDelegate+Zooming.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B06FCA729FABD0800E97371 /* UIScrollViewDelegate+Zooming.m */; };
- 4B1233D429ECCC0100174307 /* MBHighlightingTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B1233D329ECCC0100174307 /* MBHighlightingTextManager.m */; };
- 4B1233D729ECD7D200174307 /* MBHighlightingTextStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B1233D529ECD7D200174307 /* MBHighlightingTextStorage.m */; };
- 4B1233D829ECD7D200174307 /* MBHighlightingTextStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B1233D529ECD7D200174307 /* MBHighlightingTextStorage.m */; };
4B3498672AE86AA900233BA2 /* RCTInputAccessoryShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3498662AE86AA900233BA2 /* RCTInputAccessoryShadowView.m */; };
4B3498682AE86AA900233BA2 /* RCTInputAccessoryShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B3498662AE86AA900233BA2 /* RCTInputAccessoryShadowView.m */; };
4B719C392E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B719C382E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m */; };
4B719C3A2E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B719C382E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m */; };
- 4B7619D129EC9E7A0030A495 /* MBHighlightingTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B7619D029EC9E7A0030A495 /* MBHighlightingTextView.m */; };
- 4B7619D229EC9E7A0030A495 /* MBHighlightingTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B7619D029EC9E7A0030A495 /* MBHighlightingTextView.m */; };
4B8CE8AB2A1FCC69006D79CF /* GetURL.js in Resources */ = {isa = PBXBuildFile; fileRef = 4B8CE8AA2A1FCC69006D79CF /* GetURL.js */; };
- 4BAA745E2A24E56500953D91 /* MBHighlightingTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B1233D329ECCC0100174307 /* MBHighlightingTextManager.m */; };
4BAA745F2A24E78700953D91 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
4BB0A4602DFCA4DC00900B0B /* MBClipboardHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A45F2DFCA4DC00900B0B /* MBClipboardHelper.m */; };
4BB0A4612DFCA4DC00900B0B /* MBClipboardHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A45F2DFCA4DC00900B0B /* MBClipboardHelper.m */; };
+ 4BD1A3032F08000000A10001 /* MBLiquidGlassButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1A3012F08000000A10001 /* MBLiquidGlassButton.m */; };
+ 4BD1A3042F08000000A10001 /* MBLiquidGlassButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1A3012F08000000A10001 /* MBLiquidGlassButton.m */; };
+ 4BD1A3052F08000000A10001 /* MBLiquidGlassButtonManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1A3022F08000000A10001 /* MBLiquidGlassButtonManager.m */; };
+ 4BD1A3062F08000000A10001 /* MBLiquidGlassButtonManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD1A3022F08000000A10001 /* MBLiquidGlassButtonManager.m */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
8C619CFF939F9E33E65A6DAA /* libPods-MicroBlog_RN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A1AD34313DED25C45B534DA /* libPods-MicroBlog_RN.a */; };
BF76CC3E2E8E980900F61FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF76CC3D2E8E980900F61FC2 /* AppDelegate.swift */; };
@@ -69,7 +67,7 @@
0221AB3329D42CE400838597 /* MicroBlog_Share-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MicroBlog_Share-Bridging-Header.h"; sourceTree = ""; };
0221AB3429D42CE500838597 /* ShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShareViewController.swift; path = "../../node_modules/react-native-share-menu/ios/ShareViewController.swift"; sourceTree = ""; };
0221AB3629D4307300838597 /* MicroBlog_Share.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MicroBlog_Share.entitlements; sourceTree = ""; };
- 0221AB3729D4327B00838597 /* ReactShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReactShareViewController.swift; path = "../../node_modules/react-native-share-menu/ios/ReactShareViewController.swift"; sourceTree = ""; };
+ 0221AB3729D4327B00838597 /* ReactShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactShareViewController.swift; sourceTree = ""; };
0251EE442716191900B475D5 /* MicroBlog_RN-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MicroBlog_RN-Bridging-Header.h"; sourceTree = ""; };
0251EE452716191A00B475D5 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; };
02A72C7B2C2AB086006EF653 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
@@ -81,19 +79,16 @@
47E9C7DD11A09B7944E82B75 /* Pods-MicroBlog_RN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MicroBlog_RN.debug.xcconfig"; path = "Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN.debug.xcconfig"; sourceTree = ""; };
4B06FCA629FABD0800E97371 /* UIScrollViewDelegate+Zooming.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIScrollViewDelegate+Zooming.h"; path = "MicroBlog_RN/UIScrollViewDelegate+Zooming.h"; sourceTree = ""; };
4B06FCA729FABD0800E97371 /* UIScrollViewDelegate+Zooming.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIScrollViewDelegate+Zooming.m"; path = "MicroBlog_RN/UIScrollViewDelegate+Zooming.m"; sourceTree = ""; };
- 4B1233D229ECCC0100174307 /* MBHighlightingTextManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MBHighlightingTextManager.h; path = MicroBlog_RN/MBHighlightingTextManager.h; sourceTree = ""; };
- 4B1233D329ECCC0100174307 /* MBHighlightingTextManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MBHighlightingTextManager.m; path = MicroBlog_RN/MBHighlightingTextManager.m; sourceTree = ""; };
- 4B1233D529ECD7D200174307 /* MBHighlightingTextStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MBHighlightingTextStorage.m; path = MicroBlog_RN/MBHighlightingTextStorage.m; sourceTree = ""; };
- 4B1233D629ECD7D200174307 /* MBHighlightingTextStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MBHighlightingTextStorage.h; path = MicroBlog_RN/MBHighlightingTextStorage.h; sourceTree = ""; };
4B3498652AE86AA900233BA2 /* RCTInputAccessoryShadowView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RCTInputAccessoryShadowView.h; path = MicroBlog_RN/RCTInputAccessoryShadowView.h; sourceTree = ""; };
4B3498662AE86AA900233BA2 /* RCTInputAccessoryShadowView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RCTInputAccessoryShadowView.m; path = MicroBlog_RN/RCTInputAccessoryShadowView.m; sourceTree = ""; };
4B719C372E63B0D6006D4E7D /* UIBarButtonItem+Plainify.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIBarButtonItem+Plainify.h"; path = "MicroBlog_RN/UIBarButtonItem+Plainify.h"; sourceTree = ""; };
4B719C382E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBarButtonItem+Plainify.m"; path = "MicroBlog_RN/UIBarButtonItem+Plainify.m"; sourceTree = ""; };
- 4B7619CF29EC9E7A0030A495 /* MBHighlightingTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MBHighlightingTextView.h; path = MicroBlog_RN/MBHighlightingTextView.h; sourceTree = ""; };
- 4B7619D029EC9E7A0030A495 /* MBHighlightingTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MBHighlightingTextView.m; path = MicroBlog_RN/MBHighlightingTextView.m; sourceTree = ""; };
4B8CE8AA2A1FCC69006D79CF /* GetURL.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = GetURL.js; sourceTree = ""; };
4BB0A45E2DFCA4DC00900B0B /* MBClipboardHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MBClipboardHelper.h; path = MicroBlog_RN/MBClipboardHelper.h; sourceTree = ""; };
4BB0A45F2DFCA4DC00900B0B /* MBClipboardHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MBClipboardHelper.m; path = MicroBlog_RN/MBClipboardHelper.m; sourceTree = ""; };
+ 4BD1A3002F08000000A10001 /* MBLiquidGlassButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MBLiquidGlassButton.h; path = MicroBlog_RN/MBLiquidGlassButton.h; sourceTree = ""; };
+ 4BD1A3012F08000000A10001 /* MBLiquidGlassButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MBLiquidGlassButton.m; path = MicroBlog_RN/MBLiquidGlassButton.m; sourceTree = ""; };
+ 4BD1A3022F08000000A10001 /* MBLiquidGlassButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MBLiquidGlassButtonManager.m; path = MicroBlog_RN/MBLiquidGlassButtonManager.m; sourceTree = ""; };
7A1AD34313DED25C45B534DA /* libPods-MicroBlog_RN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MicroBlog_RN.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7F4610A1564E937F6AF1CE21 /* Pods-MicroBlog_RN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MicroBlog_RN.release.xcconfig"; path = "Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN.release.xcconfig"; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MicroBlog_RN/LaunchScreen.storyboard; sourceTree = ""; };
@@ -143,12 +138,6 @@
isa = PBXGroup;
children = (
02F5714D28C028CD0055509F /* MicroBlog_RN.entitlements */,
- 4B7619CF29EC9E7A0030A495 /* MBHighlightingTextView.h */,
- 4B7619D029EC9E7A0030A495 /* MBHighlightingTextView.m */,
- 4B1233D229ECCC0100174307 /* MBHighlightingTextManager.h */,
- 4B1233D329ECCC0100174307 /* MBHighlightingTextManager.m */,
- 4B1233D629ECD7D200174307 /* MBHighlightingTextStorage.h */,
- 4B1233D529ECD7D200174307 /* MBHighlightingTextStorage.m */,
4BB0A45E2DFCA4DC00900B0B /* MBClipboardHelper.h */,
4BB0A45F2DFCA4DC00900B0B /* MBClipboardHelper.m */,
4B06FCA629FABD0800E97371 /* UIScrollViewDelegate+Zooming.h */,
@@ -157,6 +146,9 @@
4B719C382E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m */,
4B3498652AE86AA900233BA2 /* RCTInputAccessoryShadowView.h */,
4B3498662AE86AA900233BA2 /* RCTInputAccessoryShadowView.m */,
+ 4BD1A3002F08000000A10001 /* MBLiquidGlassButton.h */,
+ 4BD1A3012F08000000A10001 /* MBLiquidGlassButton.m */,
+ 4BD1A3022F08000000A10001 /* MBLiquidGlassButtonManager.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
@@ -275,7 +267,6 @@
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "MicroBlog_RN" */;
buildPhases = (
993BFA2DC71DB1F379D2A371 /* [CP] Check Pods Manifest.lock */,
- FD10A7F022414F080027D42C /* Start Packager */,
2D8EC8CE127AA4FC6BFF25AF /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
@@ -369,7 +360,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n";
+ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios relative | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n";
};
0221AB3929D433D300838597 /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
@@ -387,7 +378,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\n# Force the share extension to bundle its own entry file so it doesn't reuse the main app bundle.\nexport ENTRY_FILE=${ENTRY_FILE:-index.share.js}\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n";
+ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\n# Force the share extension to bundle its own entry file so it doesn't reuse the main app bundle.\nexport ENTRY_FILE=${ENTRY_FILE:-index.share.js}\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n";
};
2D8EC8CE127AA4FC6BFF25AF /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
@@ -398,11 +389,16 @@
inputFileListPaths = (
);
inputPaths = (
+ "$(SRCROOT)/.xcode.env",
+ "$(SRCROOT)/.xcode.env.local",
+ "$(SRCROOT)/MicroBlog_RN/MicroBlog_RN.entitlements",
+ "$(SRCROOT)/Pods/Target Support Files/Pods-MicroBlog_RN/expo-configure-project.sh",
);
name = "[Expo] Configure project";
outputFileListPaths = (
);
outputPaths = (
+ "$(SRCROOT)/Pods/Target Support Files/Pods-MicroBlog_RN/ExpoModulesProvider.swift",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -438,14 +434,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-frameworks.sh\"\n";
@@ -481,14 +473,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-resources-${CONFIGURATION}-output-files.xcfilelist",
);
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN/Pods-MicroBlog_RN-resources.sh\"\n";
@@ -502,14 +490,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN-MicroBlog_Share/Pods-MicroBlog_RN-MicroBlog_Share-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN-MicroBlog_Share/Pods-MicroBlog_RN-MicroBlog_Share-resources-${CONFIGURATION}-output-files.xcfilelist",
);
- outputPaths = (
- );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MicroBlog_RN-MicroBlog_Share/Pods-MicroBlog_RN-MicroBlog_Share-resources.sh\"\n";
@@ -524,35 +508,21 @@
inputFileListPaths = (
);
inputPaths = (
+ "$(SRCROOT)/.xcode.env",
+ "$(SRCROOT)/.xcode.env.local",
+ "$(SRCROOT)/MicroBlog_Share/MicroBlog_Share.entitlements",
+ "$(SRCROOT)/Pods/Target Support Files/Pods-MicroBlog_RN-MicroBlog_Share/expo-configure-project.sh",
);
name = "[Expo] Configure project";
outputFileListPaths = (
);
outputPaths = (
+ "$(SRCROOT)/Pods/Target Support Files/Pods-MicroBlog_RN-MicroBlog_Share/ExpoModulesProvider.swift",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-MicroBlog_RN-MicroBlog_Share/expo-configure-project.sh\"\n";
};
- FD10A7F022414F080027D42C /* Start Packager */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "Start Packager";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\nexport RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > `$NODE_BINARY --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/.packager.env'\"`\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open `$NODE_BINARY --print \"require('path').dirname(require.resolve('expo/package.json')) + '/scripts/launchPackager.command'\"` || echo \"Can't start packager automatically\"\n fi\nfi\n";
- showEnvVarsInLog = 0;
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -561,13 +531,12 @@
buildActionMask = 2147483647;
files = (
0221AB3829D4327B00838597 /* ReactShareViewController.swift in Sources */,
- 4B7619D229EC9E7A0030A495 /* MBHighlightingTextView.m in Sources */,
4B3498682AE86AA900233BA2 /* RCTInputAccessoryShadowView.m in Sources */,
4BB0A4612DFCA4DC00900B0B /* MBClipboardHelper.m in Sources */,
- 4B1233D829ECD7D200174307 /* MBHighlightingTextStorage.m in Sources */,
0221AB3529D42CE500838597 /* ShareViewController.swift in Sources */,
- 4BAA745E2A24E56500953D91 /* MBHighlightingTextManager.m in Sources */,
4B719C392E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m in Sources */,
+ 4BD1A3042F08000000A10001 /* MBLiquidGlassButton.m in Sources */,
+ 4BD1A3062F08000000A10001 /* MBLiquidGlassButtonManager.m in Sources */,
2837C73874E55DEB2797484F /* ExpoModulesProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -576,14 +545,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 4B7619D129EC9E7A0030A495 /* MBHighlightingTextView.m in Sources */,
4BB0A4602DFCA4DC00900B0B /* MBClipboardHelper.m in Sources */,
- 4B1233D729ECD7D200174307 /* MBHighlightingTextStorage.m in Sources */,
- 4B1233D429ECCC0100174307 /* MBHighlightingTextManager.m in Sources */,
0251EE462716191A00B475D5 /* File.swift in Sources */,
4B06FCA829FABD0800E97371 /* UIScrollViewDelegate+Zooming.m in Sources */,
4B719C3A2E63B0D6006D4E7D /* UIBarButtonItem+Plainify.m in Sources */,
4B3498672AE86AA900233BA2 /* RCTInputAccessoryShadowView.m in Sources */,
+ 4BD1A3032F08000000A10001 /* MBLiquidGlassButton.m in Sources */,
+ 4BD1A3052F08000000A10001 /* MBLiquidGlassButtonManager.m in Sources */,
BF76CC3E2E8E980900F61FC2 /* AppDelegate.swift in Sources */,
28E40035B1E151C13639876C /* ExpoModulesProvider.swift in Sources */,
);
@@ -624,7 +592,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MicroBlog_Share/MicroBlog_Share.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 390;
+ CURRENT_PROJECT_VERSION = 458;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3F9MDJ6K4E;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -638,7 +606,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 3.6.5;
+ MARKETING_VERSION = 3.7.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@@ -672,7 +640,7 @@
CODE_SIGN_ENTITLEMENTS = MicroBlog_Share/MicroBlog_Share.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 390;
+ CURRENT_PROJECT_VERSION = 458;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3F9MDJ6K4E;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -686,7 +654,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 3.6.5;
+ MARKETING_VERSION = 3.7.4;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "blog.micro.ios.MicroBlog-Share";
@@ -713,7 +681,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MicroBlog_RN/MicroBlog_RN.entitlements;
- CURRENT_PROJECT_VERSION = 390;
+ CURRENT_PROJECT_VERSION = 458;
DEVELOPMENT_TEAM = 3F9MDJ6K4E;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = MicroBlog_RN/Info.plist;
@@ -722,7 +690,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 3.6.5;
+ MARKETING_VERSION = 3.7.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -751,7 +719,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MicroBlog_RN/MicroBlog_RN.entitlements;
- CURRENT_PROJECT_VERSION = 390;
+ CURRENT_PROJECT_VERSION = 458;
DEVELOPMENT_TEAM = 3F9MDJ6K4E;
INFOPLIST_FILE = MicroBlog_RN/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
@@ -759,7 +727,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 3.6.5;
+ MARKETING_VERSION = 3.7.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -846,10 +814,14 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
+ SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
USE_HERMES = true;
};
@@ -914,9 +886,13 @@
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
+ SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
diff --git a/ios/MicroBlog_RN/AppDelegate.swift b/ios/MicroBlog_RN/AppDelegate.swift
index 6f24a43c..d2348814 100644
--- a/ios/MicroBlog_RN/AppDelegate.swift
+++ b/ios/MicroBlog_RN/AppDelegate.swift
@@ -4,27 +4,26 @@
//
// Created by Vincent Ritter on 02/10/2025.
//
-
-
-import UIKit
+internal import Expo
import React
-import React_RCTAppDelegate
import ReactAppDependencyProvider
import RNShareMenu
+import UserNotifications
@main
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
- var reactNativeDelegate: ReactNativeDelegate?
+ var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
- func application(
+ public override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
+ NSLog("PushDebug:didFinishLaunchingWithOptions launchOptions=%@", String(describing: launchOptions))
let delegate = ReactNativeDelegate()
- let factory = RCTReactNativeFactory(delegate: delegate)
+ let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
@@ -38,11 +37,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
launchOptions: launchOptions
)
- return true
+ UNUserNotificationCenter.current().delegate = self
+
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
- func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
- return ShareMenuManager.application(application, open: url, options: options)
+ public override func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
+ return ShareMenuManager.application(application, open: url, options: options) || super.application(application, open: url, options: options)
}
// Required to register for notifications
@@ -51,17 +52,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
// Required for the register event.
- func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ public override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ NSLog("PushDebug:didRegisterForRemoteNotificationsWithDeviceToken")
RNCPushNotificationIOS.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+ public override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+ NSLog("PushDebug:didReceiveRemoteNotification userInfo=%@", String(describing: userInfo))
RNCPushNotificationIOS.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)
}
// Required for the registrationError event.
- func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
+ public override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
RNCPushNotificationIOS.didFailToRegisterForRemoteNotificationsWithError(error)
}
@@ -69,16 +72,42 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
RNCPushNotificationIOS.didReceive(notification)
}
+
+ func userNotificationCenter(
+ _ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse,
+ withCompletionHandler completionHandler: @escaping () -> Void
+ ) {
+ NSLog(
+ "PushDebug:userNotificationCenter:didReceive response action=%@ userInfo=%@",
+ response.actionIdentifier,
+ String(describing: response.notification.request.content.userInfo)
+ )
+ RNCPushNotificationIOS.didReceive(response)
+ completionHandler()
+ }
+
+ func userNotificationCenter(
+ _ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification,
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
+ ) {
+ NSLog(
+ "PushDebug:userNotificationCenter:willPresent userInfo=%@",
+ String(describing: notification.request.content.userInfo)
+ )
+ completionHandler([.badge, .sound, .banner, .list])
+ }
}
-class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
+class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
- self.bundleURL()
+ bridge.bundleURL ?? bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
- RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
+ RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
diff --git a/ios/MicroBlog_RN/Info.plist b/ios/MicroBlog_RN/Info.plist
index 786f5d1e..adc87499 100644
--- a/ios/MicroBlog_RN/Info.plist
+++ b/ios/MicroBlog_RN/Info.plist
@@ -13,7 +13,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- $(PRODUCT_NAME)
+ Micro.blog
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -51,11 +51,13 @@
NSCameraUsageDescription
For taking new photos and posting them.
NSFaceIDUsageDescription
- Allow $(PRODUCT_NAME) to access your Face ID biometric data.
+ Allow Micro.blog to access your Face ID biometric data.
NSLocationWhenInUseUsageDescription
The location may be used when posting photos.
NSPhotoLibraryUsageDescription
Micro.blog can upload photos to your microblog.
+ RCTNewArchEnabled
+
UIBackgroundModes
remote-notification
@@ -79,7 +81,5 @@
UIViewControllerBasedStatusBarAppearance
- UIDesignRequiresCompatibility
-
diff --git a/ios/MicroBlog_RN/MBHighlightingTextManager.h b/ios/MicroBlog_RN/MBHighlightingTextManager.h
deleted file mode 100644
index 896a4c68..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextManager.h
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// MBHighlightingTextManager.h
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-//
-
-#import
-#import
-
-@class MBHighlightingTextView;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MBHighlightingTextManager : RCTViewManager
-
-@property (strong, nonatomic) id textStorage;
-@property (strong, nonatomic) MBHighlightingTextView* textView;
-@property (strong, nonatomic) NSTimer* typingTimer;
-@property (assign) BOOL isTyping;
-
-+ (CGFloat) preferredTimelineFontSize;
-+ (CGFloat) preferredPostingFontSize;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/ios/MicroBlog_RN/MBHighlightingTextManager.m b/ios/MicroBlog_RN/MBHighlightingTextManager.m
deleted file mode 100644
index 0b2be49c..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextManager.m
+++ /dev/null
@@ -1,168 +0,0 @@
-//
-// MBHighlightingTextManager.m
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-//
-
-#import "MBHighlightingTextManager.h"
-
-#import "MBHighlightingTextView.h"
-#import "MBHighlightingTextStorage.h"
-#import
-
-@implementation MBHighlightingTextManager
-
-RCT_EXPORT_MODULE(MBHighlightingTextView)
-
-RCT_EXPORT_VIEW_PROPERTY(onChangeText, RCTBubblingEventBlock)
-RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTBubblingEventBlock)
-
-RCT_CUSTOM_VIEW_PROPERTY(inputAccessoryViewID, NSString, MBHighlightingTextView)
-{
- if (json) {
-// NSString* input_id = [RCTConvert NSString:json];
-// NSLog(@"inputAccessoryViewID = %@", input_id);
- }
-}
-
-RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, MBHighlightingTextView)
-{
- if (json) {
-// NSInteger font_size = [RCTConvert NSInteger:json];
-// NSLog(@"fontSize = %ld", (long)font_size);
- }
-}
-
-RCT_CUSTOM_VIEW_PROPERTY(value, NSString, MBHighlightingTextView)
-{
- if (json) {
- if (!self.isTyping) {
- NSString* new_text = [RCTConvert NSString:json];
- if (![[self.textStorage string] isEqualToString:new_text]) {
- self.textView.text = new_text;
- }
- }
- }
-}
-
-RCT_CUSTOM_VIEW_PROPERTY(selection, NSString, MBHighlightingTextView)
-{
- if (json) {
- NSInteger start_pos = 0;
- NSInteger end_pos = 0;
-
- NSString* s = [RCTConvert NSString:json];
- if (s.length > 0) {
- // start/end separated by a space, e.g. "0 5"
- NSArray* pieces = [s componentsSeparatedByString:@" "];
- start_pos = [[pieces firstObject] integerValue];
- end_pos = [[pieces lastObject] integerValue];
- }
-
- self.textView.selectedRange = NSMakeRange(start_pos, end_pos - start_pos);
- }
-}
-
-RCT_CUSTOM_VIEW_PROPERTY(autoFocus, BOOL, MBHighlightingTextView)
-{
- if (json) {
- BOOL needs_focus = [RCTConvert BOOL:json];
- if (needs_focus) {
- [self.textView becomeFirstResponder];
- }
- }
-}
-
-#pragma mark -
-
-+ (CGFloat) preferredTimelineFontSize
-{
- UIFont* font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
- CGFloat result = font.pointSize;
- return result;
-}
-
-+ (CGFloat) preferredPostingFontSize
-{
- CGFloat scale = 1.2;
- CGFloat fontsize = round ([self preferredTimelineFontSize] * scale);
- return fontsize;
-}
-
-- (UIView *) view
-{
- if (UIAccessibilityIsVoiceOverRunning()) {
- // disable highlighting
- self.textStorage = [[NSTextStorage alloc] init];
- }
- else {
- self.textStorage = [[MBHighlightingTextStorage alloc] init];
- }
-
- // default to screen width
- CGRect r = [UIScreen mainScreen].bounds;
-
- // setup layout and container
- NSLayoutManager* text_layout = [[NSLayoutManager alloc] init];
- CGSize container_size = CGSizeMake (r.size.width, CGFLOAT_MAX);
- NSTextContainer* text_container = [[NSTextContainer alloc] initWithSize:container_size];
- text_container.widthTracksTextView = YES;
- [text_layout addTextContainer:text_container];
- [self.textStorage addLayoutManager:text_layout];
-
- // make the view
- self.textView = [[MBHighlightingTextView alloc] initWithFrame:r textContainer:text_container];
- self.textView.hidden = YES;
- self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.textView.translatesAutoresizingMaskIntoConstraints = NO;
- self.textView.delegate = self;
-
- // background color
- BOOL darkmode = UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
- if (darkmode) {
- self.textView.backgroundColor = [UIColor colorWithRed:0.122 green:0.161 blue:0.216 alpha:1.0];
- }
- else {
- self.textView.backgroundColor = [UIColor whiteColor];
- }
-
- // default text
- NSString* s = @"";
- NSDictionary* attr_info = @{
- NSFontAttributeName: [UIFont systemFontOfSize:[[self class] preferredPostingFontSize]]
- };
- NSAttributedString* attr_s = [[NSAttributedString alloc] initWithString:s attributes:attr_info];
- self.textView.attributedText = attr_s;
- self.textView.textContainerInset = UIEdgeInsetsMake (10, 5, 10, 5);
- self.textView.font = [UIFont systemFontOfSize:[[self class] preferredPostingFontSize]];
- [self.textStorage setAttributedString:attr_s];
-
- return self.textView;
-}
-
-- (void) textViewDidChange:(UITextView *)textView
-{
- // hacky, keep track of whether we're changing text from typing
- self.isTyping = YES;
- [self.typingTimer invalidate];
- self.typingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:NO block:^(NSTimer* timer) {
- self.isTyping = NO;
- }];
-
- // callback to JS
- MBHighlightingTextView* v = (MBHighlightingTextView *)textView;
- [v callTextChanged:textView.text];
-
- // fix height and scrolling
- [v adjustHeight];
-}
-
-- (void) textViewDidChangeSelection:(UITextView *)textView
-{
- MBHighlightingTextView* v = (MBHighlightingTextView *)textView;
- UITextRange* range = textView.selectedTextRange;
- [v callSelectionChanged:range];
-}
-
-@end
diff --git a/ios/MicroBlog_RN/MBHighlightingTextStorage.h b/ios/MicroBlog_RN/MBHighlightingTextStorage.h
deleted file mode 100755
index 5bb8bb19..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextStorage.h
+++ /dev/null
@@ -1,13 +0,0 @@
-//
-// MBHighlightingTextStorage.h
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-// From Micro.blog 2.x iOS code, which was based on TKDHighlightingTextStorage by Max Seelemann.
-//
-
-#import
-
-@interface MBHighlightingTextStorage : NSTextStorage
-
-@end
diff --git a/ios/MicroBlog_RN/MBHighlightingTextStorage.m b/ios/MicroBlog_RN/MBHighlightingTextStorage.m
deleted file mode 100755
index 318eebb4..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextStorage.m
+++ /dev/null
@@ -1,498 +0,0 @@
-//
-// MBHighlightingTextStorage.m
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-// From Micro.blog 2.x iOS code, which was based on TKDHighlightingTextStorage by Max Seelemann.
-//
-
-#import "MBHighlightingTextStorage.h"
-
-#import "MBHighlightingTextManager.h"
-
-@implementation MBHighlightingTextStorage
-{
- NSMutableAttributedString *_imp;
-}
-
-- (id) init
-{
- self = [super init];
- if (self) {
- _imp = [[NSMutableAttributedString alloc] init];
- }
-
- return self;
-}
-
-- (NSString *) string
-{
- return _imp.string;
-}
-
-- (NSString *) accessibilityValue
-{
- return [self string];
-}
-
-- (NSDictionary *) attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
-{
- if (location < _imp.length) {
- return [_imp attributesAtIndex:location effectiveRange:range];
- }
- else {
- // hack to prevent being repeatedly called
- range->location = 0;
- range->length = NSUIntegerMax;
- return nil;
- }
-}
-
-- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)str
-{
- [self beginEditing];
-
- [_imp replaceCharactersInRange:range withString:str];
- [self edited:NSTextStorageEditedCharacters | NSTextStorageEditedAttributes range:range changeInLength:(NSInteger)str.length - (NSInteger)range.length];
-
- [self endEditing];
- [self processAutocompleteAtRange:range];
-}
-
-- (void) setAttributes:(NSDictionary *)attrs range:(NSRange)range
-{
- [self beginEditing];
-
- [_imp setAttributes:attrs range:range];
- [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
-
- [self endEditing];
-}
-
-#pragma mark -
-
-- (void) safe_addAttribute:(NSAttributedStringKey)name value:(id)value range:(NSRange)range;
-{
- NSString* s = self.string;
- if ((range.location + range.length) <= s.length) {
- [self addAttribute:name value:value range:range];
- }
-}
-
-- (void) safe_removeAttribute:(NSAttributedStringKey)name range:(NSRange)range;
-{
- NSString* s = self.string;
- if ((range.location + range.length) <= s.length) {
- [self removeAttribute:name range:range];
- }
-}
-
-#pragma mark -
-
-- (BOOL) isValidUsernameChar:(unichar)c
-{
- return (isalnum (c) || (c == '_'));
-}
-
-- (void) processBold
-{
- UIFont* bold_font = [UIFont boldSystemFontOfSize:[MBHighlightingTextManager preferredTimelineFontSize]];
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_bold = NO;
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if ((c == '*') && (next_c == '*')) {
- if (!is_bold) {
- is_bold = YES;
- current_r.location = i;
- }
- else {
- is_bold = NO;
- current_r.length = i - current_r.location + 2;
- [self safe_addAttribute:NSFontAttributeName value:bold_font range:current_r];
- }
- }
- }
-
- if (is_bold) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSFontAttributeName value:bold_font range:current_r];
- }
-}
-
-- (void) processItalic
-{
- UIFont* italic_font = [UIFont italicSystemFontOfSize:[MBHighlightingTextManager preferredTimelineFontSize]];
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_italic = NO;
- BOOL is_link = NO;
- BOOL is_username = NO;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- unichar prev_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
- if ((i - 1) > 0) {
- prev_c = [self.string characterAtIndex:i - 1];
- }
-
- if (c == '_') {
- if (!is_link && !is_username) {
- if (!is_italic) {
- if ((prev_c == ' ') || (prev_c == '\0')) {
- is_italic = YES;
- current_r.location = i;
- }
- }
- else {
- is_italic = NO;
- current_r.length = i - current_r.location + 1;
- [self safe_addAttribute:NSFontAttributeName value:italic_font range:current_r];
- }
- }
- }
- else if (c == '[') {
- if (!is_link) {
- is_link = YES;
- }
- }
- else if (c == ')') {
- if (is_link) {
- is_link = NO;
- }
- }
- else if (c == ')') {
- if (is_link) {
- is_link = NO;
- }
- }
- else if ((c == '@') && [self isValidUsernameChar:next_c]) {
- if (!is_username) {
- is_username = YES;
- }
- }
- else if (![self isValidUsernameChar:c]) {
- if (is_username) {
- is_username = NO;
- }
- }
- }
-
- if (is_italic) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSFontAttributeName value:italic_font range:current_r];
- }
-}
-
-- (void) processBlockquote
-{
- UIColor* blockquote_c = [UIColor colorWithRed:0.0 green:0.598 blue:0.004 alpha:1.0];
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_blockquote = NO;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if ((i == 0) && (c == '>')) {
- if (!is_blockquote) {
- is_blockquote = YES;
- current_r.location = i;
- }
- }
- else if ((c == '\n') && (next_c == '>')) {
- if (!is_blockquote) {
- is_blockquote = YES;
- current_r.location = i + 1;
- }
- }
- else if ((c == '\n') && (next_c == '\n')) {
- if (is_blockquote) {
- is_blockquote = NO;
- current_r.length = i - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:blockquote_c range:current_r];
- }
- }
- }
-
- if (is_blockquote) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:blockquote_c range:current_r];
- }
-}
-
-- (void) processLinks
-{
- UIColor* title_c = [UIColor colorWithRed:0.2 green:0.478 blue:0.718 alpha:1.0];
- UIColor* url_c = [UIColor colorWithWhite:0.502 alpha:1.0];
-
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_title = NO;
- BOOL is_url = NO;
- BOOL is_inbetween = NO;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if (c == '[') {
- if (!is_title) {
- is_title = YES;
- current_r.location = i;
- }
- }
- else if (c == ']') {
- if (is_title) {
- is_title = NO;
- current_r.length = i - current_r.location + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:title_c range:current_r];
-
- if (next_c == '(') {
- is_inbetween = YES;
- }
- }
- }
- else if (c == '(') {
- if (is_inbetween && !is_url) {
- is_url = YES;
- current_r.location = i;
- }
-
- is_inbetween = NO;
- }
- else if (c == ')') {
- if (is_url) {
- is_url = NO;
- current_r.length = i - current_r.location + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:url_c range:current_r];
- }
- }
- }
-
- if (is_title) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:title_c range:current_r];
- }
- else if (is_url) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:url_c range:current_r];
- }
-}
-
-- (void) processUsernames
-{
- UIColor* username_c = [UIColor colorWithWhite:0.502 alpha:1.0];
-
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_username = NO;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if ((c == '@') && [self isValidUsernameChar:next_c]) {
- if (!is_username) {
- is_username = YES;
- current_r.location = i;
- }
- }
- else if (![self isValidUsernameChar:c]) {
- if (is_username) {
- is_username = NO;
- current_r.length = i - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:username_c range:current_r];
- }
- }
- }
-
- if (is_username) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:username_c range:current_r];
- }
-}
-
-- (void) processHeaders
-{
- UIFont* header_font = [UIFont boldSystemFontOfSize:[MBHighlightingTextManager preferredTimelineFontSize]];
- UIColor* header_c = [UIColor colorNamed:@"color_editing_paragraph"];
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_header = NO;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if ((c == '\n') && (next_c == '#')) {
- if (!is_header) {
- is_header = YES;
- current_r.location = i + 1;
- }
- }
- else if ((i == 0) && (c == '#')) {
- if (!is_header) {
- is_header = YES;
- current_r.location = i;
- }
- }
- else if (c == '\n') {
- if (is_header) {
- is_header = NO;
- current_r.length = i - current_r.location;
- [self safe_addAttribute:NSFontAttributeName value:header_font range:current_r];
- [self safe_addAttribute:NSForegroundColorAttributeName value:header_c range:current_r];
- }
- }
- }
-
- if (is_header) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSFontAttributeName value:header_font range:current_r];
- [self safe_addAttribute:NSForegroundColorAttributeName value:header_c range:current_r];
- }
-}
-
-- (void) processTags
-{
- UIColor* tag_c = [UIColor colorNamed:@"color_syntax_tags"];
- UIColor* attr_c = [UIColor colorWithWhite:0.502 alpha:1.0];
- UIColor* value_c = [UIColor colorWithRed:0.2 green:0.478 blue:0.718 alpha:1.0];
- NSRange current_r = NSMakeRange (0, 0);
- BOOL is_tag = NO;
- BOOL is_attr = NO;
- BOOL is_value = NO;
- BOOL is_quote = NO;
- NSInteger tag_start = 0;
- NSInteger attr_start = 0;
- NSInteger value_start = 0;
-
- for (NSInteger i = 0; i < self.string.length; i++) {
- unichar c = [self.string characterAtIndex:i];
- unichar next_c = '\0';
- if ((i + 1) < self.string.length) {
- next_c = [self.string characterAtIndex:i + 1];
- }
-
- if (c == '<') {
- if (!is_tag) {
- is_tag = YES;
- is_attr = NO;
- is_value = NO;
- attr_start = 0;
- value_start = 0;
- tag_start = i;
- current_r.location = i;
- }
- }
- else if (c == '"') {
- is_quote = !is_quote;
- }
- else if ((c == ' ') && !is_quote) {
- if (is_tag) {
- is_tag = NO;
- is_attr = YES;
- is_value = NO;
- current_r.length = i - current_r.location;
- attr_start = i + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:tag_c range:current_r];
- }
- else if (is_value) {
- is_tag = NO;
- is_attr = YES;
- is_value = NO;
- current_r.location = value_start;
- current_r.length = i - value_start;
- attr_start = i + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:value_c range:current_r];
- }
- }
- else if (c == '=') {
- if (is_attr) {
- is_attr = NO;
- is_value = YES;
- current_r.location = attr_start;
- current_r.length = i - attr_start;
- value_start = i + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:attr_c range:current_r];
- }
- }
- else if ((c == '/') && (next_c == '>')) {
- is_tag = YES;
- is_attr = NO;
- is_value = NO;
- tag_start = i;
- }
- else if (c == '>') {
- if (is_value) {
- is_tag = NO;
- is_attr = YES;
- is_value = NO;
- current_r.location = value_start;
- current_r.length = i - value_start;
- attr_start = i + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:value_c range:current_r];
- }
- else if (is_tag) {
- is_tag = NO;
- current_r.location = tag_start;
- current_r.length = i - tag_start + 1;
- [self safe_addAttribute:NSForegroundColorAttributeName value:tag_c range:current_r];
- }
- }
- }
-
- if (is_tag) {
- current_r.length = self.string.length - current_r.location;
- [self safe_addAttribute:NSForegroundColorAttributeName value:tag_c range:current_r];
- }
-}
-
-- (void) processAutocompleteAtRange:(NSRange)range
-{
-}
-
-- (void) processEditing
-{
- // clear fonts and colors
- NSRange paragraph_r = NSMakeRange (0, self.string.length);
- UIFont* normal_font = [UIFont systemFontOfSize:[MBHighlightingTextManager preferredTimelineFontSize]];
- UIColor* c = [UIColor colorNamed:@"color_editing_paragraph"];
-
- [self safe_removeAttribute:NSForegroundColorAttributeName range:paragraph_r];
- [self safe_removeAttribute:NSFontAttributeName range:paragraph_r];
- [self safe_addAttribute:NSFontAttributeName value:normal_font range:paragraph_r];
- [self safe_addAttribute:NSForegroundColorAttributeName value:c range:paragraph_r];
-
- // update style ranges
- [self processBold];
- [self processItalic];
- [self processBlockquote];
- [self processLinks];
- [self processUsernames];
- [self processHeaders];
- [self processTags];
-
- [super processEditing];
-}
-
-@end
-
diff --git a/ios/MicroBlog_RN/MBHighlightingTextView.h b/ios/MicroBlog_RN/MBHighlightingTextView.h
deleted file mode 100644
index f40bd993..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextView.h
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// MBHighlightingTextView.h
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-//
-
-#import
-#import
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface MBHighlightingTextView : UITextView
-
-@property (copy, nonatomic) RCTBubblingEventBlock onChangeText;
-@property (copy, nonatomic) RCTBubblingEventBlock onSelectionChange;
-@property (strong, nonatomic) UIView* reactAccessoryView;
-@property (assign, nonatomic) CGFloat keyboardHeight;
-
-- (void) callTextChanged:(NSString *)text;
-- (void) callSelectionChanged:(UITextRange *)range;
-
-- (void) adjustHeight;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/ios/MicroBlog_RN/MBHighlightingTextView.m b/ios/MicroBlog_RN/MBHighlightingTextView.m
deleted file mode 100644
index 6dfdd10a..00000000
--- a/ios/MicroBlog_RN/MBHighlightingTextView.m
+++ /dev/null
@@ -1,218 +0,0 @@
-//
-// MBHighlightingTextView.m
-// MicroBlog_RN
-//
-// Created by Manton Reece on 4/16/23.
-//
-
-#import "MBHighlightingTextView.h"
-
-#import
-
-@implementation MBHighlightingTextView
-
-- (id) init
-{
- self = [super init];
- if (self) {
- self.keyboardHeight = 0;
- }
-
- return self;
-}
-
-- (void) setupNotifications
-{
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
-}
-
-- (void) setupAccessoryView
-{
- UIView* root = [self findRootView];
- UIView* v = [self findAccessoryViewFromView:root withNativeID:@"input_toolbar"];
- if (v) {
- self.reactAccessoryView = v;
-
- if (self.inputAccessoryView == nil) {
- self.inputAccessoryView = [self.reactAccessoryView inputAccessoryView];
- [self reloadInputViews];
- }
- }
-}
-
-- (void) adjustHeight
-{
- [self adjustHeightForKeyboardHeight:self.keyboardHeight animated:NO];
-}
-
-- (void) adjustHeightForKeyboardHeight:(CGFloat)keyboardHeight
-{
- [self adjustHeightForKeyboardHeight:keyboardHeight animated:YES];
-}
-
-- (void) adjustHeightForKeyboardHeight:(CGFloat)keyboardHeight animated:(BOOL)animated
-{
- // adjust position and height taking into account other views
- UIView* parent = self.superview;
- if (parent) {
- CGFloat top_views_height = 0;
- CGFloat bottom_views_height = 0;
- CGFloat bottom_safe_inset = 0;
-
- bottom_views_height += self.inputAccessoryView.bounds.size.height;
-
- for (UIView* sibling_v in self.superview.subviews) {
- if ((sibling_v != self) && !sibling_v.hidden) {
- // views with origin < 200 are probably above the text view
- if (sibling_v.frame.origin.y < 200) {
- top_views_height += sibling_v.bounds.size.height;
- }
- else {
- bottom_views_height += sibling_v.bounds.size.height;
- }
- }
- }
-
- bottom_safe_inset = self.window.safeAreaInsets.bottom;
-
- CGRect r = parent.bounds;
- r.origin.y = top_views_height;
- r.size.height = r.size.height - bottom_views_height - bottom_safe_inset - keyboardHeight;
-
- // only set frame and offset if height has changed
- if (self.frame.size.height != r.size.height) {
- self.frame = r;
- [self setContentOffset:CGPointZero animated:animated];
- }
- }
-}
-
-- (void) finishSetup
-{
- [self setupNotifications];
- [self setupAccessoryView];
- [self adjustHeightForKeyboardHeight:0 animated:NO];
-
- self.hidden = NO;
-}
-
-- (void) didSetProps:(NSArray *)changedProps
-{
-}
-
-- (BOOL) isIpad
-{
- return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
-}
-
-- (BOOL) isIpadPortrait
-{
- BOOL is_ipad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
- BOOL is_portrait = (UIDevice.currentDevice.orientation == UIDeviceOrientationPortrait) || (UIDevice.currentDevice.orientation == UIDeviceOrientationPortraitUpsideDown);
-
- return (is_ipad && is_portrait);
-}
-
-- (UIView *) findRootView
-{
- UIView* root = self;
- while ([root superview] != nil) {
- root = [root superview];
- }
-
- return root;
-}
-
-- (UIView *) findAccessoryViewFromView:(UIView *)view withNativeID:(NSString *)nativeID
-{
- UIView* found_view = nil;
-
- // is this the view?
- if ([[view nativeID] isEqualToString:nativeID]) {
- return view;
- }
-
- // look at subviews and call recursively
- NSArray* subs = view.subviews;
- for (UIView* sub in subs) {
- found_view = [self findAccessoryViewFromView:sub withNativeID:nativeID];
- if (found_view) {
- break;
- }
- }
-
- return found_view;
-}
-
-- (void) didMoveToSuperview
-{
- [super didMoveToSuperview];
-
- if (self.superview != nil) {
- // give the other React Native layout time to do whatever it's doing
- [self performSelector:@selector(finishSetup) withObject:nil afterDelay:1.0];
- }
-}
-
-- (void) callTextChanged:(NSString *)text
-{
- if (self.onChangeText) {
- self.onChangeText(@{ @"text": text });
- }
-}
-
-- (void) callSelectionChanged:(UITextRange *)range
-{
- if (self.onSelectionChange) {
- NSInteger start = [self offsetFromPosition:self.beginningOfDocument toPosition:range.start];
- NSInteger end = [self offsetFromPosition:self.beginningOfDocument toPosition:range.end];
-
- self.onSelectionChange(@{
- @"selection": @{
- @"start": @(start),
- @"end": @(end)
- }
- });
- }
-}
-
-#pragma mark -
-
-- (void) keyboardWillShowNotification:(NSNotification*)notification
-{
- CGFloat covered_height = 0.0;
-
- // in iPad portrait, keyboard does not cover anything
- NSDictionary* info = [notification userInfo];
- CGRect kb_r = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
-
- // convert to the same coordinate space as this view (i.e. the modal's content)
- CGRect kb_in_view_r = [self.superview convertRect:kb_r fromView:nil];
-
- // intersecction between modal in its own coordinates
- CGRect modal_r = self.superview.bounds;
- CGRect intersection = CGRectIntersection(modal_r, kb_in_view_r);
-
- // if intersection is non-empty, its height is how much the keyboard covers our content
- covered_height = CGRectIsNull(intersection) ? 0.0 : intersection.size.height;
-
- // remember height for later layout
- self.keyboardHeight = covered_height;
-
- [UIView animateWithDuration:0.3 animations:^{
- [self adjustHeightForKeyboardHeight:self.keyboardHeight];
- [self layoutIfNeeded];
- }];
-}
-
-- (void) keyboardWillHideNotification:(NSNotification*)aNotification
-{
- [UIView animateWithDuration:0.3 animations:^{
- [self adjustHeightForKeyboardHeight:0];
- [self layoutIfNeeded];
- }];
-}
-
-
-@end
diff --git a/ios/MicroBlog_RN/MBLiquidGlassButton.h b/ios/MicroBlog_RN/MBLiquidGlassButton.h
new file mode 100644
index 00000000..c79173e0
--- /dev/null
+++ b/ios/MicroBlog_RN/MBLiquidGlassButton.h
@@ -0,0 +1,23 @@
+//
+// MBLiquidGlassButton.h
+// MicroBlog_RN
+//
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MBLiquidGlassButton : UIView
+
+@property (copy, nonatomic, nullable) NSString* title;
+@property (copy, nonatomic, nullable) NSString* systemImageName;
+@property (copy, nonatomic, nullable) NSString* variant;
+@property (copy, nonatomic, nullable) NSString* buttonAccessibilityLabel;
+@property (strong, nonatomic, nullable) UIColor* foregroundColor;
+@property (strong, nonatomic, nullable) UIColor* baseBackgroundColor;
+@property (copy, nonatomic) RCTBubblingEventBlock onPress;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/MicroBlog_RN/MBLiquidGlassButton.m b/ios/MicroBlog_RN/MBLiquidGlassButton.m
new file mode 100644
index 00000000..89fad870
--- /dev/null
+++ b/ios/MicroBlog_RN/MBLiquidGlassButton.m
@@ -0,0 +1,133 @@
+//
+// MBLiquidGlassButton.m
+// MicroBlog_RN
+//
+
+#import "MBLiquidGlassButton.h"
+
+@interface MBLiquidGlassButton ()
+
+@property (strong, nonatomic) UIButton* button;
+
+@end
+
+@implementation MBLiquidGlassButton
+
+- (instancetype) initWithFrame:(CGRect)frame
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ self.backgroundColor = UIColor.clearColor;
+ self.button = [UIButton buttonWithType:UIButtonTypeSystem];
+ self.button.frame = self.bounds;
+ self.button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [self.button addTarget:self action:@selector(handlePress) forControlEvents:UIControlEventTouchUpInside];
+ [self addSubview:self.button];
+ [self applyConfiguration];
+ }
+
+ return self;
+}
+
+- (void) setTitle:(NSString *)title
+{
+ if ((_title == nil && title == nil) || [_title isEqualToString:title]) {
+ return;
+ }
+
+ _title = [title copy];
+ [self applyConfiguration];
+}
+
+- (void) setSystemImageName:(NSString *)systemImageName
+{
+ if ((_systemImageName == nil && systemImageName == nil) || [_systemImageName isEqualToString:systemImageName]) {
+ return;
+ }
+
+ _systemImageName = [systemImageName copy];
+ [self applyConfiguration];
+}
+
+- (void) setVariant:(NSString *)variant
+{
+ if ((_variant == nil && variant == nil) || [_variant isEqualToString:variant]) {
+ return;
+ }
+
+ _variant = [variant copy];
+ [self applyConfiguration];
+}
+
+- (void) setButtonAccessibilityLabel:(NSString *)buttonAccessibilityLabel
+{
+ _buttonAccessibilityLabel = [buttonAccessibilityLabel copy];
+ self.button.accessibilityLabel = buttonAccessibilityLabel;
+}
+
+- (void) setForegroundColor:(UIColor *)foregroundColor
+{
+ _foregroundColor = foregroundColor;
+ [self applyConfiguration];
+}
+
+- (void) setBaseBackgroundColor:(UIColor *)baseBackgroundColor
+{
+ _baseBackgroundColor = baseBackgroundColor;
+ [self applyConfiguration];
+}
+
+- (UIButtonConfiguration *) buttonConfiguration
+{
+#if defined(__IPHONE_26_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_26_0
+ if (@available(iOS 26.0, *)) {
+ if ([self.variant isEqualToString:@"prominent"]) {
+ return [UIButtonConfiguration prominentGlassButtonConfiguration];
+ }
+
+ if ([self.variant isEqualToString:@"clear"]) {
+ return [UIButtonConfiguration clearGlassButtonConfiguration];
+ }
+
+ if ([self.variant isEqualToString:@"prominentClear"]) {
+ return [UIButtonConfiguration prominentClearGlassButtonConfiguration];
+ }
+
+ return [UIButtonConfiguration glassButtonConfiguration];
+ }
+#endif
+
+ return [UIButtonConfiguration borderedButtonConfiguration];
+}
+
+- (void) applyConfiguration
+{
+ UIButtonConfiguration* configuration = [self buttonConfiguration];
+ configuration.title = self.title;
+ configuration.baseForegroundColor = self.foregroundColor;
+ configuration.baseBackgroundColor = self.baseBackgroundColor;
+ configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;
+ configuration.buttonSize = UIButtonConfigurationSizeMedium;
+
+ if (self.systemImageName.length > 0) {
+ UIImageSymbolConfiguration* symbol_config = [UIImageSymbolConfiguration configurationWithPointSize:17 weight:UIImageSymbolWeightSemibold];
+ configuration.image = [UIImage systemImageNamed:self.systemImageName withConfiguration:symbol_config];
+ configuration.preferredSymbolConfigurationForImage = symbol_config;
+ configuration.contentInsets = NSDirectionalEdgeInsetsMake(0, 0, 0, 0);
+ }
+ else {
+ configuration.contentInsets = NSDirectionalEdgeInsetsMake(7, 14, 7, 14);
+ }
+
+ self.button.configuration = configuration;
+ self.button.accessibilityLabel = self.buttonAccessibilityLabel;
+}
+
+- (void) handlePress
+{
+ if (self.onPress) {
+ self.onPress(@{});
+ }
+}
+
+@end
diff --git a/ios/MicroBlog_RN/MBLiquidGlassButtonManager.m b/ios/MicroBlog_RN/MBLiquidGlassButtonManager.m
new file mode 100644
index 00000000..c5677925
--- /dev/null
+++ b/ios/MicroBlog_RN/MBLiquidGlassButtonManager.m
@@ -0,0 +1,31 @@
+//
+// MBLiquidGlassButtonManager.m
+// MicroBlog_RN
+//
+
+#import
+
+#import "MBLiquidGlassButton.h"
+
+@interface MBLiquidGlassButtonManager : RCTViewManager
+
+@end
+
+@implementation MBLiquidGlassButtonManager
+
+RCT_EXPORT_MODULE(MBLiquidGlassButton)
+
+RCT_EXPORT_VIEW_PROPERTY(title, NSString)
+RCT_EXPORT_VIEW_PROPERTY(systemImageName, NSString)
+RCT_EXPORT_VIEW_PROPERTY(variant, NSString)
+RCT_EXPORT_VIEW_PROPERTY(buttonAccessibilityLabel, NSString)
+RCT_EXPORT_VIEW_PROPERTY(foregroundColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(baseBackgroundColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
+
+- (UIView *) view
+{
+ return [[MBLiquidGlassButton alloc] init];
+}
+
+@end
diff --git a/ios/MicroBlog_RN/MicroBlog_RN.entitlements b/ios/MicroBlog_RN/MicroBlog_RN.entitlements
index 6b91c66c..cbcbe5eb 100644
--- a/ios/MicroBlog_RN/MicroBlog_RN.entitlements
+++ b/ios/MicroBlog_RN/MicroBlog_RN.entitlements
@@ -8,6 +8,10 @@
group.blog.micro.ios
+ com.apple.developer.applesignin
+
+ Default
+
keychain-access-groups
$(AppIdentifierPrefix)blog.micro.ios
diff --git a/ios/MicroBlog_Share/Info.plist b/ios/MicroBlog_Share/Info.plist
index 304f9b6b..af41a85e 100644
--- a/ios/MicroBlog_Share/Info.plist
+++ b/ios/MicroBlog_Share/Info.plist
@@ -55,5 +55,7 @@
+ RCTNewArchEnabled
+
diff --git a/ios/MicroBlog_Share/ReactShareViewController.swift b/ios/MicroBlog_Share/ReactShareViewController.swift
new file mode 100644
index 00000000..c2afc9d8
--- /dev/null
+++ b/ios/MicroBlog_Share/ReactShareViewController.swift
@@ -0,0 +1,174 @@
+//
+// ReactShareViewController.swift
+// MicroBlog_Share
+//
+
+internal import Expo
+import React
+import ReactAppDependencyProvider
+import RNShareMenu
+
+class ReactShareViewController: ShareViewController, ReactShareViewDelegate {
+ private var reactNativeDelegate: ShareReactNativeDelegate?
+ private var reactNativeFactory: ExpoReactNativeFactory?
+ private weak var reactRootView: UIView?
+ private var appliedColorScheme: String?
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ let delegate = ShareReactNativeDelegate()
+ let factory = ExpoReactNativeFactory(delegate: delegate)
+ delegate.dependencyProvider = RCTAppDependencyProvider()
+
+ reactNativeDelegate = delegate
+ reactNativeFactory = factory
+
+ let colorScheme = currentColorScheme()
+ let rootView = factory.rootViewFactory.view(
+ withModuleName: "ShareMenuModuleComponent",
+ initialProperties: ["colorScheme": colorScheme],
+ launchOptions: nil
+ )
+ rootView.backgroundColor = configuredBackgroundColor(for: colorScheme)
+ rootView.overrideUserInterfaceStyle = interfaceStyle(for: colorScheme)
+
+ reactRootView = rootView
+ view = rootView
+ syncColorScheme()
+
+ ShareMenuReactView.attachViewDelegate(self)
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ syncColorScheme()
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ syncColorScheme()
+ }
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle {
+ syncColorScheme()
+ }
+ }
+
+ override func viewDidDisappear(_ animated: Bool) {
+ cancel()
+ ShareMenuReactView.detachViewDelegate()
+
+ super.viewDidDisappear(animated)
+ }
+
+ func loadExtensionContext() -> NSExtensionContext {
+ return extensionContext!
+ }
+
+ func openApp() {
+ openHostApp()
+ }
+
+ func continueInApp(with items: [NSExtensionItem], and extraData: [String:Any]?) {
+ handlePost(items, extraData: extraData)
+ }
+
+ private func configuredBackgroundColor(for colorScheme: String) -> UIColor? {
+ guard let backgroundColorConfig = Bundle.main.infoDictionary?[REACT_SHARE_VIEW_BACKGROUND_COLOR_KEY] as? [String:Any] else {
+ return defaultBackgroundColor(for: colorScheme)
+ }
+
+ if let transparent = backgroundColorConfig[COLOR_TRANSPARENT_KEY] as? Bool, transparent {
+ return nil
+ }
+
+ let red = numberValue(backgroundColorConfig[COLOR_RED_KEY], fallback: 1)
+ let green = numberValue(backgroundColorConfig[COLOR_GREEN_KEY], fallback: 1)
+ let blue = numberValue(backgroundColorConfig[COLOR_BLUE_KEY], fallback: 1)
+ let alpha = numberValue(backgroundColorConfig[COLOR_ALPHA_KEY], fallback: 1)
+
+ if red == 1 && green == 1 && blue == 1 && alpha == 1 {
+ return defaultBackgroundColor(for: colorScheme)
+ }
+
+ return UIColor(red: red, green: green, blue: blue, alpha: alpha)
+ }
+
+ private func currentColorScheme() -> String {
+ return currentInterfaceStyle() == .dark ? "dark" : "light"
+ }
+
+ private func currentInterfaceStyle() -> UIUserInterfaceStyle {
+ let interfaceStyles = [
+ viewIfLoaded?.window?.traitCollection.userInterfaceStyle,
+ traitCollection.userInterfaceStyle,
+ UIScreen.main.traitCollection.userInterfaceStyle,
+ UITraitCollection.current.userInterfaceStyle,
+ viewIfLoaded?.traitCollection.userInterfaceStyle
+ ]
+
+ return interfaceStyles.compactMap { $0 }.first { $0 != .unspecified } ?? .light
+ }
+
+ private func syncColorScheme() {
+ let colorScheme = currentColorScheme()
+
+ guard colorScheme != appliedColorScheme else {
+ return
+ }
+
+ appliedColorScheme = colorScheme
+ reactRootView?.overrideUserInterfaceStyle = interfaceStyle(for: colorScheme)
+ reactRootView?.backgroundColor = configuredBackgroundColor(for: colorScheme)
+ updateReactRootProperties(colorScheme: colorScheme)
+ }
+
+ private func updateReactRootProperties(colorScheme: String) {
+ let properties = ["colorScheme": colorScheme]
+
+ if let rootView = reactRootView as? RCTRootView {
+ rootView.appProperties = properties
+ }
+ else if reactRootView?.responds(to: Selector(("setAppProperties:"))) == true {
+ reactRootView?.setValue(properties, forKey: "appProperties")
+ }
+ }
+
+ private func interfaceStyle(for colorScheme: String) -> UIUserInterfaceStyle {
+ return colorScheme == "dark" ? .dark : .light
+ }
+
+ private func defaultBackgroundColor(for colorScheme: String) -> UIColor {
+ if colorScheme == "dark" {
+ return UIColor(red: 31.0 / 255.0, green: 41.0 / 255.0, blue: 55.0 / 255.0, alpha: 1)
+ }
+
+ return UIColor(red: 1, green: 1, blue: 1, alpha: 1)
+ }
+
+ private func numberValue(_ value: Any?, fallback: CGFloat) -> CGFloat {
+ guard let number = value as? NSNumber else {
+ return fallback
+ }
+
+ return CGFloat(number.floatValue)
+ }
+}
+
+class ShareReactNativeDelegate: ExpoReactNativeFactoryDelegate {
+ override func sourceURL(for bridge: RCTBridge) -> URL? {
+ bridge.bundleURL ?? bundleURL()
+ }
+
+ override func bundleURL() -> URL? {
+#if DEBUG
+ RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.share")
+#else
+ Bundle.main.url(forResource: "main", withExtension: "jsbundle")
+#endif
+ }
+}
diff --git a/ios/Podfile b/ios/Podfile
index b2838b0e..6639765a 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,31 +1,35 @@
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
-# Resolve react_native_pods.rb with node to allow for hoisting
-require Pod::Executable.execute_command('node', ['-p',
- 'require.resolve(
- "react-native/scripts/react_native_pods.rb",
- {paths: [process.argv[1]]},
- )', __dir__]).strip
-
-platform :ios, min_ios_version_supported
-prepare_react_native_project!
+require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
+
+require 'json'
+podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
-linkage = ENV['USE_FRAMEWORKS']
-if linkage != nil
- Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
- use_frameworks! :linkage => linkage.to_sym
+def ccache_enabled?(podfile_properties)
+ return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
+
+ podfile_properties['apple.ccacheEnabled'] == 'true'
end
+ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
+ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true'
+ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true'
+ENV['RCT_HERMES_V1_ENABLED'] ||= '1' if podfile_properties['expo.useHermesV1'] == 'true'
+
+platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
+prepare_react_native_project!
+
target 'MicroBlog_RN' do
use_expo_modules!
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
- config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
+ config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]
else
config_command = [
'node',
'--no-warnings',
'--eval',
- 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
+ 'require(\'expo/bin/autolinking\')',
+ 'expo-modules-autolinking',
'react-native-config',
'--json',
'--platform',
@@ -35,10 +39,15 @@ target 'MicroBlog_RN' do
config = use_native_modules!(config_command)
+ use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
+ use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
+
use_react_native!(
:path => config[:reactNativePath],
+ :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
# An absolute path to your application root.
- :app_path => "#{Pod::Config.instance.installation_root}/.."
+ :app_path => "#{Pod::Config.instance.installation_root}/..",
+ :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
)
target 'MicroBlog_Share' do
@@ -46,13 +55,13 @@ target 'MicroBlog_RN' do
end
post_install do |installer|
- # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
- :mac_catalyst_enabled => false
+ :mac_catalyst_enabled => false,
+ :ccache_enabled => ccache_enabled?(podfile_properties),
)
-
+
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 092cfdfb..9a831b73 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,22 +1,17 @@
PODS:
- - boost (1.84.0)
- - DoubleConversion (1.1.6)
- - EXConstants (17.1.7):
+ - EXConstants (55.0.16):
- ExpoModulesCore
- - Expo (53.0.24):
- - DoubleConversion
+ - Expo (55.0.27):
- ExpoModulesCore
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -29,35 +24,38 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - ExpoAsset (11.1.7):
+ - ExpoAsset (55.0.17):
+ - ExpoModulesCore
+ - ExpoDomWebView (55.0.6):
- ExpoModulesCore
- - ExpoFileSystem (18.1.11):
+ - ExpoFileSystem (55.0.23):
- ExpoModulesCore
- - ExpoFont (13.3.2):
+ - ExpoFont (55.0.8):
- ExpoModulesCore
- - ExpoImage (2.4.1):
+ - ExpoImage (55.0.11):
- ExpoModulesCore
- libavif/libdav1d
- SDWebImage (~> 5.21.0)
- SDWebImageAVIFCoder (~> 0.11.0)
- SDWebImageSVGCoder (~> 1.7.0)
- SDWebImageWebPCoder (~> 0.14.6)
- - ExpoKeepAwake (14.1.4):
+ - ExpoKeepAwake (55.0.8):
- ExpoModulesCore
- - ExpoModulesCore (2.5.0):
- - DoubleConversion
- - glog
+ - ExpoLogBox (55.0.12):
+ - React-Core
+ - ExpoModulesCore (55.0.25):
+ - ExpoModulesJSI
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-jsinspector
@@ -69,20 +67,24 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - RNWorklets
- Yoga
- - ExpoSecureStore (14.2.4):
+ - ExpoModulesJSI (55.0.25):
+ - hermes-engine
+ - React-Core
+ - React-runtimescheduler
+ - ReactCommon
+ - ExpoSecureStore (55.0.15):
- ExpoModulesCore
- - ExpoWebBrowser (14.2.0):
+ - ExpoWebBrowser (55.0.17):
- ExpoModulesCore
- - fast_float (6.1.4)
- - FBLazyVector (0.79.7)
- - fmt (11.0.2)
- - glog (0.3.5)
- - hermes-engine (0.79.7):
- - hermes-engine/Pre-built (= 0.79.7)
- - hermes-engine/Pre-built (0.79.7)
- - libavif/core (0.11.1)
- - libavif/libdav1d (0.11.1):
+ - FBLazyVector (0.83.6)
+ - hermes-engine (0.14.1):
+ - hermes-engine/Pre-built (= 0.14.1)
+ - hermes-engine/Pre-built (0.14.1)
+ - libavif/core (1.0.0)
+ - libavif/libdav1d (1.0.0):
- libavif/core
- libdav1d (>= 0.6.0)
- libdav1d (1.2.0)
@@ -98,68 +100,54 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- - RCT-Folly (2024.11.18.00):
- - boost
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - RCT-Folly/Default (= 2024.11.18.00)
- - RCT-Folly/Default (2024.11.18.00):
- - boost
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - RCT-Folly/Fabric (2024.11.18.00):
- - boost
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - RCTDeprecation (0.79.7)
- - RCTRequired (0.79.7)
- - RCTTypeSafety (0.79.7):
- - FBLazyVector (= 0.79.7)
- - RCTRequired (= 0.79.7)
- - React-Core (= 0.79.7)
- - React (0.79.7):
- - React-Core (= 0.79.7)
- - React-Core/DevSupport (= 0.79.7)
- - React-Core/RCTWebSocket (= 0.79.7)
- - React-RCTActionSheet (= 0.79.7)
- - React-RCTAnimation (= 0.79.7)
- - React-RCTBlob (= 0.79.7)
- - React-RCTImage (= 0.79.7)
- - React-RCTLinking (= 0.79.7)
- - React-RCTNetwork (= 0.79.7)
- - React-RCTSettings (= 0.79.7)
- - React-RCTText (= 0.79.7)
- - React-RCTVibration (= 0.79.7)
- - React-callinvoker (0.79.7)
- - React-Core (0.79.7):
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
+ - RCTDeprecation (0.83.6)
+ - RCTRequired (0.83.6)
+ - RCTSwiftUI (0.83.6)
+ - RCTSwiftUIWrapper (0.83.6):
+ - RCTSwiftUI
+ - RCTTypeSafety (0.83.6):
+ - FBLazyVector (= 0.83.6)
+ - RCTRequired (= 0.83.6)
+ - React-Core (= 0.83.6)
+ - React (0.83.6):
+ - React-Core (= 0.83.6)
+ - React-Core/DevSupport (= 0.83.6)
+ - React-Core/RCTWebSocket (= 0.83.6)
+ - React-RCTActionSheet (= 0.83.6)
+ - React-RCTAnimation (= 0.83.6)
+ - React-RCTBlob (= 0.83.6)
+ - React-RCTImage (= 0.83.6)
+ - React-RCTLinking (= 0.83.6)
+ - React-RCTNetwork (= 0.83.6)
+ - React-RCTSettings (= 0.83.6)
+ - React-RCTText (= 0.83.6)
+ - React-RCTVibration (= 0.83.6)
+ - React-callinvoker (0.83.6)
+ - React-Core (0.83.6):
+ - hermes-engine
- RCTDeprecation
- - React-Core/Default (= 0.79.7)
+ - React-Core-prebuilt
+ - React-Core/Default (= 0.83.6)
- React-cxxreact
- React-featureflags
- React-hermes
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/CoreModulesHeaders (0.79.7):
- - glog
+ - React-Core-prebuilt (0.83.6):
+ - ReactNativeDependencies
+ - React-Core/CoreModulesHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -167,53 +155,56 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/Default (0.79.7):
- - glog
+ - React-Core/Default (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-cxxreact
- React-featureflags
- React-hermes
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/DevSupport (0.79.7):
- - glog
+ - React-Core/DevSupport (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
- - React-Core/Default (= 0.79.7)
- - React-Core/RCTWebSocket (= 0.79.7)
+ - React-Core-prebuilt
+ - React-Core/Default (= 0.83.6)
+ - React-Core/RCTWebSocket (= 0.83.6)
- React-cxxreact
- React-featureflags
- React-hermes
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTActionSheetHeaders (0.79.7):
- - glog
+ - React-Core/RCTActionSheetHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -221,17 +212,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTAnimationHeaders (0.79.7):
- - glog
+ - React-Core/RCTAnimationHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -239,17 +231,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTBlobHeaders (0.79.7):
- - glog
+ - React-Core/RCTBlobHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -257,17 +250,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTImageHeaders (0.79.7):
- - glog
+ - React-Core/RCTImageHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -275,17 +269,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTLinkingHeaders (0.79.7):
- - glog
+ - React-Core/RCTLinkingHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -293,17 +288,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTNetworkHeaders (0.79.7):
- - glog
+ - React-Core/RCTNetworkHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -311,17 +307,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTSettingsHeaders (0.79.7):
- - glog
+ - React-Core/RCTSettingsHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -329,17 +326,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTTextHeaders (0.79.7):
- - glog
+ - React-Core/RCTTextHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -347,17 +345,18 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTVibrationHeaders (0.79.7):
- - glog
+ - React-Core/RCTVibrationHeaders (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
+ - React-Core-prebuilt
- React-Core/Default
- React-cxxreact
- React-featureflags
@@ -365,1080 +364,1150 @@ PODS:
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-Core/RCTWebSocket (0.79.7):
- - glog
+ - React-Core/RCTWebSocket (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTDeprecation
- - React-Core/Default (= 0.79.7)
+ - React-Core-prebuilt
+ - React-Core/Default (= 0.83.6)
- React-cxxreact
- React-featureflags
- React-hermes
- React-jsi
- React-jsiexecutor
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsitooling
- React-perflogger
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - SocketRocket (= 0.7.1)
+ - ReactNativeDependencies
- Yoga
- - React-CoreModules (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - RCT-Folly (= 2024.11.18.00)
- - RCTTypeSafety (= 0.79.7)
- - React-Core/CoreModulesHeaders (= 0.79.7)
- - React-jsi (= 0.79.7)
+ - React-CoreModules (0.83.6):
+ - RCTTypeSafety (= 0.83.6)
+ - React-Core-prebuilt
+ - React-Core/CoreModulesHeaders (= 0.83.6)
+ - React-debug
+ - React-jsi (= 0.83.6)
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- React-NativeModulesApple
- React-RCTBlob
- React-RCTFBReactNativeSpec
- - React-RCTImage (= 0.79.7)
+ - React-RCTImage (= 0.83.6)
+ - React-runtimeexecutor
+ - React-utils
- ReactCommon
- - SocketRocket (= 0.7.1)
- - React-cxxreact (0.79.7):
- - boost
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-callinvoker (= 0.79.7)
- - React-debug (= 0.79.7)
- - React-jsi (= 0.79.7)
+ - ReactNativeDependencies
+ - React-cxxreact (0.83.6):
+ - hermes-engine
+ - React-callinvoker (= 0.83.6)
+ - React-Core-prebuilt
+ - React-debug (= 0.83.6)
+ - React-jsi (= 0.83.6)
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- - React-logger (= 0.79.7)
- - React-perflogger (= 0.79.7)
- - React-runtimeexecutor (= 0.79.7)
- - React-timing (= 0.79.7)
- - React-debug (0.79.7)
- - React-defaultsnativemodule (0.79.7):
- - hermes-engine
- - RCT-Folly
+ - React-logger (= 0.83.6)
+ - React-perflogger (= 0.83.6)
+ - React-runtimeexecutor
+ - React-timing (= 0.83.6)
+ - React-utils
+ - ReactNativeDependencies
+ - React-debug (0.83.6)
+ - React-defaultsnativemodule (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
- React-domnativemodule
+ - React-featureflags
- React-featureflagsnativemodule
- - React-hermes
- React-idlecallbacksnativemodule
+ - React-intersectionobservernativemodule
- React-jsi
- React-jsiexecutor
- React-microtasksnativemodule
- React-RCTFBReactNativeSpec
- - React-domnativemodule (0.79.7):
+ - React-webperformancenativemodule
+ - ReactNativeDependencies
+ - Yoga
+ - React-domnativemodule (0.83.6):
- hermes-engine
- - RCT-Folly
+ - React-Core-prebuilt
- React-Fabric
+ - React-Fabric/bridging
- React-FabricComponents
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-RCTFBReactNativeSpec
+ - React-runtimeexecutor
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-Fabric (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-Fabric (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- - React-Fabric/animations (= 0.79.7)
- - React-Fabric/attributedstring (= 0.79.7)
- - React-Fabric/componentregistry (= 0.79.7)
- - React-Fabric/componentregistrynative (= 0.79.7)
- - React-Fabric/components (= 0.79.7)
- - React-Fabric/consistency (= 0.79.7)
- - React-Fabric/core (= 0.79.7)
- - React-Fabric/dom (= 0.79.7)
- - React-Fabric/imagemanager (= 0.79.7)
- - React-Fabric/leakchecker (= 0.79.7)
- - React-Fabric/mounting (= 0.79.7)
- - React-Fabric/observers (= 0.79.7)
- - React-Fabric/scheduler (= 0.79.7)
- - React-Fabric/telemetry (= 0.79.7)
- - React-Fabric/templateprocessor (= 0.79.7)
- - React-Fabric/uimanager (= 0.79.7)
+ - React-Fabric/animated (= 0.83.6)
+ - React-Fabric/animationbackend (= 0.83.6)
+ - React-Fabric/animations (= 0.83.6)
+ - React-Fabric/attributedstring (= 0.83.6)
+ - React-Fabric/bridging (= 0.83.6)
+ - React-Fabric/componentregistry (= 0.83.6)
+ - React-Fabric/componentregistrynative (= 0.83.6)
+ - React-Fabric/components (= 0.83.6)
+ - React-Fabric/consistency (= 0.83.6)
+ - React-Fabric/core (= 0.83.6)
+ - React-Fabric/dom (= 0.83.6)
+ - React-Fabric/imagemanager (= 0.83.6)
+ - React-Fabric/leakchecker (= 0.83.6)
+ - React-Fabric/mounting (= 0.83.6)
+ - React-Fabric/observers (= 0.83.6)
+ - React-Fabric/scheduler (= 0.83.6)
+ - React-Fabric/telemetry (= 0.83.6)
+ - React-Fabric/templateprocessor (= 0.83.6)
+ - React-Fabric/uimanager (= 0.83.6)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/animations (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/animated (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/attributedstring (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/animationbackend (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/componentregistry (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/animations (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/componentregistrynative (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/attributedstring (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/components (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/bridging (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- - React-Fabric/components/legacyviewmanagerinterop (= 0.79.7)
- - React-Fabric/components/root (= 0.79.7)
- - React-Fabric/components/scrollview (= 0.79.7)
- - React-Fabric/components/view (= 0.79.7)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/components/legacyviewmanagerinterop (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/componentregistry (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/components/root (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/componentregistrynative (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/components/scrollview (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/components (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
+ - React-Fabric/components/legacyviewmanagerinterop (= 0.83.6)
+ - React-Fabric/components/root (= 0.83.6)
+ - React-Fabric/components/scrollview (= 0.83.6)
+ - React-Fabric/components/view (= 0.83.6)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/components/view (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/components/legacyviewmanagerinterop (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-rendererdebug
+ - React-runtimeexecutor
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - React-Fabric/components/root (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-rendererdebug
+ - React-runtimeexecutor
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - React-Fabric/components/scrollview (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-rendererdebug
+ - React-runtimeexecutor
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - React-Fabric/components/view (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-renderercss
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-Fabric/consistency (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-Fabric/consistency (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/core (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/core (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/dom (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/dom (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/imagemanager (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/imagemanager (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/leakchecker (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/leakchecker (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/mounting (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/mounting (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - React-Fabric/observers (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-Fabric/observers/events (= 0.83.6)
+ - React-Fabric/observers/intersection (= 0.83.6)
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/observers (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/observers/events (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- - React-Fabric/observers/events (= 0.79.7)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/observers/events (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/observers/intersection (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/scheduler (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/scheduler (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric/observers/events
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-performancecdpmetrics
- React-performancetimeline
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/telemetry (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/telemetry (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/templateprocessor (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/templateprocessor (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/uimanager (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/uimanager (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- - React-Fabric/uimanager/consistency (= 0.79.7)
+ - React-Fabric/uimanager/consistency (= 0.83.6)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererconsistency
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-Fabric/uimanager/consistency (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-Fabric/uimanager/consistency (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
- React-rendererconsistency
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
- - React-FabricComponents (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-FabricComponents (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- - React-FabricComponents/components (= 0.79.7)
- - React-FabricComponents/textlayoutmanager (= 0.79.7)
+ - React-FabricComponents/components (= 0.83.6)
+ - React-FabricComponents/textlayoutmanager (= 0.83.6)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- - React-FabricComponents/components/inputaccessory (= 0.79.7)
- - React-FabricComponents/components/iostextinput (= 0.79.7)
- - React-FabricComponents/components/modal (= 0.79.7)
- - React-FabricComponents/components/rncore (= 0.79.7)
- - React-FabricComponents/components/safeareaview (= 0.79.7)
- - React-FabricComponents/components/scrollview (= 0.79.7)
- - React-FabricComponents/components/text (= 0.79.7)
- - React-FabricComponents/components/textinput (= 0.79.7)
- - React-FabricComponents/components/unimplementedview (= 0.79.7)
+ - React-FabricComponents/components/inputaccessory (= 0.83.6)
+ - React-FabricComponents/components/iostextinput (= 0.83.6)
+ - React-FabricComponents/components/modal (= 0.83.6)
+ - React-FabricComponents/components/rncore (= 0.83.6)
+ - React-FabricComponents/components/safeareaview (= 0.83.6)
+ - React-FabricComponents/components/scrollview (= 0.83.6)
+ - React-FabricComponents/components/switch (= 0.83.6)
+ - React-FabricComponents/components/text (= 0.83.6)
+ - React-FabricComponents/components/textinput (= 0.83.6)
+ - React-FabricComponents/components/unimplementedview (= 0.83.6)
+ - React-FabricComponents/components/virtualview (= 0.83.6)
+ - React-FabricComponents/components/virtualviewexperimental (= 0.83.6)
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/inputaccessory (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/inputaccessory (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/iostextinput (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/iostextinput (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/modal (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/modal (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/rncore (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/rncore (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/safeareaview (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/safeareaview (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/scrollview (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/scrollview (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/text (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/switch (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/textinput (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/text (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/components/unimplementedview (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/textinput (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricComponents/textlayoutmanager (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/unimplementedview (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-logger
+ - React-RCTFBReactNativeSpec
- React-rendererdebug
- React-runtimescheduler
- React-utils
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-FabricImage (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - React-FabricComponents/components/virtualview (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- - RCTRequired (= 0.79.7)
- - RCTTypeSafety (= 0.79.7)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-RCTFBReactNativeSpec
+ - React-rendererdebug
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
+ - React-FabricComponents/components/virtualviewexperimental (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-RCTFBReactNativeSpec
+ - React-rendererdebug
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
+ - React-FabricComponents/textlayoutmanager (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-logger
+ - React-RCTFBReactNativeSpec
+ - React-rendererdebug
+ - React-runtimescheduler
+ - React-utils
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
+ - React-FabricImage (0.83.6):
+ - hermes-engine
+ - RCTRequired (= 0.83.6)
+ - RCTTypeSafety (= 0.83.6)
+ - React-Core-prebuilt
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- - React-jsiexecutor (= 0.79.7)
+ - React-jsiexecutor (= 0.83.6)
- React-logger
- React-rendererdebug
- React-utils
- ReactCommon
+ - ReactNativeDependencies
- Yoga
- - React-featureflags (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
- - React-featureflagsnativemodule (0.79.7):
+ - React-featureflags (0.83.6):
+ - React-Core-prebuilt
+ - ReactNativeDependencies
+ - React-featureflagsnativemodule (0.83.6):
- hermes-engine
- - RCT-Folly
+ - React-Core-prebuilt
- React-featureflags
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- - React-graphics (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-graphics (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- - React-hermes
+ - React-Core-prebuilt
- React-jsi
- React-jsiexecutor
- React-utils
- - React-hermes (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
+ - ReactNativeDependencies
+ - React-hermes (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-cxxreact (= 0.79.7)
+ - React-Core-prebuilt
+ - React-cxxreact (= 0.83.6)
- React-jsi
- - React-jsiexecutor (= 0.79.7)
+ - React-jsiexecutor (= 0.83.6)
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- - React-perflogger (= 0.79.7)
+ - React-oscompat
+ - React-perflogger (= 0.83.6)
- React-runtimeexecutor
- - React-idlecallbacksnativemodule (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-idlecallbacksnativemodule (0.83.6):
- hermes-engine
- - RCT-Folly
- - React-hermes
+ - React-Core-prebuilt
- React-jsi
- React-jsiexecutor
- React-RCTFBReactNativeSpec
+ - React-runtimeexecutor
- React-runtimescheduler
- ReactCommon/turbomodule/core
- - React-ImageManager (0.79.7):
- - glog
- - RCT-Folly/Fabric
+ - ReactNativeDependencies
+ - React-ImageManager (0.83.6):
+ - React-Core-prebuilt
- React-Core/Default
- React-debug
- React-Fabric
- React-graphics
- React-rendererdebug
- React-utils
- - React-jserrorhandler (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-intersectionobservernativemodule (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-Fabric
+ - React-Fabric/bridging
+ - React-graphics
+ - React-jsi
+ - React-jsiexecutor
+ - React-RCTFBReactNativeSpec
+ - React-runtimeexecutor
+ - React-runtimescheduler
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
+ - React-jserrorhandler (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- React-jsi
- ReactCommon/turbomodule/bridging
- - React-jsi (0.79.7):
- - boost
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-jsiexecutor (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-cxxreact (= 0.79.7)
- - React-jsi (= 0.79.7)
+ - ReactNativeDependencies
+ - React-jsi (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - ReactNativeDependencies
+ - React-jsiexecutor (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-debug
+ - React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- - React-perflogger (= 0.79.7)
- - React-jsinspector (0.79.7):
- - DoubleConversion
- - glog
+ - React-perflogger
+ - React-runtimeexecutor
+ - React-utils
+ - ReactNativeDependencies
+ - React-jsinspector (0.83.6):
- hermes-engine
- - RCT-Folly
+ - React-Core-prebuilt
- React-featureflags
- React-jsi
+ - React-jsinspectorcdp
+ - React-jsinspectornetwork
- React-jsinspectortracing
- - React-perflogger (= 0.79.7)
- - React-runtimeexecutor (= 0.79.7)
- - React-jsinspectortracing (0.79.7):
- - RCT-Folly
- React-oscompat
- - React-jsitooling (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - RCT-Folly (= 2024.11.18.00)
- - React-cxxreact (= 0.79.7)
- - React-jsi (= 0.79.7)
+ - React-perflogger (= 0.83.6)
+ - React-runtimeexecutor
+ - React-utils
+ - ReactNativeDependencies
+ - React-jsinspectorcdp (0.83.6):
+ - React-Core-prebuilt
+ - ReactNativeDependencies
+ - React-jsinspectornetwork (0.83.6):
+ - React-Core-prebuilt
+ - React-jsinspectorcdp
+ - ReactNativeDependencies
+ - React-jsinspectortracing (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - React-jsi
+ - React-jsinspectornetwork
+ - React-oscompat
+ - React-timing
+ - React-utils
+ - ReactNativeDependencies
+ - React-jsitooling (0.83.6):
+ - React-Core-prebuilt
+ - React-cxxreact (= 0.83.6)
+ - React-debug
+ - React-jsi (= 0.83.6)
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- - React-jsitracing (0.79.7):
+ - React-runtimeexecutor
+ - React-utils
+ - ReactNativeDependencies
+ - React-jsitracing (0.83.6):
- React-jsi
- - React-logger (0.79.7):
- - glog
- - React-Mapbuffer (0.79.7):
- - glog
+ - React-logger (0.83.6):
+ - React-Core-prebuilt
+ - ReactNativeDependencies
+ - React-Mapbuffer (0.83.6):
+ - React-Core-prebuilt
- React-debug
- - React-microtasksnativemodule (0.79.7):
+ - ReactNativeDependencies
+ - React-microtasksnativemodule (0.83.6):
- hermes-engine
- - RCT-Folly
- - React-hermes
+ - React-Core-prebuilt
- React-jsi
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- - react-native-context-menu-view (1.20.0):
+ - ReactNativeDependencies
+ - react-native-context-menu-view (1.21.0):
- React
- react-native-cookies (6.2.1):
- React-Core
- react-native-document-picker (10.1.7):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1449,20 +1518,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- react-native-image-picker (7.2.3):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1473,23 +1540,21 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-keyboard-controller (1.20.1):
- - DoubleConversion
- - glog
+ - react-native-keyboard-controller (1.20.7):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- - react-native-keyboard-controller/common (= 1.20.1)
+ - react-native-keyboard-controller/common (= 1.20.7)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@@ -1498,20 +1563,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-keyboard-controller/common (1.20.1):
- - DoubleConversion
- - glog
+ - react-native-keyboard-controller/common (1.20.7):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1522,20 +1585,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-menu (1.2.4):
- - DoubleConversion
- - glog
+ - react-native-menu (2.0.0):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1546,20 +1607,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- react-native-safe-area-context (5.6.2):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.6.2)
@@ -1572,20 +1631,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- react-native-safe-area-context/common (5.6.2):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1596,20 +1653,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- react-native-safe-area-context/fabric (5.6.2):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common
@@ -1621,24 +1676,20 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-sensitive-info (5.5.8):
- - React
- react-native-sfsymbols (1.2.2):
- React
- react-native-simple-toast (3.3.2):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1649,24 +1700,22 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Toast (~> 4)
- Yoga
- - react-native-video (6.18.0):
- - DoubleConversion
- - glog
+ - react-native-video (6.19.2):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- - react-native-video/Video (= 6.18.0)
+ - react-native-video/Video (= 6.19.2)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@@ -1675,20 +1724,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-video/Fabric (6.18.0):
- - DoubleConversion
- - glog
+ - react-native-video/Fabric (6.19.2):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1699,20 +1746,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - react-native-video/Video (6.18.0):
- - DoubleConversion
- - glog
+ - react-native-video/Video (6.19.2):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- react-native-video/Fabric
@@ -1724,20 +1769,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- react-native-webview (13.16.0):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -1748,47 +1791,68 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - React-NativeModulesApple (0.79.7):
- - glog
+ - React-NativeModulesApple (0.83.6):
- hermes-engine
- React-callinvoker
- React-Core
+ - React-Core-prebuilt
- React-cxxreact
+ - React-debug
- React-featureflags
- - React-hermes
- React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-runtimeexecutor
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - React-oscompat (0.79.7)
- - React-perflogger (0.79.7):
- - DoubleConversion
- - RCT-Folly (= 2024.11.18.00)
- - React-performancetimeline (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
- - React-cxxreact
+ - ReactNativeDependencies
+ - React-networking (0.83.6):
+ - React-Core-prebuilt
+ - React-featureflags
+ - React-jsinspectornetwork
+ - React-jsinspectortracing
+ - React-performancetimeline
+ - React-timing
+ - ReactNativeDependencies
+ - React-oscompat (0.83.6)
+ - React-perflogger (0.83.6):
+ - React-Core-prebuilt
+ - ReactNativeDependencies
+ - React-performancecdpmetrics (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - React-jsi
+ - React-performancetimeline
+ - React-runtimeexecutor
+ - React-timing
+ - ReactNativeDependencies
+ - React-performancetimeline (0.83.6):
+ - React-Core-prebuilt
- React-featureflags
- React-jsinspectortracing
- React-perflogger
- React-timing
- - React-RCTActionSheet (0.79.7):
- - React-Core/RCTActionSheetHeaders (= 0.79.7)
- - React-RCTAnimation (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
+ - ReactNativeDependencies
+ - React-RCTActionSheet (0.83.6):
+ - React-Core/RCTActionSheetHeaders (= 0.83.6)
+ - React-RCTAnimation (0.83.6):
- RCTTypeSafety
+ - React-Core-prebuilt
- React-Core/RCTAnimationHeaders
+ - React-featureflags
- React-jsi
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- ReactCommon
- - React-RCTAppDelegate (0.79.7):
+ - ReactNativeDependencies
+ - React-RCTAppDelegate (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-CoreModules
- React-debug
- React-defaultsnativemodule
@@ -1806,130 +1870,162 @@ PODS:
- React-rendererdebug
- React-RuntimeApple
- React-RuntimeCore
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
- ReactCommon
- - React-RCTBlob (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
+ - ReactNativeDependencies
+ - React-RCTBlob (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
+ - React-Core-prebuilt
- React-Core/RCTBlobHeaders
- React-Core/RCTWebSocket
- React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- React-RCTNetwork
- ReactCommon
- - React-RCTFabric (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-RCTFabric (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
+ - RCTSwiftUIWrapper
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-FabricComponents
- React-FabricImage
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
+ - React-networking
+ - React-performancecdpmetrics
- React-performancetimeline
- React-RCTAnimation
+ - React-RCTFBReactNativeSpec
- React-RCTImage
- React-RCTText
- React-rendererconsistency
- React-renderercss
- React-rendererdebug
+ - React-runtimeexecutor
- React-runtimescheduler
- React-utils
+ - ReactNativeDependencies
- Yoga
- - React-RCTFBReactNativeSpec (0.79.7):
+ - React-RCTFBReactNativeSpec (0.83.6):
- hermes-engine
- - RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
- - React-hermes
+ - React-Core-prebuilt
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFBReactNativeSpec/components (= 0.83.6)
+ - ReactCommon
+ - ReactNativeDependencies
+ - React-RCTFBReactNativeSpec/components (0.83.6):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
- React-jsi
- - React-jsiexecutor
- React-NativeModulesApple
+ - React-rendererdebug
+ - React-utils
- ReactCommon
- - React-RCTImage (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
+ - ReactNativeDependencies
+ - Yoga
+ - React-RCTImage (0.83.6):
- RCTTypeSafety
+ - React-Core-prebuilt
- React-Core/RCTImageHeaders
- React-jsi
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- React-RCTNetwork
- ReactCommon
- - React-RCTLinking (0.79.7):
- - React-Core/RCTLinkingHeaders (= 0.79.7)
- - React-jsi (= 0.79.7)
+ - ReactNativeDependencies
+ - React-RCTLinking (0.83.6):
+ - React-Core/RCTLinkingHeaders (= 0.83.6)
+ - React-jsi (= 0.83.6)
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- ReactCommon
- - ReactCommon/turbomodule/core (= 0.79.7)
- - React-RCTNetwork (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
+ - ReactCommon/turbomodule/core (= 0.83.6)
+ - React-RCTNetwork (0.83.6):
- RCTTypeSafety
+ - React-Core-prebuilt
- React-Core/RCTNetworkHeaders
+ - React-debug
+ - React-featureflags
- React-jsi
+ - React-jsinspectorcdp
+ - React-jsinspectornetwork
- React-NativeModulesApple
+ - React-networking
- React-RCTFBReactNativeSpec
- ReactCommon
- - React-RCTRuntime (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-RCTRuntime (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- React-Core
- - React-hermes
+ - React-Core-prebuilt
+ - React-debug
- React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- React-jsitooling
- React-RuntimeApple
- React-RuntimeCore
+ - React-runtimeexecutor
- React-RuntimeHermes
- - React-RCTSettings (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
+ - React-utils
+ - ReactNativeDependencies
+ - React-RCTSettings (0.83.6):
- RCTTypeSafety
+ - React-Core-prebuilt
- React-Core/RCTSettingsHeaders
- React-jsi
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- ReactCommon
- - React-RCTText (0.79.7):
- - React-Core/RCTTextHeaders (= 0.79.7)
+ - ReactNativeDependencies
+ - React-RCTText (0.83.6):
+ - React-Core/RCTTextHeaders (= 0.83.6)
- Yoga
- - React-RCTVibration (0.79.7):
- - RCT-Folly (= 2024.11.18.00)
+ - React-RCTVibration (0.83.6):
+ - React-Core-prebuilt
- React-Core/RCTVibrationHeaders
- React-jsi
- React-NativeModulesApple
- React-RCTFBReactNativeSpec
- ReactCommon
- - React-rendererconsistency (0.79.7)
- - React-renderercss (0.79.7):
+ - ReactNativeDependencies
+ - React-rendererconsistency (0.83.6)
+ - React-renderercss (0.83.6):
- React-debug
- React-utils
- - React-rendererdebug (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - RCT-Folly (= 2024.11.18.00)
+ - React-rendererdebug (0.83.6):
+ - React-Core-prebuilt
- React-debug
- - React-rncore (0.79.7)
- - React-RuntimeApple (0.79.7):
+ - ReactNativeDependencies
+ - React-RuntimeApple (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
- React-callinvoker
+ - React-Core-prebuilt
- React-Core/Default
- React-CoreModules
- React-cxxreact
@@ -1948,14 +2044,13 @@ PODS:
- React-RuntimeHermes
- React-runtimescheduler
- React-utils
- - React-RuntimeCore (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-RuntimeCore (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
+ - React-Core-prebuilt
- React-cxxreact
- React-Fabric
- React-featureflags
- - React-hermes
- React-jserrorhandler
- React-jsi
- React-jsiexecutor
@@ -1965,29 +2060,36 @@ PODS:
- React-runtimeexecutor
- React-runtimescheduler
- React-utils
- - React-runtimeexecutor (0.79.7):
- - React-jsi (= 0.79.7)
- - React-RuntimeHermes (0.79.7):
+ - ReactNativeDependencies
+ - React-runtimeexecutor (0.83.6):
+ - React-Core-prebuilt
+ - React-debug
+ - React-featureflags
+ - React-jsi (= 0.83.6)
+ - React-utils
+ - ReactNativeDependencies
+ - React-RuntimeHermes (0.83.6):
- hermes-engine
- - RCT-Folly/Fabric (= 2024.11.18.00)
+ - React-Core-prebuilt
- React-featureflags
- React-hermes
- React-jsi
- React-jsinspector
+ - React-jsinspectorcdp
- React-jsinspectortracing
- React-jsitooling
- React-jsitracing
- React-RuntimeCore
+ - React-runtimeexecutor
- React-utils
- - React-runtimescheduler (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-runtimescheduler (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- React-callinvoker
+ - React-Core-prebuilt
- React-cxxreact
- React-debug
- React-featureflags
- - React-hermes
- React-jsi
- React-jsinspectortracing
- React-performancetimeline
@@ -1996,30 +2098,39 @@ PODS:
- React-runtimeexecutor
- React-timing
- React-utils
- - React-timing (0.79.7)
- - React-utils (0.79.7):
- - glog
+ - ReactNativeDependencies
+ - React-timing (0.83.6):
+ - React-debug
+ - React-utils (0.83.6):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
+ - React-Core-prebuilt
- React-debug
- - React-hermes
- - React-jsi (= 0.79.7)
- - ReactAppDependencyProvider (0.79.7):
+ - React-jsi (= 0.83.6)
+ - ReactNativeDependencies
+ - React-webperformancenativemodule (0.83.6):
+ - hermes-engine
+ - React-Core-prebuilt
+ - React-cxxreact
+ - React-jsi
+ - React-jsiexecutor
+ - React-performancetimeline
+ - React-RCTFBReactNativeSpec
+ - React-runtimeexecutor
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - ReactAppDependencyProvider (0.83.6):
- ReactCodegen
- - ReactCodegen (0.79.7):
- - DoubleConversion
- - glog
+ - ReactCodegen (0.83.6):
- hermes-engine
- - RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-FabricImage
- React-featureflags
- React-graphics
- - React-hermes
- React-jsi
- React-jsiexecutor
- React-NativeModulesApple
@@ -2028,62 +2139,56 @@ PODS:
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - ReactCommon (0.79.7):
- - ReactCommon/turbomodule (= 0.79.7)
- - ReactCommon/turbomodule (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-callinvoker (= 0.79.7)
- - React-cxxreact (= 0.79.7)
- - React-jsi (= 0.79.7)
- - React-logger (= 0.79.7)
- - React-perflogger (= 0.79.7)
- - ReactCommon/turbomodule/bridging (= 0.79.7)
- - ReactCommon/turbomodule/core (= 0.79.7)
- - ReactCommon/turbomodule/bridging (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-callinvoker (= 0.79.7)
- - React-cxxreact (= 0.79.7)
- - React-jsi (= 0.79.7)
- - React-logger (= 0.79.7)
- - React-perflogger (= 0.79.7)
- - ReactCommon/turbomodule/core (0.79.7):
- - DoubleConversion
- - fast_float (= 6.1.4)
- - fmt (= 11.0.2)
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - React-callinvoker (= 0.79.7)
- - React-cxxreact (= 0.79.7)
- - React-debug (= 0.79.7)
- - React-featureflags (= 0.79.7)
- - React-jsi (= 0.79.7)
- - React-logger (= 0.79.7)
- - React-perflogger (= 0.79.7)
- - React-utils (= 0.79.7)
- - RNCAsyncStorage (2.1.2):
- - DoubleConversion
- - glog
- - hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- - RCTRequired
- - RCTTypeSafety
- - React-Core
+ - ReactNativeDependencies
+ - ReactCommon (0.83.6):
+ - React-Core-prebuilt
+ - ReactCommon/turbomodule (= 0.83.6)
+ - ReactNativeDependencies
+ - ReactCommon/turbomodule (0.83.6):
+ - hermes-engine
+ - React-callinvoker (= 0.83.6)
+ - React-Core-prebuilt
+ - React-cxxreact (= 0.83.6)
+ - React-jsi (= 0.83.6)
+ - React-logger (= 0.83.6)
+ - React-perflogger (= 0.83.6)
+ - ReactCommon/turbomodule/bridging (= 0.83.6)
+ - ReactCommon/turbomodule/core (= 0.83.6)
+ - ReactNativeDependencies
+ - ReactCommon/turbomodule/bridging (0.83.6):
+ - hermes-engine
+ - React-callinvoker (= 0.83.6)
+ - React-Core-prebuilt
+ - React-cxxreact (= 0.83.6)
+ - React-jsi (= 0.83.6)
+ - React-logger (= 0.83.6)
+ - React-perflogger (= 0.83.6)
+ - ReactNativeDependencies
+ - ReactCommon/turbomodule/core (0.83.6):
+ - hermes-engine
+ - React-callinvoker (= 0.83.6)
+ - React-Core-prebuilt
+ - React-cxxreact (= 0.83.6)
+ - React-debug (= 0.83.6)
+ - React-featureflags (= 0.83.6)
+ - React-jsi (= 0.83.6)
+ - React-logger (= 0.83.6)
+ - React-perflogger (= 0.83.6)
+ - React-utils (= 0.83.6)
+ - ReactNativeDependencies
+ - ReactNativeDependencies (0.83.6)
+ - RNAppleAuthentication (2.5.1):
+ - React-Core
+ - RNCAsyncStorage (2.2.0):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -2094,20 +2199,18 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- RNCClipboard (1.16.3):
- - DoubleConversion
- - glog
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -2118,26 +2221,24 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - RNCPushNotificationIOS (1.11.0):
+ - RNCPushNotificationIOS (1.12.0):
- React-Core
- RNDeviceInfo (8.7.1):
- React-Core
- RNFS (2.20.0):
- React-Core
- - RNGestureHandler (2.24.0):
- - DoubleConversion
- - glog
+ - RNGestureHandler (2.30.1):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
@@ -2148,15 +2249,14 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - RNReanimated (3.17.5):
- - DoubleConversion
- - glog
+ - RNReanimated (4.2.1):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2172,17 +2272,16 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNReanimated/reanimated (= 3.17.5)
- - RNReanimated/worklets (= 3.17.5)
+ - ReactNativeDependencies
+ - RNReanimated/reanimated (= 4.2.1)
+ - RNWorklets
- Yoga
- - RNReanimated/reanimated (3.17.5):
- - DoubleConversion
- - glog
+ - RNReanimated/reanimated (4.2.1):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2198,16 +2297,16 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNReanimated/reanimated/apple (= 3.17.5)
+ - ReactNativeDependencies
+ - RNReanimated/reanimated/apple (= 4.2.1)
+ - RNWorklets
- Yoga
- - RNReanimated/reanimated/apple (3.17.5):
- - DoubleConversion
- - glog
+ - RNReanimated/reanimated/apple (4.2.1):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2223,90 +2322,109 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - RNWorklets
- Yoga
- - RNReanimated/worklets (3.17.5):
- - DoubleConversion
- - glog
+ - RNScreens (4.23.0):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
+ - React-RCTImage
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNReanimated/worklets/apple (= 3.17.5)
+ - ReactNativeDependencies
+ - RNScreens/common (= 4.23.0)
- Yoga
- - RNReanimated/worklets/apple (3.17.5):
- - DoubleConversion
- - glog
+ - RNScreens/common (4.23.0):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
+ - React-RCTImage
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - RNScreens (4.18.0):
- - DoubleConversion
- - glog
+ - RNShareMenu (6.0.0):
+ - React
+ - RNSVG (15.15.3):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- - React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- - React-RCTImage
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNScreens/common (= 4.18.0)
+ - ReactNativeDependencies
+ - RNSVG/common (= 15.15.3)
- Yoga
- - RNScreens/common (4.18.0):
- - DoubleConversion
- - glog
+ - RNSVG/common (15.15.3):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
+ - RNWorklets (0.7.4):
+ - hermes-engine
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2316,24 +2434,21 @@ PODS:
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- - React-RCTImage
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - RNWorklets/worklets (= 0.7.4)
- Yoga
- - RNShareMenu (6.0.0):
- - React
- - RNSVG (15.11.2):
- - DoubleConversion
- - glog
+ - RNWorklets/worklets (0.7.4):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2349,16 +2464,15 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNSVG/common (= 15.11.2)
+ - ReactNativeDependencies
+ - RNWorklets/worklets/apple (= 0.7.4)
- Yoga
- - RNSVG/common (15.11.2):
- - DoubleConversion
- - glog
+ - RNWorklets/worklets/apple (0.7.4):
- hermes-engine
- - RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
+ - React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
@@ -2374,10 +2488,11 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - ReactNativeDependencies
- Yoga
- - SDWebImage (5.21.2):
- - SDWebImage/Core (= 5.21.2)
- - SDWebImage/Core (5.21.2)
+ - SDWebImage (5.21.7):
+ - SDWebImage/Core (= 5.21.7)
+ - SDWebImage/Core (5.21.7)
- SDWebImageAVIFCoder (0.11.1):
- libavif/core (>= 0.11.0)
- SDWebImage (~> 5.10)
@@ -2386,36 +2501,34 @@ PODS:
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- - SocketRocket (0.7.1)
- Toast (4.1.1)
- Yoga (0.0.0)
DEPENDENCIES:
- - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- Expo (from `../node_modules/expo`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
+ - "ExpoDomWebView (from `../node_modules/@expo/dom-webview/ios`)"
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`)
- ExpoImage (from `../node_modules/expo-image/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
+ - "ExpoLogBox (from `../node_modules/@expo/log-box`)"
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
+ - ExpoModulesJSI (from `../node_modules/expo-modules-core`)
- ExpoSecureStore (from `../node_modules/expo-secure-store/ios`)
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
- - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
+ - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`)
+ - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../node_modules/react-native/`)
+ - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
@@ -2431,10 +2544,13 @@ DEPENDENCIES:
- React-hermes (from `../node_modules/react-native/ReactCommon/hermes`)
- React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)
- React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
+ - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`)
- React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`)
+ - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)
+ - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`)
- React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)
- React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`)
- React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`)
@@ -2448,14 +2564,15 @@ DEPENDENCIES:
- react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`)
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- - react-native-sensitive-info (from `../node_modules/react-native-sensitive-info`)
- react-native-sfsymbols (from `../node_modules/react-native-sfsymbols`)
- react-native-simple-toast (from `../node_modules/react-native-simple-toast`)
- react-native-video (from `../node_modules/react-native-video`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
+ - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
+ - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`)
- React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@@ -2473,7 +2590,6 @@ DEPENDENCIES:
- React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`)
- React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`)
- React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`)
- - React-rncore (from `../node_modules/react-native/ReactCommon`)
- React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`)
- React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
@@ -2481,9 +2597,12 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-timing (from `../node_modules/react-native/ReactCommon/react/timing`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- - ReactAppDependencyProvider (from `build/generated/ios`)
- - ReactCodegen (from `build/generated/ios`)
+ - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`)
+ - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`)
+ - ReactCodegen (from `build/generated/ios/ReactCodegen`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
+ - "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
@@ -2494,6 +2613,7 @@ DEPENDENCIES:
- RNScreens (from `../node_modules/react-native-screens`)
- RNShareMenu (from `../node_modules/react-native-share-menu`)
- RNSVG (from `../node_modules/react-native-svg`)
+ - RNWorklets (from `../node_modules/react-native-worklets`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -2505,20 +2625,17 @@ SPEC REPOS:
- SDWebImageAVIFCoder
- SDWebImageSVGCoder
- SDWebImageWebPCoder
- - SocketRocket
- Toast
EXTERNAL SOURCES:
- boost:
- :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
- DoubleConversion:
- :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EXConstants:
:path: "../node_modules/expo-constants/ios"
Expo:
:path: "../node_modules/expo"
ExpoAsset:
:path: "../node_modules/expo-asset/ios"
+ ExpoDomWebView:
+ :path: "../node_modules/@expo/dom-webview/ios"
ExpoFileSystem:
:path: "../node_modules/expo-file-system/ios"
ExpoFont:
@@ -2527,29 +2644,29 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-image/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
+ ExpoLogBox:
+ :path: "../node_modules/@expo/log-box"
ExpoModulesCore:
:path: "../node_modules/expo-modules-core"
+ ExpoModulesJSI:
+ :path: "../node_modules/expo-modules-core"
ExpoSecureStore:
:path: "../node_modules/expo-secure-store/ios"
ExpoWebBrowser:
:path: "../node_modules/expo-web-browser/ios"
- fast_float:
- :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec"
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector"
- fmt:
- :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec"
- glog:
- :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
- :tag: hermes-2025-06-04-RNv0.79.3-7f9a871eefeb2c3852365ee80f0b6733ec12ac3b
- RCT-Folly:
- :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
+ :tag: hermes-v0.14.1
RCTDeprecation:
:path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
RCTRequired:
:path: "../node_modules/react-native/Libraries/Required"
+ RCTSwiftUI:
+ :path: "../node_modules/react-native/ReactApple/RCTSwiftUI"
+ RCTSwiftUIWrapper:
+ :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper"
RCTTypeSafety:
:path: "../node_modules/react-native/Libraries/TypeSafety"
React:
@@ -2558,6 +2675,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Core:
:path: "../node_modules/react-native/"
+ React-Core-prebuilt:
+ :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec"
React-CoreModules:
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
@@ -2586,6 +2705,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks"
React-ImageManager:
:path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios"
+ React-intersectionobservernativemodule:
+ :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver"
React-jserrorhandler:
:path: "../node_modules/react-native/ReactCommon/jserrorhandler"
React-jsi:
@@ -2594,6 +2715,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern"
+ React-jsinspectorcdp:
+ :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp"
+ React-jsinspectornetwork:
+ :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network"
React-jsinspectortracing:
:path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing"
React-jsitooling:
@@ -2620,8 +2745,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-menu/menu"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
- react-native-sensitive-info:
- :path: "../node_modules/react-native-sensitive-info"
react-native-sfsymbols:
:path: "../node_modules/react-native-sfsymbols"
react-native-simple-toast:
@@ -2632,10 +2755,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-webview"
React-NativeModulesApple:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
+ React-networking:
+ :path: "../node_modules/react-native/ReactCommon/react/networking"
React-oscompat:
:path: "../node_modules/react-native/ReactCommon/oscompat"
React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
+ React-performancecdpmetrics:
+ :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics"
React-performancetimeline:
:path: "../node_modules/react-native/ReactCommon/react/performance/timeline"
React-RCTActionSheet:
@@ -2670,8 +2797,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/renderer/css"
React-rendererdebug:
:path: "../node_modules/react-native/ReactCommon/react/renderer/debug"
- React-rncore:
- :path: "../node_modules/react-native/ReactCommon"
React-RuntimeApple:
:path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios"
React-RuntimeCore:
@@ -2686,12 +2811,18 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/timing"
React-utils:
:path: "../node_modules/react-native/ReactCommon/react/utils"
+ React-webperformancenativemodule:
+ :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance"
ReactAppDependencyProvider:
- :path: build/generated/ios
+ :path: build/generated/ios/ReactAppDependencyProvider
ReactCodegen:
- :path: build/generated/ios
+ :path: build/generated/ios/ReactCodegen
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ ReactNativeDependencies:
+ :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec"
+ RNAppleAuthentication:
+ :path: "../node_modules/@invertase/react-native-apple-authentication"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@@ -2712,122 +2843,130 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-share-menu"
RNSVG:
:path: "../node_modules/react-native-svg"
+ RNWorklets:
+ :path: "../node_modules/react-native-worklets"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
- boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
- DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
- EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
- Expo: d68c92f4bee50a21f0f0d7d2481dd6dbbd51e82b
- ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
- ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
- ExpoFont: cf508bc2e6b70871e05386d71cab927c8524cc8e
- ExpoImage: b0b4a838c62bb9d5438bb475cccc85619a5f59dc
- ExpoKeepAwake: bf0811570c8da182bfb879169437d4de298376e7
- ExpoModulesCore: 00a1b5c73248465bd0b93f59f8538c4573dac579
- ExpoSecureStore: 3f1b632d6d40bcc62b4983ef9199cd079592a50a
- ExpoWebBrowser: dc39a88485f007e61a3dff05d6a75f22ab4a2e92
- fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
- FBLazyVector: b60fe06f0f15b7d7408f169442176e69e8eeacde
- fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
- glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
- hermes-engine: 13c84524b3b6e884b2cf3b3b1e002ffd147d88a3
- libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
+ EXConstants: dadbeba983acc30f855a919658a2b34fbd86615d
+ Expo: b0f96de93d2e9ab7c6eabcf88597b13f6fe3f67e
+ ExpoAsset: 7721c5c4ae2a7c3c8147a046727ac6c020b05648
+ ExpoDomWebView: b2e9d601f9cb77d3c97da73234e175b64be91928
+ ExpoFileSystem: 49ab894dee0818cb8b83d489fc51eafade73efb9
+ ExpoFont: a7e34a75bdfb703fa07a0e87807f836236608c76
+ ExpoImage: 5854bd51edd7c0a775755473598f9daeb3fde4e3
+ ExpoKeepAwake: d0eb7a0719500d2a43fbf29b6f79e84d70c75ebf
+ ExpoLogBox: a678d36477ab9544fe63e0b5328a0716539b6774
+ ExpoModulesCore: 5e1ad569ce7cf8d35c0737585f34cc299fc3f0ca
+ ExpoModulesJSI: 964334271fc77832736f66ee1785761cdc778dbe
+ ExpoSecureStore: f495a3e0ab65683be8427a96f694726a88048f46
+ ExpoWebBrowser: 3f57d6eec40cd76608ee9e660915bc474b7cfa6a
+ FBLazyVector: 427fa1856a481856091061293ce865a75aa4f20f
+ hermes-engine: 005e88ab86167d754a6dd6ccfef17c56bc4682bf
+ libavif: 5f8e715bea24debec477006f21ef9e95432e254d
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
- RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
- RCTDeprecation: b9b1716eec53aaeaa859aa45e27de7b615cff732
- RCTRequired: 862acb469086f601f81aab298e00c2a13d572099
- RCTTypeSafety: 45120b9028ec6819266f3ef7a6b8e8a9f0a083d4
- React: 49e89943d7b1bc95c5895a05c6cf106ba112d037
- React-callinvoker: 9baafac613e363728c47ceaf65b9597bd1b96df0
- React-Core: 2f642fff28911adf30dd5169a7d9d2b95dc59bc9
- React-CoreModules: a499af0e4a8dfa78cbc04c131b59fa3286210148
- React-cxxreact: 1a485bb66f4bdde62f89eb0e5db1262182fc40e0
- React-debug: 40119ee63d9fb04f8b11d460c3c30b5997d9d737
- React-defaultsnativemodule: d578aae775984b79f126a0ffd8481b70c0f145fc
- React-domnativemodule: 2351486bd32ead37c242b00a71cbdff8dabb274c
- React-Fabric: 3e4c67dd7918274fe00fdc34a56696095e4d83f0
- React-FabricComponents: c82dfc3d8e1faf5cd87b08f5f9e2be0fe9b775f3
- React-FabricImage: a296eafd939fb33e0acf09d33239408fe4624582
- React-featureflags: 3fdf55dee69c3e165411fbde4f9f3acb9e428dfd
- React-featureflagsnativemodule: 1139ddc9fc4c703ef9165a50c83cdca710baa674
- React-graphics: 4d1c605d894e9c9a887bb6934d9cca89d2ff36f5
- React-hermes: 59edcac48ec38831b40cf28ed34b6d4d735cbdd3
- React-idlecallbacksnativemodule: 23c6b73801992033086c2e8b9519bbf6aaf84165
- React-ImageManager: 2b2c8c39cc29e55a59dc1a0a9427ec7f89aae02b
- React-jserrorhandler: 7d2f6eb3821818dc914a278f06586f047d623d72
- React-jsi: f6fee849355b8819936b02d5c5b23a55a3d69540
- React-jsiexecutor: cd6fd873482d7183206eb612ce362d02c6fb8556
- React-jsinspector: d2b58ae4a2fa080b3fcc4a5a81dfbd7727654a6d
- React-jsinspectortracing: d3c7d7ec87d24f4c3832ecf141afcbf8844eee80
- React-jsitooling: bfde104a6ea4540830a246c6ffd9f1d07bf677b0
- React-jsitracing: 826dc37b6d98de03e0e64d8d6017b40af766dbf0
- React-logger: fbdc0814b62fefa412a90af7cacc666387f1bc9a
- React-Mapbuffer: cfe4726ba1929b9dab4645c78376a9165375d62f
- React-microtasksnativemodule: c99b8f1240609566de105b73f2569c0f22880ee1
- react-native-context-menu-view: 418877dcac6add4abba8fc2ee92c39f05b763f3a
+ RCTDeprecation: 9502b5eb8e3c9eb75ff5a333b2c591bab9d91e28
+ RCTRequired: 0268a5d86aef1defea2e04289d74fdf3570197c1
+ RCTSwiftUI: cff211c5e4d94e491dda240d663b8effd25296d7
+ RCTSwiftUIWrapper: b731e4d3257895dfcc4bbe2dc3ae24249cd55f6e
+ RCTTypeSafety: c6ed564d0d54c9a01be7db724fbc753eb77b3dc1
+ React: 9b033c7a99e1c8eec40960b6b2e6844e011e8bd1
+ React-callinvoker: f64c8d57f45173835b6b9891cda27f1625551972
+ React-Core: 341863a2597c4a2320f916b4232d280cb8a28e4b
+ React-Core-prebuilt: 09c1c72e1af4819464f8c12c8663e6c9482777df
+ React-CoreModules: 9370769c9b2b1732c58960687f4d50d9d3510efc
+ React-cxxreact: 41ae74bd9eab80af9c5da6a9b4934eb97e2c7569
+ React-debug: eb0f19ff879ad3870dc760b6c21ad56cbb3e4b98
+ React-defaultsnativemodule: aa25696c7d18115bae062629c2183c267d96e017
+ React-domnativemodule: 016da08b63d25b2131eda8a3bb4836929a062dd7
+ React-Fabric: 179061816251452fa8ec7dfe7a16df1d06e12a59
+ React-FabricComponents: 3ea07086a38f4ff29aac97d2a99815e33ac71ffa
+ React-FabricImage: a55a5318d125f25ac9828e0465275ad081303720
+ React-featureflags: b0327e47f6e97ae98bd844ba6ebf97d62f07f5ff
+ React-featureflagsnativemodule: d94042d8fa774a4e162627d77eadd79861771865
+ React-graphics: 7eed2e1c4736871456c2b05926bc334e533f89eb
+ React-hermes: feafb2b23ca8a4c5a1892681ca12970c0861b2f5
+ React-idlecallbacksnativemodule: 05aeb9baf9b55d3a61dd0650f17ac5a609107578
+ React-ImageManager: 8dfcfe118282f2a0b3d279350d0f97a63e6b4034
+ React-intersectionobservernativemodule: 2fdf4760cf45f6303427d54a006adb0983c6719d
+ React-jserrorhandler: 375f46da481dea08283cc395627545e113217efa
+ React-jsi: 98e544352782dad976e1bb9e428e572f5e618978
+ React-jsiexecutor: cd242143c5973359a28edd6accfdfe8b281fd07d
+ React-jsinspector: bada2d53d66ca027cedab80327262df3e4686ef4
+ React-jsinspectorcdp: 1f345e95c747c4abc0786193294b5d4ec8deae68
+ React-jsinspectornetwork: f1191804c726ca36fd1d32a6b4806a6e6994183a
+ React-jsinspectortracing: 298ed7d8fb5fa6f0d78c5d13d0566db71847a5fd
+ React-jsitooling: d108a612810309154369fc74b89caa8793506485
+ React-jsitracing: 80caa0cbcff315e795d7f6cc013530367871a649
+ React-logger: 702a49dadb10a6bd63d56b297c3350b5ea552141
+ React-Mapbuffer: bcd9f00a498b2981c7b390f00fd2b7cd6e0ead29
+ React-microtasksnativemodule: 203b41906d627dac7d697bccba6137b630951fae
+ react-native-context-menu-view: da39a4612c1294d651907162529cfba8421e9b89
react-native-cookies: d648ab7025833b977c0b19e142503034f5f29411
- react-native-document-picker: 52817f2ac5174b08fb218dedb00440787e315936
- react-native-image-picker: 8a3f16000e794f5381a7fe47bb48fd8d06741e47
- react-native-keyboard-controller: 0faec0497327d901a610bd3f5810525a41a17bf1
- react-native-menu: 9efed55ad478c20b85bbd5c376f15b2cc5e41181
- react-native-safe-area-context: 0b8555c40461feb7198e999912a3446602e7c601
- react-native-sensitive-info: 31674faa5e17977b394fb67986c35ef94f8ffe81
+ react-native-document-picker: 4aa06b45d0f6dfc7fac5bcb5d1cee4f45af5d68a
+ react-native-image-picker: 168d6972e50ea848defe4ac4c222faa9bc52aea0
+ react-native-keyboard-controller: c872321fc580f8d815e9e2ad5558b24c26c18b4f
+ react-native-menu: 2de122c54dc10056932c0c0f73b82adfa4231faf
+ react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2
react-native-sfsymbols: 3309f24c98d32705568c84b714268f3ec62017c7
- react-native-simple-toast: c441c833fe4c18473c09fd3b5789f0b279dd074d
- react-native-video: 1f5605e948f042fa137f0b2c97f5776675952783
- react-native-webview: 1d8659a52cb8b8504f6c965555d1b27bdd313880
- React-NativeModulesApple: 74051ff264ce0b89ac799b56314f8f440a95ee4d
- React-oscompat: 0047f0ce53a328ce225777a6c617970556602c7c
- React-perflogger: bf00d816c3c3ad91658b44f93ce9ee343b4c70df
- React-performancetimeline: 75139687f2db7ca68fd0377466949af0b3408995
- React-RCTActionSheet: 9b3b04bbe75241c964d59a3ec18716d0bec22b2c
- React-RCTAnimation: 2ffefa3b11df9aa1248182fac5b74e6a9b5ebd21
- React-RCTAppDelegate: 535dd2b407b204bbdd0c46d4c7050ee1a28fa6f9
- React-RCTBlob: 3cf08a694a3f7b082e23717c7587b8d914685582
- React-RCTFabric: 2026fe43aa1142896e7e68237f6e88e283f8cab3
- React-RCTFBReactNativeSpec: 43cea3b898e0e7e81abfeafd2dd4084dd8afe662
- React-RCTImage: 4eaca67175ed2a33d39eeb8bad0e724c849bdc11
- React-RCTLinking: 5133d7bf648707bc3fb65fb7ab3cfa7f445e9893
- React-RCTNetwork: 708cf70b59721a23cc362cde08dd204375c9d0ac
- React-RCTRuntime: b361aa130a54eeb9bd3129fe0335513c981353b3
- React-RCTSettings: 6d413683ae5a36e80c7965f1ff8efa893b211a17
- React-RCTText: 0bd1e63583ba3dab0358f62ade056732b76463f8
- React-RCTVibration: bab25bb8bc2f017e4729deb59d26bb32240b16db
- React-rendererconsistency: 32ded9b29e3dc4c2607b2c2f774bc16320fbf7ed
- React-renderercss: a1e299ffd55c69fbc57e4a77a0e4dbf4a8069ada
- React-rendererdebug: b640acffa5b80be66f7c5e463ffef60fa74fbbce
- React-rncore: 6faaf52d39dca54cb3e63fd45cd4bd2004bceba7
- React-RuntimeApple: c734fb41922e0d6afbcbf6326b04c41c44d4e166
- React-RuntimeCore: b84b58450e647cb1e912857b81b9761317e29af0
- React-runtimeexecutor: e6aceac245c2e4f0dca15c2c57182d1dabc90e11
- React-RuntimeHermes: 9ae57e883b94d3da384b52ab3e4b22a7ac49d639
- React-runtimescheduler: e7a8619f43121a426f1ea44a6a76b22259bc56cc
- React-timing: 10c2421a49f1c2906d6bb95027248a4543ac113f
- React-utils: 9ec29e69f3e6347a4c93745f84c2a214b5b1ce64
- ReactAppDependencyProvider: b203bace11326361b7f0513b3f5854cd340aa929
- ReactCodegen: 6a29ad4365aeeceecb951cfd6546a99ce617cc50
- ReactCommon: 4e460daed3ccb6af9e3de3176920362d8b888a17
- RNCAsyncStorage: 39c42c1e478e1f5166d1db52b5055e090e85ad66
- RNCClipboard: ea6045252d5c0753cf17f62290f647dc0096f6ac
- RNCPushNotificationIOS: 6c4ca3388c7434e4a662b92e4dfeeee858e6f440
+ react-native-simple-toast: ff99357a514443f8140527b0698bced724cf99a9
+ react-native-video: 293a1dfb84d6b79b5bf9dd33925931008e225d5a
+ react-native-webview: 3e303e80cadb5f17118c8c1502aa398e9287e415
+ React-NativeModulesApple: 9068613f7650828eed29ead6fc0d0a1620117e6a
+ React-networking: 233dd85e1b890ca191f64f0896153165571b3039
+ React-oscompat: 05afae2c7689405dbb4f6458110d501d55357980
+ React-perflogger: e3b6c4e302c1db7ef30ea2484d22350cb91ed209
+ React-performancecdpmetrics: 78d0eea925c56b851ffc62377ed2c55be985a89b
+ React-performancetimeline: 0dd54019ec9fe7de84bfb1fc8a9ede032af8a7e9
+ React-RCTActionSheet: 6905f5568a98e1466071904ecce56f412477276b
+ React-RCTAnimation: 2296d52d6ea82e69c5019b8def1e7fd6c2adbeed
+ React-RCTAppDelegate: 1fe1d13018c2f556b583819219f7de73d5b70a68
+ React-RCTBlob: fa1130cb4e2f880ddc6cd910543d1e634330ee72
+ React-RCTFabric: 12abb4c93fac4af6a9de3fc6676a6263720b7f9e
+ React-RCTFBReactNativeSpec: 6e025ccbdb0d36a7ee57d79863da9a5b0d4a1c6d
+ React-RCTImage: cdae5d3d50e30ff8eb587134a87f742d37bc795a
+ React-RCTLinking: eeb33d8033a41c0ebab879110063a25f1a282f45
+ React-RCTNetwork: 066703b5cc088bd682f748a0d6b0fe7fbabc898f
+ React-RCTRuntime: ec68db9d8ab02e031053be5a6624be8018f79876
+ React-RCTSettings: 5fc7087bc8fd37c43f727ad7840d9014fb14625b
+ React-RCTText: 3e79e3df3a37e7ecf75b0a19815b8031531bfb06
+ React-RCTVibration: bfbc37851c3fb413ca206d768c1745fb9fce7cc4
+ React-rendererconsistency: ecc195511eaba84d5c9f3a9ef92f1c3942b2c825
+ React-renderercss: c72c11962925fbcad46ee6dc30ea04667f4ed089
+ React-rendererdebug: ea9f3d3c2bc5bdd5ee2d8e3ebc2abc72c27698ab
+ React-RuntimeApple: 1413c7dad511cea861046a1e5150bf0ae9f0c5c7
+ React-RuntimeCore: 337347732af37d78e93bbcc619ce973224a98eb9
+ React-runtimeexecutor: 096e0b91e643b6ed51c206dcfa4b9657eeadf7dc
+ React-RuntimeHermes: 44accf583d9d3aba9be9b9dbeef3e80504e215a3
+ React-runtimescheduler: c4b30a7b110b377f3b59b78ef1b78dba997614c6
+ React-timing: 84de84d0ad5b053a07d58be44a6f4913882759c2
+ React-utils: f1ec2d55598341eae23adcc8d946e9de5d6bd789
+ React-webperformancenativemodule: a9bba1ed256de5fb6607a038f37e2466e0f01fbf
+ ReactAppDependencyProvider: 3ec8fc5d32ad422f5b805523a5a5ea5556d8b3a2
+ ReactCodegen: 169fa6c4ad3c3065f98fa2364bf95e9a21c3afec
+ ReactCommon: ba28cd4e7d9e24014d2772a80e32da020c732b00
+ ReactNativeDependencies: 1b6406c8f01077c05d4896bf0ca80ec47ad45836
+ RNAppleAuthentication: a89c9804592b38ed4ab11f0aee68d05ba12ad432
+ RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
+ RNCClipboard: 88d7eeb555d1183915f0885bdbc5c97eb6f7f3ba
+ RNCPushNotificationIOS: 85957a0534fd1309b4c028ccbe5b1baf9ab5cf57
RNDeviceInfo: d3e91ffb33ee97a7982108476edb68cb3672efa6
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
- RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b
- RNReanimated: 2313402fe27fecb7237619e9c6fcee3177f08a65
- RNScreens: f38464ec1e83bda5820c3b05ccf4908e3841c5cc
+ RNGestureHandler: 0ea8153746a92b3744d4eaadade647debedf646e
+ RNReanimated: 86e5991396f1aa514db90d6c79d4c3e37a37bb10
+ RNScreens: 14243fa0d9842ffa7f8bb2d00b6c3cfd3ca817e8
RNShareMenu: e1cdfa3b9af89416afc75a80377cfd0de4f30ded
- RNSVG: 794f269526df9ddc1f79b3d1a202b619df0368e3
- SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
+ RNSVG: 0bd149b873018bc6e0e3321e2f6435bdba294460
+ RNWorklets: 5eddf924d11308d62920405addeddc3dfe1daaaa
+ SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
- SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
- Yoga: 9663a18d096f7f7e17a535cf00849db756709955
+ Yoga: e41d64347f52d634dd8c2f6bac33f95081959fd3
-PODFILE CHECKSUM: 101be349ab1d704481c999dd0f31cc3ca05df79e
+PODFILE CHECKSUM: b7b0b21e39ec98e457fc86268586d1a897a022df
-COCOAPODS: 1.16.2
+COCOAPODS: 1.15.2
diff --git a/metro.config.js b/metro.config.js
index ad594534..b512365d 100644
--- a/metro.config.js
+++ b/metro.config.js
@@ -1,11 +1,14 @@
-const { getDefaultConfig } = require('expo/metro-config');
-const { mergeConfig } = require('@react-native/metro-config');
+const {
+ getDefaultConfig: getReactNativeDefaultConfig,
+ mergeConfig,
+} = require('@react-native/metro-config')
+const { getDefaultConfig: getExpoDefaultConfig } = require('expo/metro-config')
/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
- * @type {import('metro-config').MetroConfig}
+ * @type {import('@react-native/metro-config').MetroConfig}
*/
const config = {
transformer: {
@@ -16,6 +19,10 @@ const config = {
},
}),
},
-};
+}
-module.exports = mergeConfig(getDefaultConfig(__dirname), config);
+module.exports = mergeConfig(
+ getReactNativeDefaultConfig(__dirname),
+ getExpoDefaultConfig(__dirname),
+ config
+)
diff --git a/package.json b/package.json
index 21c0af2b..9bc4b7ba 100644
--- a/package.json
+++ b/package.json
@@ -2,62 +2,64 @@
"name": "microblog_rn",
"version": "0.0.1",
"private": true,
+ "packageManager": "bun@1.4.0",
+ "main": "index.js",
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start --client-logs",
"test": "jest",
"lint": "eslint .",
- "postinstall": "patch-package",
"check-dependencies": "rnx-align-deps",
"fix-dependencies": "rnx-align-deps --write",
"pods": "npx pod-install ios",
"adb": "adb reverse tcp:8081 tcp:8081"
},
"dependencies": {
- "@react-native-async-storage/async-storage": "2.1.2",
+ "@invertase/react-native-apple-authentication": "^2.4.1",
+ "@likashefqet/react-native-image-zoom": "likashefqet/react-native-image-zoom",
+ "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3",
- "@react-native-community/push-notification-ios": "^1.11.0",
+ "@react-native-community/push-notification-ios": "^1.12.0",
"@react-native-cookies/cookies": "^6.2.1",
"@react-native-documents/picker": "^10.1.7",
- "@react-native-menu/menu": "^1.2.4",
- "@react-navigation/bottom-tabs": "^7.8.8",
+ "@react-native-menu/menu": "^2.0.0",
+ "@react-navigation/bottom-tabs": "7.8.8",
"@react-navigation/native": "^7.1.22",
"@react-navigation/native-stack": "^7.8.2",
"@xmldom/xmldom": "^0.9.8",
"axios": "^0.22.0",
- "expo": "~53.0.24",
- "expo-image": "~2.4.1",
- "expo-secure-store": "~14.2.4",
- "expo-web-browser": "~14.2.0",
+ "expo": "~55.0.17",
+ "expo-image": "~55.0.9",
+ "expo-secure-store": "~55.0.13",
+ "expo-web-browser": "~55.0.14",
"fast-xml-parser": "^5.3.2",
"markdown-it": "^14.1.0",
"mobx": "^6.15.0",
"mobx-react": "^7.6.0",
"mobx-state-tree": "^7.0.2",
- "react": "19.0.0",
- "react-native": "0.79.7",
+ "react": "19.2.0",
+ "react-native": "0.83.6",
"react-native-actions-sheet": "^0.9.8",
- "react-native-context-menu-view": "^1.20.0",
+ "react-native-context-menu-view": "^1.21.0",
"react-native-device-info": "^8.7.1",
"react-native-fs": "^2.20.0",
- "react-native-gesture-handler": "~2.24.0",
+ "react-native-gesture-handler": "~2.30.0",
"react-native-hyperlink": "^0.0.22",
"react-native-image-picker": "7.2.3",
- "react-native-image-viewing": "^0.2.2",
- "react-native-keyboard-controller": "^1.20.1",
+ "react-native-keyboard-controller": "1.20.7",
"react-native-push-notification": "^8.1.1",
- "react-native-reanimated": "~3.17.5",
- "react-native-safe-area-context": "^5.6.2",
- "react-native-screens": "^4.18.0",
- "react-native-sensitive-info": "5.5.8",
+ "react-native-reanimated": "4.2.1",
+ "react-native-safe-area-context": "~5.6.2",
+ "react-native-screens": "~4.23.0",
"react-native-sfsymbols": "^1.2.2",
"react-native-share-menu": "microdotblog/react-native-share-menu",
"react-native-simple-toast": "^3.3.2",
- "react-native-svg": "15.11.2",
+ "react-native-svg": "15.15.3",
"react-native-url-polyfill": "^2.0.0",
"react-native-video": "^6.18.0",
- "react-native-webview": "13.16.0"
+ "react-native-webview": "13.16.0",
+ "react-native-worklets": "0.7.4"
},
"devDependencies": {
"@babel/core": "^7.28.5",
@@ -65,36 +67,39 @@
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-env": "^7.28.5",
"@babel/runtime": "^7.28.4",
- "@react-native-community/cli": "15.0.1",
- "@react-native-community/cli-platform-android": "15.0.1",
- "@react-native-community/cli-platform-ios": "15.0.1",
+ "@react-native-community/cli": "20.0.0",
+ "@react-native-community/cli-platform-android": "20.0.0",
+ "@react-native-community/cli-platform-ios": "20.0.0",
"@react-native-community/eslint-config": "^3.2.0",
- "@react-native/babel-preset": "0.79.7",
- "@react-native/eslint-config": "0.79.7",
- "@react-native/metro-config": "0.79.7",
- "@react-native/typescript-config": "0.79.7",
+ "@react-native/babel-preset": "0.83.6",
+ "@react-native/eslint-config": "0.83.6",
+ "@react-native/metro-config": "0.83.6",
+ "@react-native/typescript-config": "0.83.6",
"@tsconfig/react-native": "^2.0.3",
"@types/jest": "^29.5.14",
- "@types/react": "~19.0.14",
+ "@types/react": "^19.2.0",
"@types/react-test-renderer": "^19.1.0",
"babel-jest": "^29.7.0",
"babel-plugin-transform-remove-console": "^6.9.4",
- "babel-preset-expo": "~13.0.0",
+ "babel-preset-expo": "~55.0.18",
"eslint": "^8.57.1",
"jest": "^29.7.0",
- "patch-package": "^6.5.1",
- "postinstall-postinstall": "^2.1.0",
"prettier": "2.8.8",
- "react-test-renderer": "19.0.0",
+ "react-test-renderer": "19.2.0",
"typescript": "~5.8.3"
},
"jest": {
"preset": "react-native"
},
"engines": {
- "node": ">=18"
+ "node": ">=20.19.4",
+ "bun": ">=1.4.0"
},
"patchedDependencies": {
- "react-native-sensitive-info@5.5.8": "patches/react-native-sensitive-info@5.5.8.patch"
+ "@react-native-cookies/cookies@6.2.1": "patches/@react-native-cookies+cookies+6.2.1.patch",
+ "@react-navigation/bottom-tabs@7.8.8": "patches/@react-navigation+bottom-tabs+7.8.8.patch",
+ "react-native-fs@2.20.0": "patches/react-native-fs+2.20.0.patch",
+ "react-native-push-notification@8.1.1": "patches/react-native-push-notification+8.1.1.patch",
+ "react-native-screens@4.23.0": "patches/react-native-screens+4.23.0.patch"
}
-}
\ No newline at end of file
+}
diff --git a/patches/@react-native-cookies+cookies+6.2.1.patch b/patches/@react-native-cookies+cookies+6.2.1.patch
new file mode 100644
index 00000000..f3c231ad
--- /dev/null
+++ b/patches/@react-native-cookies+cookies+6.2.1.patch
@@ -0,0 +1,21 @@
+diff --git a/android/build.gradle b/android/build.gradle
+--- a/android/build.gradle
++++ b/android/build.gradle
+@@ -31,7 +31,7 @@ buildscript {
+ if (project == rootProject) {
+ repositories {
+ google()
+- jcenter()
++ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.3'
+@@ -67,7 +67,7 @@ repositories {
+ url "$rootDir/../node_modules/jsc-android/dist"
+ }
+ google()
+- jcenter()
++ mavenCentral()
+ }
+
+ dependencies {
diff --git a/patches/@react-navigation+bottom-tabs+7.8.8.patch b/patches/@react-navigation+bottom-tabs+7.8.8.patch
new file mode 100644
index 00000000..1d754030
--- /dev/null
+++ b/patches/@react-navigation+bottom-tabs+7.8.8.patch
@@ -0,0 +1,39 @@
+diff --git a/src/unstable/NativeBottomTabView.native.tsx b/src/unstable/NativeBottomTabView.native.tsx
+--- a/src/unstable/NativeBottomTabView.native.tsx
++++ b/src/unstable/NativeBottomTabView.native.tsx
+@@ -16,8 +16,7 @@ import Color from 'color';
+ import * as React from 'react';
+ import { type ColorValue, Platform, PlatformColor } from 'react-native';
+ import {
+- BottomTabs,
+- BottomTabsScreen,
++ Tabs,
+ type BottomTabsScreenItemStateAppearance,
+ type PlatformIcon,
+ } from 'react-native-screens';
+@@ -32,6 +31,9 @@ import type {
+ NativeBottomTabNavigationProp,
+ } from './types';
+
++const BottomTabs = Tabs.Host;
++const BottomTabsScreen = Tabs.Screen;
++
+ type Props = NativeBottomTabNavigationConfig & {
+ state: TabNavigationState;
+ navigation: NativeBottomTabNavigationHelpers;
+diff --git a/lib/module/unstable/NativeBottomTabView.native.js b/lib/module/unstable/NativeBottomTabView.native.js
+--- a/lib/module/unstable/NativeBottomTabView.native.js
++++ b/lib/module/unstable/NativeBottomTabView.native.js
+@@ -5,9 +5,11 @@ import { CommonActions, StackActions, useTheme } from '@react-navigation/native';
+ import Color from 'color';
+ import * as React from 'react';
+ import { Platform, PlatformColor } from 'react-native';
+-import { BottomTabs, BottomTabsScreen } from 'react-native-screens';
++import { Tabs } from 'react-native-screens';
+ import { NativeScreen } from "./NativeScreen/NativeScreen.js";
+ import { jsx as _jsx } from "react/jsx-runtime";
++const BottomTabs = Tabs.Host;
++const BottomTabsScreen = Tabs.Screen;
+ export function NativeBottomTabView({
+ state,
+ navigation,
diff --git a/patches/react-native-fs+2.20.0.patch b/patches/react-native-fs+2.20.0.patch
index 3938548f..c1b461fc 100644
--- a/patches/react-native-fs+2.20.0.patch
+++ b/patches/react-native-fs+2.20.0.patch
@@ -1,7 +1,7 @@
-diff --git a/node_modules/react-native-fs/RNFSManager.m b/node_modules/react-native-fs/RNFSManager.m
+diff --git a/RNFSManager.m b/RNFSManager.m
index 5ddd941..7bca62a 100755
---- a/node_modules/react-native-fs/RNFSManager.m
-+++ b/node_modules/react-native-fs/RNFSManager.m
+--- a/RNFSManager.m
++++ b/RNFSManager.m
@@ -294,8 +294,8 @@ + (BOOL)requiresMainQueueSetup
}
diff --git a/patches/react-native-hyperlink@0.0.22.patch b/patches/react-native-hyperlink@0.0.22.patch
new file mode 100644
index 00000000..f12a5c3c
--- /dev/null
+++ b/patches/react-native-hyperlink@0.0.22.patch
@@ -0,0 +1,98 @@
+diff --git a/src/Hyperlink.tsx b/src/Hyperlink.tsx
+index c2d8ef6..e54501d 100644
+--- a/src/Hyperlink.tsx
++++ b/src/Hyperlink.tsx
+@@ -73,11 +73,11 @@ class Hyperlink extends Component {
+ let elements = [];
+ let _lastIndex = 0;
+
+- const componentProps = {
+- ...component.props,
+- ref: undefined,
+- key: undefined,
+- };
++ const componentProps = {
++ ...component.props,
++ };
++ delete componentProps.key;
++ delete componentProps.ref;
+
+ try {
+ this.state.linkifyIt
+@@ -136,11 +136,11 @@ class Hyperlink extends Component {
+ let { props: { children } = { children: undefined } } = component || {};
+ if (!children) return component;
+
+- const componentProps = {
+- ...component.props,
+- ref: undefined,
+- key: undefined,
+- };
++ const componentProps = {
++ ...component.props,
++ };
++ delete componentProps.key;
++ delete componentProps.ref;
+
+ return React.cloneElement(
+ component,
+diff --git a/dist/commonjs/Hyperlink.js b/dist/commonjs/Hyperlink.js
+index 69385a6..f4a98bf 100644
+--- a/dist/commonjs/Hyperlink.js
++++ b/dist/commonjs/Hyperlink.js
+@@ -56,9 +56,8 @@ class Hyperlink extends _react.Component {
+ if (!this.state.linkifyIt.pretest(component.props.children) || !this.state.linkifyIt.test(component.props.children)) return component;
+ let elements = [];
+ let _lastIndex = 0;
+- const componentProps = { ...component.props,
+- ref: undefined,
+- key: undefined
+- };
++ const componentProps = { ...component.props };
++ delete componentProps.key;
++ delete componentProps.ref;
+
+ try {
+@@ -117,9 +116,8 @@ class Hyperlink extends _react.Component {
+ }
+ } = component || {};
+ if (!children) return component;
+- const componentProps = { ...component.props,
+- ref: undefined,
+- key: undefined
+- };
++ const componentProps = { ...component.props };
++ delete componentProps.key;
++ delete componentProps.ref;
+ return /*#__PURE__*/_react.default.cloneElement(component, componentProps, _react.default.Children.map(children, child => {
+ let {
+diff --git a/dist/module/Hyperlink.js b/dist/module/Hyperlink.js
+index 4b798f3..49d08b7 100644
+--- a/dist/module/Hyperlink.js
++++ b/dist/module/Hyperlink.js
+@@ -36,9 +36,8 @@ class Hyperlink extends Component {
+ if (!this.state.linkifyIt.pretest(component.props.children) || !this.state.linkifyIt.test(component.props.children)) return component;
+ let elements = [];
+ let _lastIndex = 0;
+- const componentProps = { ...component.props,
+- ref: undefined,
+- key: undefined
+- };
++ const componentProps = { ...component.props };
++ delete componentProps.key;
++ delete componentProps.ref;
+
+ try {
+@@ -97,9 +96,8 @@ class Hyperlink extends Component {
+ }
+ } = component || {};
+ if (!children) return component;
+- const componentProps = { ...component.props,
+- ref: undefined,
+- key: undefined
+- };
++ const componentProps = { ...component.props };
++ delete componentProps.key;
++ delete componentProps.ref;
+ return /*#__PURE__*/React.cloneElement(component, componentProps, React.Children.map(children, child => {
+ let {
diff --git a/patches/react-native-image-viewing+0.2.2.patch b/patches/react-native-image-viewing+0.2.2.patch
deleted file mode 100644
index 33ed8e93..00000000
--- a/patches/react-native-image-viewing+0.2.2.patch
+++ /dev/null
@@ -1,17 +0,0 @@
-diff --git a/node_modules/react-native-image-viewing/dist/components/ImageItem/ImageItem.ios.js b/node_modules/react-native-image-viewing/dist/components/ImageItem/ImageItem.ios.js
-index 0708505..1b9a7cc 100644
---- a/node_modules/react-native-image-viewing/dist/components/ImageItem/ImageItem.ios.js
-+++ b/node_modules/react-native-image-viewing/dist/components/ImageItem/ImageItem.ios.js
-@@ -26,10 +26,10 @@ const ImageItem = ({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPre
- const scrollValueY = new Animated.Value(0);
- const scaleValue = new Animated.Value(scale || 1);
- const translateValue = new Animated.ValueXY(translate);
-- const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1;
-+ const maxScale = scale && scale > 0 ? 3 : 1;
- const imageOpacity = scrollValueY.interpolate({
- inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET],
-- outputRange: [0.5, 1, 0.5],
-+ outputRange: [0.95, 1, 0.95],
- });
- const imagesStyles = getImageStyles(imageDimensions, translateValue, scaleValue);
- const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity };
diff --git a/patches/react-native-push-notification+8.1.1.patch b/patches/react-native-push-notification+8.1.1.patch
new file mode 100644
index 00000000..63d0ffd2
--- /dev/null
+++ b/patches/react-native-push-notification+8.1.1.patch
@@ -0,0 +1,18 @@
+diff --git a/android/build.gradle b/android/build.gradle
+--- a/android/build.gradle
++++ b/android/build.gradle
+@@ -2,7 +2,6 @@ buildscript {
+ repositories {
+ mavenCentral()
+ google()
+- jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.0'
+@@ -12,6 +12,5 @@ allprojects {
+ repositories {
+ mavenCentral()
+ google()
+- jcenter()
+ }
+ }
diff --git a/patches/react-native-screens+4.23.0.patch b/patches/react-native-screens+4.23.0.patch
new file mode 100644
index 00000000..68220a00
--- /dev/null
+++ b/patches/react-native-screens+4.23.0.patch
@@ -0,0 +1,98 @@
+diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm
+--- a/ios/RNSScreen.mm
++++ b/ios/RNSScreen.mm
+@@ -571,6 +571,7 @@
+
+ - (void)notifyDismissedWithCount:(int)dismissCount
+ {
++ self.userInteractionEnabled = NO;
+ #ifdef RCT_NEW_ARCH_ENABLED
+ // If screen is already unmounted then there will be no event emitter
+ if (_eventEmitter != nullptr) {
+@@ -592,6 +593,7 @@
+
+ - (void)notifyDismissCancelledWithDismissCount:(int)dismissCount
+ {
++ self.userInteractionEnabled = YES;
+ #ifdef RCT_NEW_ARCH_ENABLED
+ // If screen is already unmounted then there will be no event emitter
+ if (_eventEmitter != nullptr) {
+@@ -608,6 +610,7 @@
+
+ - (void)notifyWillAppear
+ {
++ self.userInteractionEnabled = YES;
+ #ifdef RCT_NEW_ARCH_ENABLED
+ // If screen is already unmounted then there will be no event emitter
+ if (_eventEmitter != nullptr) {
+@@ -1345,6 +1348,7 @@
+ - (void)willBeUnmountedInUpcomingTransaction
+ {
+ _markedForUnmountInCurrentTransaction = YES;
++ self.userInteractionEnabled = NO;
+ }
+
+ + (react::ComponentDescriptorProvider)componentDescriptorProvider
+diff --git a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt
+--- a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt
++++ b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt
+@@ -11,6 +11,8 @@
+ import com.facebook.react.modules.core.ReactChoreographer
+ import com.facebook.react.uimanager.ThemedReactContext
+ import com.swmansion.rnscreens.utils.InsetsCompat
++import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
++import com.swmansion.rnscreens.utils.getDecorViewTopInset
+ import com.swmansion.rnscreens.utils.resolveInsetsOrZero
+ import kotlin.math.max
+
+@@ -32,7 +34,12 @@
+ // edge-to-edge was enabled: https://github.com/software-mansion/react-native-screens/pull/2464/files#diff-bd1164595b04f44490738b8183f84a625c0e7552a4ae70bfefcdf3bca4d37fc7R34).
+ private val shouldAvoidDisplayCutout = true
+
+- private val shouldApplyTopInset = true
++ // Android 15+ enforces edge-to-edge for targetSdk 35+ even when RN's
++ // edgeToEdgeEnabled flag is false, so always keep the status bar inset there.
++ private val shouldApplyTopInset: Boolean
++ get() =
++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM ||
++ isEdgeToEdgeFeatureFlagOn
+
+ private var shouldApplyLayoutCorrectionForTopInset = false
+
+@@ -139,13 +146,32 @@
+ 0,
+ )
+
+- // We want to handle display cutout always, no matter the HeaderConfig prop values.
+- // If there are no cutout displays, we want to apply the additional padding to
+- // respect the status bar.
++ val statusBarInsets =
++ resolveInsetsOrZero(WindowInsetsCompat.Type.statusBars(), unhandledInsets)
++
++ // Keep a status bar inset on Android 15+ edge-to-edge windows, but do not let
++ // displayCutout.top or other system bar insets make the toolbar taller.
++ // SafeAreaProvider may already have consumed statusBars, leaving unhandled top
++ // as 0 — fall back to DecorView root insets so header buttons stay tappable.
++ val topInset =
++ if (shouldApplyTopInset) {
++ // Keep in sync with src/utils/android_toolbar_inset.test.js
++ val fromUnhandled = statusBarInsets.top
++ if (fromUnhandled > 0) {
++ fromUnhandled
++ } else {
++ val decorView =
++ (context as? ThemedReactContext)?.currentActivity?.window?.decorView
++ if (decorView != null) getDecorViewTopInset(decorView) else 0
++ }
++ } else {
++ 0
++ }
++
+ val verticalInsets =
+ InsetsCompat.of(
+ 0,
+- max(cutoutInsets.top, if (shouldApplyTopInset) systemBarInsets.top else 0),
++ topInset,
+ 0,
+ max(cutoutInsets.bottom, 0),
+ )
diff --git a/patches/react-native-sensitive-info@5.5.8.patch b/patches/react-native-sensitive-info@5.5.8.patch
deleted file mode 100644
index f7a6f358..00000000
--- a/patches/react-native-sensitive-info@5.5.8.patch
+++ /dev/null
@@ -1,45 +0,0 @@
-diff --git a/RNSensitiveInfo.js b/RNSensitiveInfo.js
-index b3e164c6bb00e1bb885e796f6f402909ecfb6071..34cec63f2020108cac0051861175a08c92aa5817 100644
---- a/RNSensitiveInfo.js
-+++ b/RNSensitiveInfo.js
-@@ -1,23 +1,23 @@
--import {NativeModules} from 'react-native';
-+import { NativeModules } from 'react-native';
-
- const RNSensitiveInfo = NativeModules.RNSensitiveInfo;
-
--module.exports = {
-- ...RNSensitiveInfo,
-- setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment) {
-- if (RNSensitiveInfo.setInvalidatedByBiometricEnrollment == null) {
-- return;
-- }
-+RNSensitiveInfo.setInvalidatedByBiometricEnrollment = (
-+ invalidatedByBiometricEnrollment,
-+) => {
-+ if (RNSensitiveInfo.setInvalidatedByBiometricEnrollment == null) {
-+ return undefined;
-+ }
-
-- return RNSensitiveInfo.setInvalidatedByBiometricEnrollment(
-- invalidatedByBiometricEnrollment,
-- );
-- },
-- cancelFingerprintAuth() {
-- if (RNSensitiveInfo.cancelFingerprintAuth == null) {
-- return;
-- }
-+ return RNSensitiveInfo.setInvalidatedByBiometricEnrollment(
-+ invalidatedByBiometricEnrollment,
-+ );
-+};
-+RNSensitiveInfo.cancelFingerprintAuth = () => {
-+ if (RNSensitiveInfo.cancelFingerprintAuth == null) {
-+ return undefined;
-+ }
-
-- return RNSensitiveInfo.cancelFingerprintAuth();
-- },
-+ return RNSensitiveInfo.cancelFingerprintAuth();
- };
-+export default RNSensitiveInfo;
diff --git a/src/api/MicroBlogApi.js b/src/api/MicroBlogApi.js
index d4afdf47..3a3be4e1 100644
--- a/src/api/MicroBlogApi.js
+++ b/src/api/MicroBlogApi.js
@@ -16,6 +16,7 @@ export const REPORTING_ERROR = 8;
export const MUTING_ERROR = 9;
export const DELETE_ERROR = 10;
export const DUPLICATE_REPLY = 11;
+export const APPLE_USERNAME_REQUIRED = 12;
export let CURRENT_REPLY_ID = 0;
axios.defaults.baseURL = API_URL;
@@ -73,6 +74,41 @@ class MicroBlogApi {
});
return login
}
+
+ async login_with_apple({ user_id, identity_token, email = "", full_name = "", username = null }) {
+ const form = new FormData()
+ form.append("user_id", user_id)
+ form.append("identity_token", identity_token)
+ if (email != null) {
+ form.append("email", email)
+ }
+ if (full_name != null) {
+ form.append("full_name", full_name)
+ }
+ if (username != null) {
+ form.append("username", username)
+ }
+ form.append("app_name", APP_NAME)
+
+ const login = axios
+ .post('/account/apple', form)
+ .then(response => {
+ if (response.data.error) {
+ return response.data
+ }
+ else if (response.data.username != null && response.data.username.length === 0) {
+ return APPLE_USERNAME_REQUIRED
+ }
+ else {
+ return response.data
+ }
+ })
+ .catch(error => {
+ console.log(error)
+ return LOGIN_ERROR
+ })
+ return login
+ }
async get_profile(username) {
console.log('MicroBlogApi:get_profile', username);
diff --git a/src/api/MicroBlogAuth.js b/src/api/MicroBlogAuth.js
new file mode 100644
index 00000000..3d9d5bc2
--- /dev/null
+++ b/src/api/MicroBlogAuth.js
@@ -0,0 +1,145 @@
+export const MICRO_BLOG_AUTH_URL = 'https://micro.blog/indieauth/auth'
+export const MICRO_BLOG_TOKEN_URL = 'https://micro.blog/indieauth/token'
+export const MICRO_BLOG_CLIENT_ID = 'https://micro.blog/client.json'
+export const MICRO_BLOG_SCOPE = 'create'
+export const MICRO_BLOG_SCHEME = 'microblog'
+export const MICRO_BLOG_REDIRECT_URI = `${MICRO_BLOG_SCHEME}://auth/callback`
+
+export function get_micro_blog_redirect_uri() {
+ return MICRO_BLOG_REDIRECT_URI
+}
+
+export function build_micro_blog_auth_url({
+ client_id = MICRO_BLOG_CLIENT_ID,
+ redirect_uri = get_micro_blog_redirect_uri(),
+ state,
+} = {}) {
+ const params = new URLSearchParams({
+ app: 1,
+ client_id,
+ redirect_uri,
+ response_type: 'code',
+ scope: MICRO_BLOG_SCOPE,
+ state,
+ })
+
+ return `${MICRO_BLOG_AUTH_URL}?${params.toString()}`
+}
+
+export function extract_micro_blog_callback_params(raw_url = '') {
+ if (!raw_url) {
+ return {
+ code: '',
+ state: '',
+ }
+ }
+
+ try {
+ const parsed_url = new URL(raw_url)
+
+ return {
+ code: parsed_url.searchParams.get('code')?.trim() || '',
+ state: parsed_url.searchParams.get('state')?.trim() || '',
+ }
+ }
+ catch (error) {
+ return {
+ code: '',
+ state: '',
+ }
+ }
+}
+
+export function extract_signin_token(raw_url = '') {
+ if (!raw_url) {
+ return ''
+ }
+
+ try {
+ const parsed_url = new URL(raw_url)
+
+ if (parsed_url.protocol !== `${MICRO_BLOG_SCHEME}:`) {
+ return ''
+ }
+
+ if (parsed_url.host !== 'signin') {
+ return ''
+ }
+
+ return parsed_url.pathname.split('/').filter(Boolean).pop()?.trim() || ''
+ }
+ catch (error) {
+ return ''
+ }
+}
+
+export function is_micro_blog_callback_url(raw_url = '') {
+ if (!raw_url) {
+ return false
+ }
+
+ try {
+ const parsed_url = new URL(raw_url)
+ const matches_host_callback =
+ parsed_url.host === 'auth' && parsed_url.pathname === '/callback'
+ const matches_path_callback = parsed_url.pathname === '/auth/callback'
+
+ return parsed_url.protocol === `${MICRO_BLOG_SCHEME}:` &&
+ (matches_host_callback || matches_path_callback)
+ }
+ catch (error) {
+ return false
+ }
+}
+
+export function is_signin_token_url(raw_url = '') {
+ return extract_signin_token(raw_url).length > 0
+}
+
+export async function exchange_micro_blog_code({
+ client_id = MICRO_BLOG_CLIENT_ID,
+ code,
+ redirect_uri = get_micro_blog_redirect_uri(),
+} = {}) {
+ const body = new URLSearchParams({
+ client_id,
+ code,
+ grant_type: 'authorization_code',
+ redirect_uri,
+ })
+
+ const response = await fetch(MICRO_BLOG_TOKEN_URL, {
+ body: body.toString(),
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ method: 'POST',
+ })
+ const payload = await response.json().catch(() => ({}))
+
+ if (!response.ok || payload?.error) {
+ const error = new Error(
+ payload?.error_description || payload?.error || 'Micro.blog token exchange failed.'
+ )
+ error.status = response.status
+ throw error
+ }
+
+ return payload
+}
+
+export function create_oauth_state() {
+ const bytes = new Uint8Array(16)
+
+ if (typeof globalThis.crypto?.getRandomValues === 'function') {
+ globalThis.crypto.getRandomValues(bytes)
+ }
+ else {
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = Math.floor(Math.random() * 256)
+ }
+ }
+
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')
+}
diff --git a/src/api/MicroPubApi.js b/src/api/MicroPubApi.js
index 3509f104..9f096ffa 100644
--- a/src/api/MicroPubApi.js
+++ b/src/api/MicroPubApi.js
@@ -1,7 +1,8 @@
-import { Alert, Platform } from 'react-native';
+import { Alert } from 'react-native';
import axios from 'axios';
import { DOMParser } from "@xmldom/xmldom";
import App from "./../stores/App";
+import { buildUploadFileName } from "../utils/file_names"
export const FETCH_ERROR = 2
export const POST_ERROR = 3
@@ -11,6 +12,18 @@ export const NO_AUTH = 6
export const DELETE_ERROR = 7
export const MICROPUB_NOT_FOUND = 8
+const progress_from_upload_event = progressEvent => {
+ const loaded = Number(progressEvent?.loaded)
+ const total = Number(progressEvent?.total)
+ if (!Number.isFinite(loaded) || loaded <= 0) {
+ return 0
+ }
+ if (!Number.isFinite(total) || total <= 0) {
+ return 1
+ }
+ return Math.max(1, Math.min(100, Math.round((loaded * 100) / total)))
+}
+
class MicroPubApi {
async discover_micropub_endpoints(url, alternate_html_match = false) {
@@ -79,7 +92,7 @@ class MicroPubApi {
return this.discover_micropub_endpoints(url, true)
}
else {
- Alert.alert("Whoops, an error occurred trying to connect. Please try again.")
+ Alert.alert("An error occurred trying to connect. Please try again.")
}
return MICROPUB_NOT_FOUND
}
@@ -279,8 +292,9 @@ class MicroPubApi {
async upload_image(service, file) {
const data = new FormData();
+ const file_name = buildUploadFileName(file, Date.now())
data.append("file", {
- name: `image${file.file_extension()}`,
+ name: file_name,
type: file.type,
uri: file.uri
})
@@ -292,10 +306,7 @@ class MicroPubApi {
headers: { Authorization: `Bearer ${ service.token }` },
timeout: 60000,
onUploadProgress: progressEvent => {
- const progress = Math.round(
- (progressEvent.loaded * 100) / progressEvent.total
- )
- file.update_progress(progress)
+ file.update_progress(progress_from_upload_event(progressEvent))
},
cancelToken: file.cancel_source.token,
})
@@ -330,17 +341,16 @@ class MicroPubApi {
async upload_media(service, file, destination) {
const data = new FormData()
- // Get extension from file name, for example: 12345.jpg
- let extension = file.uri.split('.').pop()
- if (Platform.OS === "android" && file.name != null && file.name !== "") {
- extension = file.name.split('.').pop()
- }
+ const file_name = buildUploadFileName(file, Date.now())
data.append("file", {
- name: `media.${extension.toLowerCase()}`,
+ name: file_name,
type: file.type,
uri: file.uri,
})
- data.append("mp-destination", App.current_screen_name === "microblog.UploadsScreen" || service.temporary_destination !== null && service.temporary_destination !== service.destination ? service.temporary_destination : service.destination)
+ const upload_destination = destination || (App.current_screen_name === "microblog.UploadsScreen" || service.temporary_destination !== null && service.temporary_destination !== service.destination ? service.temporary_destination : service.destination)
+ if (upload_destination) {
+ data.append("mp-destination", upload_destination)
+ }
console.log('MicroPubApi:upload_media', service, file, data)
const upload = axios
@@ -348,10 +358,7 @@ class MicroPubApi {
headers: { Authorization: `Bearer ${ service.token }` },
timeout: 60000, // 60 second timeout
onUploadProgress: progressEvent => {
- const progress = Math.round(
- (progressEvent.loaded * 100) / progressEvent.total
- )
- file.update_progress(progress)
+ file.update_progress(progress_from_upload_event(progressEvent))
},
cancelToken: file.cancel_source.token,
})
diff --git a/src/api/XMLRPCApi.js b/src/api/XMLRPCApi.js
index a3de01c5..af94fab0 100644
--- a/src/api/XMLRPCApi.js
+++ b/src/api/XMLRPCApi.js
@@ -145,10 +145,10 @@ class XMLRPCApi {
console.log('XMLRPCApi:error', err)
// Check if error is incorrect password/username
if (err?.toString()?.includes("username or password")) {
- Alert.alert("Whoops. Your username or password is wrong. Please try again.")
+ Alert.alert("Your username or password is wrong. Please try again.")
}
else {
- Alert.alert("Whoops, an error occured. Please try again.")
+ Alert.alert("An error occured. Please try again.")
}
return XML_ERROR
})
@@ -172,10 +172,10 @@ class XMLRPCApi {
console.log('XMLRPCApi:error', err)
// Check if error is incorrect password/username
if (err?.toString()?.includes("username or password")) {
- Alert.alert("Whoops. Your username or password is wrong. Please try again.")
+ Alert.alert("Your username or password is wrong. Please try again.")
}
else {
- Alert.alert("Whoops, an error occured. Please try again.")
+ Alert.alert("An error occured. Please try again.")
}
return XML_ERROR
})
@@ -247,10 +247,10 @@ class XMLRPCApi {
console.log('XMLRPCApi:error', err)
// Check if error is incorrect password/username
if (err?.toString()?.includes("username or password")) {
- Alert.alert("Whoops. Your username or password is wrong. Please try again.")
+ Alert.alert("Your username or password is wrong. Please try again.")
}
else {
- Alert.alert("Whoops, an error occured. Please try again.")
+ Alert.alert("An error occured. Please try again.")
}
return XML_ERROR
})
diff --git a/src/assets/app_icon.png b/src/assets/app_icon.png
new file mode 100644
index 00000000..cd232ea7
Binary files /dev/null and b/src/assets/app_icon.png differ
diff --git a/src/assets/mb_logo.png b/src/assets/mb_logo.png
new file mode 100644
index 00000000..38471b22
Binary files /dev/null and b/src/assets/mb_logo.png differ
diff --git a/src/bootstrap/push_notifications.js b/src/bootstrap/push_notifications.js
new file mode 100644
index 00000000..7939ecca
--- /dev/null
+++ b/src/bootstrap/push_notifications.js
@@ -0,0 +1,48 @@
+import PushNotification from 'react-native-push-notification'
+import PushNotificationIOS from '@react-native-community/push-notification-ios'
+import { AppState } from 'react-native'
+import Push from '../stores/Push'
+
+let did_bootstrap = false
+
+export function bootstrap_push_notifications() {
+ if (did_bootstrap) {
+ return
+ }
+
+ did_bootstrap = true
+ console.log("Push:bootstrap:start", AppState.currentState)
+
+ PushNotification.configure({
+ onRegister: function (data) {
+ console.log("Push:bootstrap:onRegister:data", data)
+ Push.set_token(data.token)
+ },
+ onRegistrationError: function (error) {
+ console.log("Push:bootstrap:onRegistrationError:error", error)
+ },
+ onNotification: function (notification) {
+ console.log("Push:bootstrap:onNotification", notification)
+ Push.handle_notification(notification)
+ if (typeof notification?.finish === 'function') {
+ notification.finish(PushNotificationIOS.FetchResult.NoData)
+ }
+ },
+ onAction: function (data) {
+ console.log("Push:bootstrap:onAction:data", data)
+ },
+ permissions: {
+ alert: true,
+ badge: true,
+ sound: true
+ },
+ popInitialNotification: true,
+ requestPermissions: true
+ })
+
+ AppState.addEventListener('change', (nextAppState) => {
+ console.log("Push:bootstrap:app_state", nextAppState)
+ })
+}
+
+bootstrap_push_notifications()
diff --git a/src/components/auth/AuthBackground.js b/src/components/auth/AuthBackground.js
new file mode 100644
index 00000000..6b4c760c
--- /dev/null
+++ b/src/components/auth/AuthBackground.js
@@ -0,0 +1,127 @@
+import React from 'react'
+import { StyleSheet, View } from 'react-native'
+import { observer } from 'mobx-react'
+import Animated, {
+ Easing,
+ useAnimatedStyle,
+ useSharedValue,
+ withRepeat,
+ withSequence,
+ withTiming,
+} from 'react-native-reanimated'
+
+import App from '../../stores/App'
+
+function with_opacity(hex_color = '', opacity = 1) {
+ const normalized = `${hex_color || ''}`.trim()
+ const match = normalized.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/)
+ if (!match) {
+ return `rgba(255, 136, 0, ${opacity})`
+ }
+
+ let hex = match[1]
+ if (hex.length === 3) {
+ hex = hex.split('').map(char => `${char}${char}`).join('')
+ }
+
+ const r = parseInt(hex.slice(0, 2), 16)
+ const g = parseInt(hex.slice(2, 4), 16)
+ const b = parseInt(hex.slice(4, 6), 16)
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`
+}
+
+function get_background_layers(is_dark, accent) {
+ if (is_dark) {
+ return {
+ base: '#15100b',
+ mid: '#1c140c',
+ glow: with_opacity(accent, 0.14),
+ edge: with_opacity(accent, 0.08),
+ }
+ }
+
+ return {
+ base: '#fffaf0',
+ mid: '#fff5e0',
+ glow: with_opacity(accent, 0.16),
+ edge: with_opacity(accent, 0.1),
+ }
+}
+
+function AuthBackground() {
+ const is_dark = App.is_dark_mode()
+ const accent = App.theme_accent_color()
+ const layers = get_background_layers(is_dark, accent)
+ const glow_shift = useSharedValue(0)
+
+ React.useEffect(() => {
+ glow_shift.value = withRepeat(
+ withSequence(
+ withTiming(1, {
+ duration: 16000,
+ easing: Easing.inOut(Easing.sin),
+ }),
+ withTiming(0, {
+ duration: 16000,
+ easing: Easing.inOut(Easing.sin),
+ }),
+ ),
+ -1,
+ false,
+ )
+ }, [glow_shift])
+
+ const glow_style = useAnimatedStyle(() => {
+ return {
+ opacity: 0.55 + glow_shift.value * 0.2,
+ transform: [
+ { translateX: 10 - glow_shift.value * 24 },
+ { translateY: -14 + glow_shift.value * 28 },
+ { scale: 1.05 + glow_shift.value * 0.06 },
+ ],
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default observer(AuthBackground)
+
+const styles = StyleSheet.create({
+ canvas: {
+ ...StyleSheet.absoluteFillObject,
+ },
+ container: {
+ ...StyleSheet.absoluteFillObject,
+ overflow: 'hidden',
+ },
+ edge: {
+ ...StyleSheet.absoluteFillObject,
+ opacity: 0.55,
+ },
+ glow: {
+ borderRadius: 280,
+ height: 420,
+ position: 'absolute',
+ right: -80,
+ top: -40,
+ width: 420,
+ },
+ mid: {
+ ...StyleSheet.absoluteFillObject,
+ opacity: 0.55,
+ },
+})
diff --git a/src/components/bookmarks/tag_filter_header.js b/src/components/bookmarks/tag_filter_header.js
index ad317cba..9b58426e 100644
--- a/src/components/bookmarks/tag_filter_header.js
+++ b/src/components/bookmarks/tag_filter_header.js
@@ -28,6 +28,8 @@ export default class TagFilterHeader extends React.Component{
height: 26
}}
onPress={() => Auth.selected_user?.set_selected_tag(null)}
+ accessibilityRole="button"
+ accessibilityLabel={`Clear ${Auth.selected_user?.selected_tag} tag filter`}
>
{
Platform.OS === "ios" ?
diff --git a/src/components/cells/temp_upload_cell.js b/src/components/cells/temp_upload_cell.js
index 03169f5c..fd7d1a73 100644
--- a/src/components/cells/temp_upload_cell.js
+++ b/src/components/cells/temp_upload_cell.js
@@ -1,6 +1,6 @@
import * as React from "react";
import { observer } from "mobx-react";
-import { Dimensions, View, Platform } from "react-native";
+import { ActivityIndicator, Animated, Dimensions, View, Platform } from "react-native";
import App from "../../stores/App";
import { MenuView } from "@react-native-menu/menu";
import { SvgXml } from "react-native-svg";
@@ -9,9 +9,95 @@ import MBImage from "../common/MBImage";
@observer
export default class TempUploadCell extends React.Component {
+ constructor(props) {
+ super(props)
+ this.processingOpacity = new Animated.Value(0)
+ this.wasProcessing = false
+ }
+
+ componentDidMount() {
+ this.update_processing_animation(this.is_processing(this.props.upload))
+ }
+
+ componentDidUpdate() {
+ const is_processing = this.is_processing(this.props.upload)
+ this.update_processing_animation(is_processing)
+ }
+
+ progress_for(upload) {
+ const progress = Number(upload.progress)
+ if (!Number.isFinite(progress)) {
+ return 0
+ }
+ return Math.max(0, Math.min(100, progress))
+ }
+
+ is_processing(upload) {
+ return upload?.is_uploading && this.progress_for(upload) >= 100
+ }
+
+ upload_type_label(upload) {
+ if (upload.is_audio()) {
+ return "Audio upload"
+ }
+ if (upload.is_video()) {
+ return "Video upload"
+ }
+ return "Image upload"
+ }
+
+ upload_detail_label(upload) {
+ const name = upload.original_file_name || upload.name || upload.fileName
+ if (name && name.length > 0) {
+ return name
+ }
+
+ try {
+ const uri_path = (upload.uri || "").split("?")[0]
+ const filename = uri_path.split("/").pop() || ""
+ return decodeURIComponent(filename)
+ }
+ catch {
+ return ""
+ }
+ }
+
+ upload_accessibility_label(upload, progress_label) {
+ const detail = this.upload_detail_label(upload)
+ const type = this.upload_type_label(upload)
+ const base_label = detail.length > 0 ? `${type}, ${detail}` : type
+ return upload.is_uploading ? `${base_label}, ${progress_label}` : `${base_label}, upload actions`
+ }
+
+ update_processing_animation(is_processing) {
+ if (is_processing && !this.wasProcessing) {
+ this.processingOpacity.setValue(0)
+ Animated.timing(this.processingOpacity, {
+ toValue: 1,
+ duration: 250,
+ useNativeDriver: true,
+ }).start()
+ }
+ else if (!is_processing && this.wasProcessing) {
+ this.processingOpacity.setValue(0)
+ }
+ this.wasProcessing = is_processing
+ }
+
render() {
const { upload } = this.props;
const dimension = Dimensions.get("screen")?.width / 4 - 10;
+ const preview_uri = upload.cached_uri || upload.uri
+ const destructive_icon_color = App.theme_warning_text_color()
+ const progress = this.progress_for(upload)
+ const progress_width = `${Math.max(progress, progress > 0 ? 4 : 8)}%`
+ const is_processing = this.is_processing(upload)
+ const progress_label = is_processing ? "Processing" : progress > 1 ? `${progress}%` : "Uploading"
+ const accessibility_value = upload.is_uploading ? {
+ min: 0,
+ max: 100,
+ now: Math.round(progress)
+ } : undefined
const actions = [
{
title: "Cancel upload",
@@ -19,6 +105,7 @@ export default class TempUploadCell extends React.Component {
image: Platform.select({
ios: "trash",
}),
+ imageColor: destructive_icon_color,
attributes: {
destructive: true,
},
@@ -37,6 +124,10 @@ export default class TempUploadCell extends React.Component {
}
}}
actions={actions}
+ accessibilityRole="button"
+ accessibilityLabel={this.upload_accessibility_label(upload, progress_label)}
+ accessibilityHint="Shows upload actions"
+ accessibilityValue={accessibility_value}
>
) : (
+ <>
+
+
+
+ {progress_label}
+
+
+
+
+
+ >
)}
diff --git a/src/components/cells/upload_cell.js b/src/components/cells/upload_cell.js
index 98e969e2..8925887e 100644
--- a/src/components/cells/upload_cell.js
+++ b/src/components/cells/upload_cell.js
@@ -15,6 +15,8 @@ export default class UploadCell extends React.Component {
super(props);
this.state = {
did_load: false,
+ use_original_url: false,
+ use_preview_url: false,
num_columns: DeviceInfo.isTablet() ? 4 : 3,
};
}
@@ -23,14 +25,57 @@ export default class UploadCell extends React.Component {
}
componentDidUpdate(prevProps, prevState) {
- if (prevProps.upload?.url !== this.props.upload?.url) {
- this.setState({ did_load: false });
+ const did_change_upload = prevProps.upload?.url !== this.props.upload?.url
+ const did_change_remote_url = this.remote_url_for(prevProps.upload) !== this.remote_url_for(this.props.upload)
+ const did_change_preview_url = this.preview_url_for(prevProps.upload) !== this.preview_url_for(this.props.upload)
+ if (did_change_upload || did_change_remote_url || did_change_preview_url) {
+ this.setState({ did_load: false, use_original_url: false, use_preview_url: false })
}
}
componentWillUnmount() {
}
+ remote_url_for(upload) {
+ return upload?.cdn?.medium || upload?.cdn?.large || upload?.url
+ }
+
+ preview_url_for(upload) {
+ return upload?.preview_uri || null
+ }
+
+ upload_type_label(upload) {
+ if (upload.is_audio()) {
+ return "Audio upload"
+ }
+ if (upload.is_video()) {
+ return "Video upload"
+ }
+ return "Image upload"
+ }
+
+ upload_detail_label(upload) {
+ const alt_text = upload.alt?.trim()
+ if (alt_text && alt_text.length > 0) {
+ return alt_text
+ }
+
+ try {
+ const url_path = (upload.url || "").split("?")[0]
+ const filename = url_path.split("/").pop() || ""
+ return decodeURIComponent(filename)
+ }
+ catch {
+ return ""
+ }
+ }
+
+ upload_accessibility_label(upload) {
+ const detail = this.upload_detail_label(upload)
+ const type = this.upload_type_label(upload)
+ return detail.length > 0 ? `${type}, ${detail}` : type
+ }
+
render_cell(upload) {
const has_poster = upload.poster && upload.poster.length > 0;
const dimension =
@@ -91,10 +136,11 @@ export default class UploadCell extends React.Component {
);
} else {
- let best_url = upload.url;
- if (upload.cdn && upload.cdn.small) {
- best_url = upload.cdn.small;
- }
+ const remote_url = this.remote_url_for(upload)
+ const preview_url = this.preview_url_for(upload)
+ let image_url = this.state.use_original_url ? upload.url : remote_url
+ image_url = this.state.use_preview_url && preview_url ? preview_url : image_url
+ const placeholder_source = preview_url && image_url !== preview_url ? { uri: preview_url } : undefined
return (
{
this.setState({ did_load: true });
}}
+ onError={() => {
+ if (!this.state.use_original_url && image_url !== upload.url) {
+ this.setState({ did_load: false, use_original_url: true })
+ return
+ }
+ if (preview_url && image_url !== preview_url) {
+ this.setState({ did_load: false, use_preview_url: true })
+ }
+ }}
/>
);
@@ -138,6 +198,10 @@ export default class UploadCell extends React.Component {
render() {
const { upload } = this.props;
+ const icon_color = App.theme_text_color()
+ const destructive_icon_color = App.theme_warning_text_color()
+ const upload_label = this.upload_accessibility_label(upload)
+
if (this.props.add_to_editor) {
return (
{this.render_cell(upload)}
@@ -161,6 +228,9 @@ export default class UploadCell extends React.Component {
padding: 5,
backgroundColor: App.theme_background_color_secondary(),
}}
+ accessibilityRole="button"
+ accessibilityLabel={`${upload_label}, actions available`}
+ accessibilityHint="Shows upload actions"
onPressAction={async ({ nativeEvent }) => {
const event_id = nativeEvent.event;
if (event_id === "copy_link") {
@@ -194,6 +264,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "link",
}),
+ imageColor: icon_color,
},
{
title: "Copy HTML",
@@ -201,6 +272,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "curlybraces",
}),
+ imageColor: icon_color,
},
...(upload.is_audio()
? [
@@ -210,6 +282,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "curlybraces",
}),
+ imageColor: icon_color,
},
]
: []),
@@ -221,6 +294,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "textformat",
}),
+ imageColor: icon_color,
},
]
: []),
@@ -230,6 +304,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "photo.on.rectangle",
}),
+ imageColor: icon_color,
},
{
title: "Get Info",
@@ -237,6 +312,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "info.circle",
}),
+ imageColor: icon_color,
},
{
title: "Open in Browser",
@@ -244,6 +320,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "safari",
}),
+ imageColor: icon_color,
},
{
title: "Delete...",
@@ -251,6 +328,7 @@ export default class UploadCell extends React.Component {
image: Platform.select({
ios: "trash",
}),
+ imageColor: destructive_icon_color,
attributes: {
destructive: true,
},
diff --git a/src/components/common/MBImage.js b/src/components/common/MBImage.js
index 26ef079b..1735b593 100644
--- a/src/components/common/MBImage.js
+++ b/src/components/common/MBImage.js
@@ -25,6 +25,8 @@ const MBImage = React.forwardRef(({ contentFit, ...props }, ref) => {
transition,
cachePolicy,
placeholder,
+ placeholderContentFit,
+ recyclingKey,
...restProps
} = props
diff --git a/src/components/discover/tagmoji_bar.js b/src/components/discover/tagmoji_bar.js
index 73d292b3..055d695d 100644
--- a/src/components/discover/tagmoji_bar.js
+++ b/src/components/discover/tagmoji_bar.js
@@ -1,12 +1,11 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import Discover from '../../stores/Discover'
-import { TouchableOpacity, View, Text, Platform, Image, TextInput, Keyboard } from 'react-native';
+import { TouchableOpacity, View, Text, Platform, Image, Keyboard } from 'react-native';
import App from '../../stores/App'
import SearchIcon from '../../assets/icons/nav/discover.png';
import SearchBar from '../../components/search_bar';
import { SFSymbol } from "react-native-sfsymbols";
-import { SvgXml } from 'react-native-svg';
@observer
export default class TagmojiBar extends React.Component{
@@ -54,6 +53,9 @@ export default class TagmojiBar extends React.Component{
borderRadius: 5
}}
onPress={() => App.open_sheet("tagmoji_menu")}
+ accessibilityRole="button"
+ accessibilityLabel={`Choose Discover topic, current topic ${Discover.random_tagmoji}`}
+ accessibilityHint="Shows community post topics"
>
{Discover.random_tagmoji}
@@ -71,6 +73,8 @@ export default class TagmojiBar extends React.Component{
marginRight: 4
}}
onPress={Discover.toggle_search_bar}
+ accessibilityRole="button"
+ accessibilityLabel="Search Discover"
>
{
Platform.OS === "ios" ?
@@ -87,7 +91,7 @@ export default class TagmojiBar extends React.Component{
)
}
+ }
return null
}
- }
-}
\ No newline at end of file
+}
diff --git a/src/components/generic/generic_screen.js b/src/components/generic/generic_screen.js
index 6c7a4c2a..1a7a9c39 100644
--- a/src/components/generic/generic_screen.js
+++ b/src/components/generic/generic_screen.js
@@ -2,7 +2,6 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { View } from 'react-native';
import LoginMessage from '../info/login_message';
-import ImageModalModule from '../images/image_modal';
import WebViewModule from '../web/webview_module';
import App from '../../stores/App';
@@ -21,7 +20,6 @@ export default class GenericScreenComponent extends React.Component{
:
!this.props.is_search && !this.props.is_filtered && !App.is_changing_font_scale && !App.is_loading_bookmarks &&
}
-
)
}
diff --git a/src/components/header/add_bookmark.js b/src/components/header/add_bookmark.js
index 27a1fe5b..e040382c 100644
--- a/src/components/header/add_bookmark.js
+++ b/src/components/header/add_bookmark.js
@@ -5,20 +5,34 @@ import Auth from './../../stores/Auth';
import App from '../../stores/App'
import { SFSymbol } from "react-native-sfsymbols";
import { SvgXml } from 'react-native-svg';
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui';
@observer
export default class AddBookmarkButton extends React.Component{
render() {
if(Auth.selected_user != null && Auth.selected_user.posting?.posting_enabled()){
+ const button_style = isLiquidGlass() ?
+ {
+ width: 28,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
+ :
+ {
+ height: 22,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
+
return(
App.navigate_to_screen("add_bookmark")}
+ accessibilityRole="button"
+ accessibilityLabel="Add bookmark"
>
{
Platform.OS === 'ios' ?
@@ -44,4 +58,4 @@ export default class AddBookmarkButton extends React.Component{
return null
}
-}
\ No newline at end of file
+}
diff --git a/src/components/header/back.js b/src/components/header/back.js
index 69759d77..0bd94a28 100644
--- a/src/components/header/back.js
+++ b/src/components/header/back.js
@@ -1,28 +1,22 @@
import * as React from 'react'
import { observer } from 'mobx-react'
-import { Platform, Pressable } from 'react-native'
+import { Platform, Pressable, TouchableOpacity } from 'react-native'
import App from './../../stores/App'
-import { isLiquidGlass, STANDARD_SLOP } from './../../utils/ui'
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui'
import { SFSymbol } from 'react-native-sfsymbols'
import { SvgXml } from 'react-native-svg'
-import { HeaderBackButton } from '@react-navigation/elements'
import { useNavigation } from '@react-navigation/native'
const BackButtonContent = observer(() => {
const navigation = useNavigation()
- const hit_slop = { top: STANDARD_SLOP, bottom: STANDARD_SLOP, left: STANDARD_SLOP, right: STANDARD_SLOP }
let button_style = {}
if (isLiquidGlass()) {
button_style = {
- marginRight: 0 - STANDARD_SLOP,
- marginLeft: 16 - STANDARD_SLOP,
- marginTop: 2 - STANDARD_SLOP,
- marginBottom: 0 - STANDARD_SLOP,
- padding: STANDARD_SLOP,
- minWidth: 44,
- minHeight: 44,
+ width: 28,
+ height: 28,
flexDirection: 'row',
+ justifyContent: 'center',
alignItems: 'center'
}
}
@@ -72,7 +66,7 @@ const BackButtonContent = observer(() => {
@@ -82,12 +76,15 @@ const BackButtonContent = observer(() => {
}
return (
- back_icon}
- />
+ hitSlop={HEADER_BUTTON_HIT_SLOP}
+ accessibilityRole="button"
+ accessibilityLabel="Back"
+ >
+ {back_icon}
+
)
})
diff --git a/src/components/header/close.js b/src/components/header/close.js
index fd9c5a8b..f2749fa8 100644
--- a/src/components/header/close.js
+++ b/src/components/header/close.js
@@ -2,7 +2,7 @@ import * as React from 'react'
import { observer } from 'mobx-react'
import { Pressable, TouchableOpacity, Platform } from 'react-native'
import App from './../../stores/App'
-import { isLiquidGlass, STANDARD_SLOP } from './../../utils/ui'
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui'
import { SFSymbol } from 'react-native-sfsymbols'
import { SvgXml } from 'react-native-svg'
@@ -10,7 +10,6 @@ import { SvgXml } from 'react-native-svg'
export default class CloseModalButton extends React.Component {
render() {
- const hit_slop = { top: STANDARD_SLOP, bottom: STANDARD_SLOP, left: STANDARD_SLOP, right: STANDARD_SLOP }
let button_style = {
marginLeft: -15,
paddingHorizontal: 12,
@@ -24,10 +23,8 @@ export default class CloseModalButton extends React.Component {
if (isLiquidGlass()) {
button_style = {
- paddingLeft: 7,
- paddingTop: 4,
- minWidth: 44,
- minHeight: 44,
+ width: 28,
+ height: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row'
@@ -60,7 +57,7 @@ export default class CloseModalButton extends React.Component {
@@ -73,7 +70,7 @@ export default class CloseModalButton extends React.Component {
diff --git a/src/components/header/close_post_clear.js b/src/components/header/close_post_clear.js
index 22d64420..324b2257 100644
--- a/src/components/header/close_post_clear.js
+++ b/src/components/header/close_post_clear.js
@@ -2,7 +2,7 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { TouchableOpacity, Platform } from 'react-native';
import App from './../../stores/App';
-import { isLiquidGlass } from './../../utils/ui';
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui';
import { SFSymbol } from 'react-native-sfsymbols';
import { SvgXml } from 'react-native-svg';
@@ -13,8 +13,12 @@ export default class ClosePostClearButton extends React.Component {
let button_style = {};
if (isLiquidGlass()) {
- button_style.paddingLeft = 8;
- button_style.paddingTop = 5;
+ button_style = {
+ width: 28,
+ height: 28,
+ alignItems: 'center',
+ justifyContent: 'center'
+ }
}
return (
@@ -23,6 +27,7 @@ export default class ClosePostClearButton extends React.Component {
App.go_back_and_clear()
}}
style={button_style}
+ hitSlop={HEADER_BUTTON_HIT_SLOP}
>
{
Platform.OS === 'ios' ?
@@ -49,4 +54,4 @@ export default class ClosePostClearButton extends React.Component {
)
}
-}
\ No newline at end of file
+}
diff --git a/src/components/header/new_collection.js b/src/components/header/new_collection.js
index 58d7be58..bc03b108 100644
--- a/src/components/header/new_collection.js
+++ b/src/components/header/new_collection.js
@@ -1,22 +1,33 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import { TouchableOpacity, Platform } from 'react-native';
-import Auth from './../../stores/Auth';
import App from '../../stores/App'
import { SFSymbol } from "react-native-sfsymbols";
import { SvgXml } from 'react-native-svg';
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui';
@observer
export default class NewCollectionButton extends React.Component{
render() {
+ const button_style = isLiquidGlass() ?
+ {
+ width: 28,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
+ :
+ {
+ height: 22,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
+
return (
{
App.navigate_to_screen("AddCollection");
}}
@@ -42,4 +53,4 @@ export default class NewCollectionButton extends React.Component{
)
}
-}
\ No newline at end of file
+}
diff --git a/src/components/header/new_post.js b/src/components/header/new_post.js
index 7737e288..c1833a72 100644
--- a/src/components/header/new_post.js
+++ b/src/components/header/new_post.js
@@ -4,7 +4,7 @@ import { TouchableOpacity, Image, Platform } from 'react-native';
import Auth from './../../stores/Auth';
import PostAddIcon from './../../assets/icons/post_add.png';
import App from '../../stores/App'
-import { isLiquidGlass } from './../../utils/ui';
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui';
import { SFSymbol } from "react-native-sfsymbols";
@observer
@@ -17,13 +17,19 @@ export default class NewPostButton extends React.Component{
};
if (isLiquidGlass()) {
- button_style.paddingLeft = 4;
+ button_style = {
+ width: 28,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
}
if(Auth.selected_user != null && Auth.selected_user.posting?.posting_enabled()){
return (
App.navigate_to_screen("Posting")}
accessibilityRole="button"
accessibilityLabel="New post"
@@ -45,4 +51,4 @@ export default class NewPostButton extends React.Component{
return null
}
-}
\ No newline at end of file
+}
diff --git a/src/components/header/new_upload.js b/src/components/header/new_upload.js
index 4a7e6e15..9280f52e 100644
--- a/src/components/header/new_upload.js
+++ b/src/components/header/new_upload.js
@@ -6,6 +6,7 @@ import App from '../../stores/App'
import { SFSymbol } from "react-native-sfsymbols"
import { MenuView } from '@react-native-menu/menu';
import AddIcon from './../../assets/icons/add.png'
+import { isLiquidGlass } from './../../utils/ui'
@observer
export default class NewUploadButton extends React.Component {
@@ -13,8 +14,20 @@ export default class NewUploadButton extends React.Component {
render() {
if (Auth.selected_user != null && Auth.selected_user.posting?.posting_enabled()) {
const { config } = Auth.selected_user.posting.selected_service
+ const icon_color = App.theme_text_color()
+ const button_style = isLiquidGlass() ?
+ {
+ width: 28,
+ height: 28,
+ alignItems: 'center',
+ justifyContent: 'center'
+ }
+ :
+ undefined
+
return (
{
const event_id = nativeEvent.event
if (event_id === 'upload_media') {
@@ -31,16 +44,21 @@ export default class NewUploadButton extends React.Component {
id: "upload_media",
image: Platform.select({
ios: 'photo'
- })
+ }),
+ imageColor: icon_color
},
{
title: "Files",
id: "upload_file",
image: Platform.select({
ios: 'folder'
- })
+ }),
+ imageColor: icon_color
}
]}
+ accessibilityRole="button"
+ accessibilityLabel="Upload"
+ accessibilityHint="Shows photo library and files options"
>
{
Platform.OS === 'ios' ?
@@ -61,4 +79,4 @@ export default class NewUploadButton extends React.Component {
return null
}
-}
\ No newline at end of file
+}
diff --git a/src/components/header/post_button.js b/src/components/header/post_button.js
index 48a46467..dc313412 100644
--- a/src/components/header/post_button.js
+++ b/src/components/header/post_button.js
@@ -1,27 +1,62 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { Button, Keyboard } from 'react-native';
-import App from './../../stores/App';
-import Auth from '../../stores/Auth';
+import * as React from 'react'
+import { observer } from 'mobx-react'
+import { Button, Keyboard, Platform, Pressable, Text } from 'react-native'
+import App from './../../stores/App'
+import Auth from '../../stores/Auth'
+import { HEADER_BUTTON_HIT_SLOP, isLiquidGlass } from './../../utils/ui'
@observer
export default class PostButton extends React.Component {
- render() {
- const { post_status } = Auth.selected_user?.posting
- return (
-
)
}
diff --git a/src/components/web/loading_banner.js b/src/components/web/loading_banner.js
index e06aefea..c401c317 100644
--- a/src/components/web/loading_banner.js
+++ b/src/components/web/loading_banner.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, useState } from "react"
+import React, { useCallback, useEffect, useRef, useState } from "react"
import { observer } from "mobx-react"
import { View, Text, Animated, Platform } from "react-native"
import App from "../../stores/App"
@@ -9,24 +9,26 @@ const LoadingBanner = observer(({ visible, loading_text = "Loading posts...", to
const sweepAnim = useRef(new Animated.Value(0)).current
const [wasVisible, setWasVisible] = useState(false)
const hasProgress = progress !== undefined && progress !== null && typeof progress === 'number' && !isNaN(progress)
+ const progressValue = hasProgress ? Math.max(0, Math.min(1, progress)) : 0
+ const progressPercent = Math.round(progressValue * 100)
- const slideIn = () => {
+ const slideIn = useCallback(() => {
Animated.spring(slideAnim, {
toValue: 0,
useNativeDriver: true,
tension: 120,
friction: 8,
}).start()
- }
+ }, [slideAnim])
- const slideOut = () => {
+ const slideOut = useCallback(() => {
Animated.spring(slideAnim, {
toValue: -100,
useNativeDriver: true,
tension: 120,
friction: 8,
}).start()
- }
+ }, [slideAnim])
useEffect(() => {
if (visible && !wasVisible) {
@@ -37,13 +39,11 @@ const LoadingBanner = observer(({ visible, loading_text = "Loading posts...", to
slideOut()
setWasVisible(false)
}
- }, [visible, wasVisible])
+ }, [visible, wasVisible, slideIn, slideOut])
useEffect(() => {
if (hasProgress && visible) {
- const progressValue = Math.max(0, Math.min(1, progress));
- const currentValue = progressAnim._value || 0;
- const duration = progressValue === 1 ? 400 : 200;
+ const duration = progressValue === 1 ? 400 : 200
Animated.timing(progressAnim, {
toValue: progressValue,
duration: duration,
@@ -76,7 +76,7 @@ const LoadingBanner = observer(({ visible, loading_text = "Loading posts...", to
sweepAnim.setValue(0)
}
}
- }, [visible, progress])
+ }, [visible, hasProgress, progressAnim, progressValue, sweepAnim])
if (!visible && !wasVisible) {
return null
@@ -84,30 +84,37 @@ const LoadingBanner = observer(({ visible, loading_text = "Loading posts...", to
const defaultTop = Platform.OS === "ios" ? 12 : 8
const top = topOffset !== undefined ? topOffset : defaultTop
+ const is_dark_mode = App.is_dark_mode()
return (
@@ -161,4 +168,3 @@ const LoadingBanner = observer(({ visible, loading_text = "Loading posts...", to
})
export default LoadingBanner
-
diff --git a/src/components/web/loading_view.js b/src/components/web/loading_view.js
index 953c36d7..b0286a2b 100644
--- a/src/components/web/loading_view.js
+++ b/src/components/web/loading_view.js
@@ -9,7 +9,7 @@ export default class WebLoadingViewModule extends React.Component{
render() {
return (
-
+
{this.props.loading_text ?? "Loading posts..."}
)
diff --git a/src/components/web/webview_module.js b/src/components/web/webview_module.js
index 275209ab..278dfb38 100644
--- a/src/components/web/webview_module.js
+++ b/src/components/web/webview_module.js
@@ -1,84 +1,223 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { RefreshControl, Platform, View } from "react-native";
-import { useFocusEffect } from '@react-navigation/native';
-import Auth from '../../stores/Auth';
-import App from '../../stores/App';
-import { ScrollView } from 'react-native-gesture-handler';
-import WebView from 'react-native-webview';
-import WebErrorViewModule from './error_view';
-import LoadingBanner from './loading_banner';
+import * as React from 'react'
+import { observer } from 'mobx-react'
+import { RefreshControl, Platform, View, InteractionManager } from "react-native"
+import { useIsFocused } from '@react-navigation/native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import Auth from '../../stores/Auth'
+import App from '../../stores/App'
+import Reply from '../../stores/Reply'
+import { ScrollView } from 'react-native-gesture-handler'
+import WebView from 'react-native-webview'
+import WebErrorViewModule from './error_view'
+import LoadingBanner from './loading_banner'
+import {
+ build_webview_source_uri,
+ get_base_webview_path,
+ is_signin_webview_path,
+ is_webview_endpoint_url,
+ should_attempt_webview_recovery,
+} from '../../utils/webview_recovery'
+import { tabBarBottomInset } from '../../utils/ui'
+
+const FOCUS_SETTLE_TIMEOUT_MS = 120
const WebViewModule = observer((props) => {
- const webViewRef = React.useRef(null);
- const hasSetDidLoadRef = React.useRef(false);
- const [currentUrl, setCurrentUrl] = React.useState(() => {
- const signinEndpoint = `hybrid/signin?token=${Auth.selected_user.token()}&redirect_to=${props.endpoint}&theme=${App.theme}&show_actions=true`;
- return Auth.did_load_one_or_more_webviews ? props.endpoint : signinEndpoint;
- });
+ const insets = useSafeAreaInsets()
+ const isFocused = useIsFocused()
+ const webViewRef = React.useRef(null)
+ const hasSetDidLoadRef = React.useRef(false)
+ const hasAttemptedRecoveryRef = React.useRef(false)
+ const [is_focus_settled, set_is_focus_settled] = React.useState(isFocused)
const [state, setState] = React.useState({
- endpoint: props.endpoint,
- signin_endpoint: `hybrid/signin?token=${Auth.selected_user.token()}&redirect_to=${props.endpoint}&theme=${App.theme}&show_actions=true`,
opacity: 0.0,
is_pull_to_refresh_enabled: true,
is_loading: true,
is_initial_load: true,
- loading_progress: 0
- });
-
+ loading_progress: 0,
+ })
+
+ const web_url = "https://micro.blog"
+ const web_view_key = `${props.endpoint}:${App.web_view_epoch}`
+ const source_uri = build_webview_source_uri({
+ did_load_one_or_more_webviews: Auth.did_load_one_or_more_webviews,
+ endpoint: props.endpoint,
+ theme: App.theme,
+ token: Auth.selected_user?.token?.() ?? '',
+ web_url,
+ })
+ const should_inject_web_view_padding = Platform.OS === 'ios' || Platform.OS === 'android'
+ const web_view_bottom_padding = tabBarBottomInset(insets.bottom)
+ const web_view_css_properties_javascript = `
+ (() => {
+ const bottom_padding = '${web_view_bottom_padding}px'
+ const apply_bottom_padding = () => {
+ document.documentElement.style.setProperty('--microblog-webview-bottom-padding', bottom_padding)
+
+ if (document.body) {
+ document.body.style.setProperty('padding-bottom', 'var(--microblog-webview-bottom-padding)')
+ }
+ }
+
+ apply_bottom_padding()
+
+ if (!document.body) {
+ document.addEventListener('DOMContentLoaded', apply_bottom_padding, { once: true })
+ }
+ })()
+ true
+ `
+ const web_view_tap_guard_javascript = `
+ void function() {
+ if (window.__microblogTapGuardInstalled) {
+ return
+ }
+
+ window.__microblogTapGuardInstalled = true
+
+ const closest = (target, selector) => {
+ return target && target.closest ? target.closest(selector) : null
+ }
+
+ const open_photo = image => {
+ const src = image?.getAttribute('src')
+ if (!src) {
+ return
+ }
+
+ window.location.href = 'microblog://photo/' + encodeURI(src)
+ }
+
+ const should_ignore_post_touch = event => {
+ const target = event.target
+ return closest(target, 'a[href], button, input, label, select, textarea')
+ }
+
+ document.addEventListener('touchend', event => {
+ if (should_ignore_post_touch(event)) {
+ return
+ }
+
+ const photo = closest(event.target, '.post_text img')
+ if (photo) {
+ event.stopPropagation()
+ open_photo(photo)
+ }
+ }, true)
+ }()
+ true
+ `
+ const web_view_injected_javascript = Platform.OS === 'ios' ? `
+ const meta = document.createElement('meta')
+ meta.setAttribute('content', 'width=width, initial-scale=${App.web_font_scale()}')
+ meta.setAttribute('name', 'viewport')
+ document.getElementsByTagName('head')[0].appendChild(meta)
+ ${web_view_css_properties_javascript}
+ ${web_view_tap_guard_javascript}
+ ` : should_inject_web_view_padding ? `
+ ${web_view_css_properties_javascript}
+ ${web_view_tap_guard_javascript}
+ ` : web_view_tap_guard_javascript
+ const should_render_direct_webview = Platform.OS === 'ios' && props.profile == null
+ const should_use_native_pull_to_refresh = should_render_direct_webview && isFocused && is_focus_settled && !Reply.is_sheet_open
+
React.useEffect(() => {
- if (props.endpoint !== state.endpoint) {
- const newSigninEndpoint = `hybrid/signin?token=${Auth.selected_user.token()}&redirect_to=${props.endpoint}&theme=${App.theme}&show_actions=true`;
- const newUrl = Auth.did_load_one_or_more_webviews ? props.endpoint : newSigninEndpoint;
- setCurrentUrl(newUrl);
- setState(prevState => ({
- ...prevState,
- endpoint: props.endpoint,
- signin_endpoint: newSigninEndpoint,
- loading_progress: 0,
- is_loading: true
- }));
+ let did_cancel = false
+ let settle_timeout = null
+
+ set_is_focus_settled(false)
+
+ if (!isFocused) {
+ return () => {}
}
- }, [props.endpoint]);
- const web_url = "https://micro.blog";
+ const interaction = InteractionManager.runAfterInteractions(() => {
+ settle_timeout = setTimeout(() => {
+ if (!did_cancel) {
+ set_is_focus_settled(true)
+ }
+ }, FOCUS_SETTLE_TIMEOUT_MS)
+ })
- useFocusEffect(
- React.useCallback(() => {
- App.set_current_web_view_ref(webViewRef.current);
- }, [])
- );
+ return () => {
+ did_cancel = true
+ clearTimeout(settle_timeout)
+ interaction?.cancel?.()
+ }
+ }, [isFocused])
+
+ React.useEffect(() => {
+ if (!isFocused) {
+ return
+ }
+
+ const current_ref = webViewRef.current
+ App.set_current_web_view_ref(current_ref)
+ return () => App.clear_current_web_view_ref(current_ref)
+ }, [isFocused, web_view_key])
+
+ React.useEffect(() => {
+ if (!Auth.did_load_one_or_more_webviews) {
+ hasSetDidLoadRef.current = false
+ }
+ })
+
+ React.useEffect(() => {
+ hasAttemptedRecoveryRef.current = false
+ }, [props.endpoint])
+
+ React.useEffect(() => {
+ if (should_inject_web_view_padding) {
+ webViewRef.current?.injectJavaScript(web_view_css_properties_javascript)
+ }
+ }, [should_inject_web_view_padding, web_view_css_properties_javascript])
const on_refresh = () => {
setState(prevState => {
if (!prevState.is_initial_load) {
- return { ...prevState, is_loading: true, opacity: 1.0 };
+ return { ...prevState, is_loading: true, opacity: 1.0 }
}
- return prevState;
- });
- webViewRef.current?.injectJavaScript('window.location.reload();');
- };
-
- const return_url_options = () => {
- let url_options = props.endpoint.includes("#post_") ? "" : `show_actions=true&fontsize=17&theme=${App.theme}`;
- if (url_options && url_options !== "") {
- url_options = `${!props.is_search && !props.is_filtered ? "?" : "&"}${url_options}&theme=${App.theme}`;
- }
- else if(!props.endpoint.includes("#post_")) {
- url_options = `?theme=${App.theme}`;
+
+ return prevState
+ })
+ webViewRef.current?.reload()
+ }
+
+ const trigger_webview_recovery = (url = '') => {
+ if (!should_attempt_webview_recovery({
+ did_load_one_or_more_webviews: Auth.did_load_one_or_more_webviews,
+ has_attempted_recovery: hasAttemptedRecoveryRef.current,
+ url,
+ })) {
+ return false
}
- return url_options;
- };
- const onContentProcessDidTerminate = () => webViewRef.current?.reload();
+ console.log("WebViewModule:trigger_webview_recovery", Platform.OS, url)
+ hasSetDidLoadRef.current = false
+ hasAttemptedRecoveryRef.current = true
+ Auth.invalidate_webview_bootstrap()
+ App.bump_web_view_epoch()
+ setState(prevState => ({
+ ...prevState,
+ is_loading: true,
+ loading_progress: 0,
+ }))
+
+ return true
+ }
+
+ const onContentProcessDidTerminate = () => {
+ console.log("WebViewModule:onContentProcessDidTerminate")
+ webViewRef.current?.reload()
+ }
const _webview = () => {
return (
(
@@ -86,136 +225,156 @@ const WebViewModule = observer((props) => {
)}
onLoadStart={() => {
setState(prevState => {
- return {
- ...prevState,
- is_loading: true,
- loading_progress: 0
- };
- });
+ return {
+ ...prevState,
+ is_loading: true,
+ loading_progress: 0,
+ }
+ })
}}
onLoadProgress={(event) => {
try {
if (event && event.nativeEvent && typeof event.nativeEvent.progress === 'number') {
- const progressValue = Math.max(0, Math.min(1, event.nativeEvent.progress));
+ const progressValue = Math.max(0, Math.min(1, event.nativeEvent.progress))
setState(prevState => {
- return { ...prevState, loading_progress: progressValue };
- });
+ return { ...prevState, loading_progress: progressValue }
+ })
}
} catch (error) {
- console.log("WebViewModule:onLoadProgress:error", error);
+ console.log("WebViewModule:onLoadProgress:error", error)
}
}}
onLoadEnd={(event) => {
- const url = event?.nativeEvent?.url || '';
- const isSigninUrl = url.includes('hybrid/signin');
- const isActualEndpoint = url.includes(props.endpoint);
-
+ const url = event?.nativeEvent?.url || ''
+ const isSigninUrl = is_signin_webview_path(url)
+ const isActualEndpoint = is_webview_endpoint_url({ endpoint: props.endpoint, url })
+
setState(prevState => {
if (isActualEndpoint && !isSigninUrl) {
if (!hasSetDidLoadRef.current && !Auth.did_load_one_or_more_webviews) {
- Auth.set_did_load_one_or_more_webviews();
- hasSetDidLoadRef.current = true;
+ Auth.set_did_load_one_or_more_webviews()
+ hasSetDidLoadRef.current = true
}
-
- const newState = {
- ...prevState,
- loading_progress: 1
- };
-
+
+ hasAttemptedRecoveryRef.current = false
+
+ const newState = {
+ ...prevState,
+ loading_progress: 1,
+ }
+
setTimeout(() => {
- setState(prevState => {
- if (prevState.is_initial_load) {
+ setState(nextState => {
+ if (nextState.is_initial_load) {
if (App.theme == "light") {
- return { ...prevState, opacity: 1.0, is_initial_load: false, is_loading: false };
+ return { ...nextState, opacity: 1.0, is_initial_load: false, is_loading: false }
}
- else {
- setTimeout(() => {
- setState(prev => ({ ...prev, opacity: 1.0, is_initial_load: false, is_loading: false }));
- }, 200);
- return { ...prevState, is_loading: false };
- }
- }
- else {
- webViewRef.current?.injectJavaScript('window.scrollTo({ top: 0, behavior: "smooth" })');
- return { ...prevState, is_loading: false };
+
+ setTimeout(() => {
+ setState(prev => ({ ...prev, opacity: 1.0, is_initial_load: false, is_loading: false }))
+ }, 200)
+
+ return { ...nextState, is_loading: false }
}
- });
- }, 300);
-
- return newState;
+
+ webViewRef.current?.injectJavaScript('window.scrollTo({ top: 0, behavior: "smooth" })')
+ return { ...nextState, is_loading: false }
+ })
+ }, 300)
+
+ return newState
}
-
- return prevState;
- });
+
+ return prevState
+ })
}}
nestedScrollEnabled={true}
onShouldStartLoadWithRequest={(event) => {
- const url = event.url;
-
- // Extract base path without query params for comparison
- const getBasePath = (urlOrEndpoint) => {
- // Remove protocol and domain if present
- let path = urlOrEndpoint.replace(/^https?:\/\/[^\/]+/, '');
- // Remove leading slash
- path = path.replace(/^\//, '');
- // Remove query params and hash
- path = path.split('?')[0].split('#')[0];
- return path;
- };
-
- const urlBasePath = getBasePath(url);
- const endpointBasePath = getBasePath(props.endpoint);
-
- // Allow signin URL to load (needed for initial authentication flow)
+ const url = event.url
+ const urlBasePath = get_base_webview_path(url)
+ const endpointBasePath = get_base_webview_path(props.endpoint)
+
if (urlBasePath === 'hybrid/signin') {
- return true;
+ return true
}
-
- // Allow if it's the same base endpoint (e.g., hybrid/favorites with different tag filters)
+
if (urlBasePath === endpointBasePath) {
- return true;
+ return true
}
-
- // Block navigation for different endpoints
- if (urlBasePath !== endpointBasePath) {
- App.handle_url_from_webview(event.url);
- return false;
- }
-
- return true;
+
+ App.handle_url_from_webview(event.url)
+ return false
}}
onScroll={(e) => {
- if (e.nativeEvent.contentOffset != null && e.nativeEvent.contentOffset.y != null) {
+ App.set_is_scrolling()
+
+ if (!should_render_direct_webview && e.nativeEvent.contentOffset != null && e.nativeEvent.contentOffset.y != null) {
const y = e.nativeEvent.contentOffset.y
- setState(prevState => ({ ...prevState, is_pull_to_refresh_enabled: y <= 0.15 }));
+ const is_pull_to_refresh_enabled = y <= 0.15
+ setState(prevState => {
+ if (prevState.is_pull_to_refresh_enabled === is_pull_to_refresh_enabled) {
+ return prevState
+ }
+
+ return { ...prevState, is_pull_to_refresh_enabled }
+ })
}
- App.set_is_scrolling();
}}
onMessage={(event) => {
- App.handle_web_view_message(event.nativeEvent.data);
+ App.handle_web_view_message(event.nativeEvent.data)
+ }}
+ onError={(event) => {
+ console.log("WebViewModule:onError", event?.nativeEvent)
+ trigger_webview_recovery(event?.nativeEvent?.url ?? source_uri)
+ }}
+ onHttpError={(event) => {
+ console.log("WebViewModule:onHttpError", event?.nativeEvent)
+ trigger_webview_recovery(event?.nativeEvent?.url ?? source_uri)
+ }}
+ onRenderProcessGone={(event) => {
+ console.log("WebViewModule:onRenderProcessGone", event?.nativeEvent)
+ trigger_webview_recovery(event?.nativeEvent?.url ?? source_uri)
}}
style={{ flex: 1, backgroundColor: App.theme_background_color(), opacity: state.opacity }}
- renderError={(name, code, description) => }
- injectedJavaScript={Platform.OS === 'ios' ? `const meta = document.createElement('meta'); meta.setAttribute('content', 'width=width, initial-scale=${App.web_font_scale()}'); meta.setAttribute('name', 'viewport'); document.getElementsByTagName('head')[0].appendChild(meta);` : null}
+ renderError={(name, code, description) => (
+ {
+ hasAttemptedRecoveryRef.current = false
+ webViewRef.current?.reload()
+ }}
+ />
+ )}
+ injectedJavaScript={web_view_injected_javascript}
+ injectedJavaScriptBeforeContentLoaded={should_inject_web_view_padding ? web_view_css_properties_javascript : null}
onContentProcessDidTerminate={onContentProcessDidTerminate}
/>
- );
- };
+ )
+ }
- const profileHeaderRef = React.useRef(null);
- const [profileHeaderHeight, setProfileHeaderHeight] = React.useState(0);
- const is_conversation = props.endpoint.includes("conversation");
+ const [profileHeaderHeight, setProfileHeaderHeight] = React.useState(0)
+ const is_conversation = props.endpoint.includes("conversation")
+ const loading_banner = !is_conversation ? (
+
+ ) : null
+
+ if (should_render_direct_webview) {
+ return (
+ <>
+ {loading_banner}
+ {_webview()}
+ >
+ )
+ }
return (
<>
- {!is_conversation && (
-
- )}
+ {loading_banner}
{
>
{
props.profile != null ?
- {
- const { height } = event.nativeEvent.layout;
- if (height > 0) {
- setProfileHeaderHeight(height);
- }
- }}
- >
- {props.profile}
-
+ {
+ const { height } = event.nativeEvent.layout
+ if (height > 0) {
+ setProfileHeaderHeight(height)
+ }
+ }}
+ >
+ {props.profile}
+
: null
}
{_webview()}
>
- );
-});
+ )
+})
-export default WebViewModule;
+export default WebViewModule
diff --git a/src/index.js b/src/index.js
index 4d9f4b32..d33f46f5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,6 @@
import { AppRegistry } from 'react-native';
+import * as WebBrowser from 'expo-web-browser'
+import './bootstrap/push_notifications'
import MainApp from './screens/App';
import { name as appName } from './../app.json';
import './utils/dev';
@@ -6,4 +8,6 @@ import './utils/string_checker';
import './utils/snapshots';
import './utils/string_utils';
-AppRegistry.registerComponent(appName, () => MainApp);
\ No newline at end of file
+WebBrowser.maybeCompleteAuthSession()
+
+AppRegistry.registerComponent(appName, () => MainApp);
diff --git a/src/screens/App.js b/src/screens/App.js
index e692428e..05b04379 100644
--- a/src/screens/App.js
+++ b/src/screens/App.js
@@ -12,8 +12,11 @@ import {
initialWindowMetrics,
} from "react-native-safe-area-context";
import App from './../stores/App';
+import Auth from './../stores/Auth';
import TabNavigator from './stacks/TabNavigator';
+import AuthNavigator from './stacks/AuthNavigator';
import LoginScreen from './login/login';
+import AppleUsernameScreen from './login/apple_username';
import ReplyScreen from './conversation/reply';
import CloseModalButton from '../components/header/close';
import Reply from '../stores/Reply';
@@ -28,6 +31,7 @@ import ReplyEditScreen from './replies/edit';
import UpdateReplyButton from '../components/header/update_reply';
import PageEditScreen from './pages/edit';
import UpdatePageButton from '../components/header/update_page';
+import BackButton from '../components/header/back'
import Posting from './stacks/PostingStack';
import PostEditStack from './stacks/PostEditStack';
@@ -37,9 +41,13 @@ import Share from '../stores/Share'
import ShareMenu from 'react-native-share-menu'
import ShareScreen from './share'
import PublishingProgress from '../components/generic/publishing_progress'
+import ImageModalModule from '../components/images/image_modal'
+import { android_status_bar_options, android_uses_translucent_status_bar, headerLeftElement, headerRightElement } from '../utils/navigation'
const Stack = createNativeStackNavigator();
+const renderBackButton = () =>
+
@observer
export default class MainApp extends React.Component {
state = { current_route: null }
@@ -96,7 +104,9 @@ export default class MainApp extends React.Component {
}
render() {
+ const is_logged_in = Auth.is_logged_in()
const is_reply_pane_open = (this.state.current_route === "Reply") || Reply.is_sheet_open
+ const android_translucent_status_bar = Platform.OS === 'android' && android_uses_translucent_status_bar()
if (App.is_loading) {
return (
@@ -104,20 +114,36 @@ export default class MainApp extends React.Component {
)
}
+
+ if (!is_logged_in) {
+ return (
+
+
+
+
+
+
+
+ )
+ }
+
return (
-
+
{
Platform.OS === 'android' &&
}
{
+ App.set_navigation_ready(true)
+ }}
onStateChange={(state) => {
const route = state?.routes?.[state.index]
const nested = route?.state?.routes?.[route.state?.index]
@@ -141,7 +167,8 @@ export default class MainApp extends React.Component {
initialRouteName="Tabs"
screenOptions={{
headerShown: false,
- headerTintColor: App.theme_text_color()
+ headerTintColor: App.theme_text_color(),
+ ...android_status_bar_options()
}}
>
@@ -150,9 +177,31 @@ export default class MainApp extends React.Component {
name="Login"
component={LoginScreen}
options={{
- title: "Sign In",
+ title: "",
+ headerTitle: () => null,
+ headerShown: true,
+ headerBackVisible: false,
+ headerBackTitleVisible: false,
+ headerTransparent: true,
+ headerShadowVisible: false,
+ headerStyle: {
+ backgroundColor: 'transparent'
+ },
+ ...headerLeftElement(renderBackButton)
+ }}
+ />
+
+ ...headerLeftElement(() => )
}}
>
({
headerTitle: "New Reply",
gestureEnabled: true,
- headerRight: () =>
+ ...headerRightElement(() => )
})}
/>
+ ...headerRightElement(() => )
}}
/>
,
- headerRight: () =>
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => )
}}
/>
+
diff --git a/src/screens/conversation/conversation.js b/src/screens/conversation/conversation.js
index 7f90d1af..68e27377 100644
--- a/src/screens/conversation/conversation.js
+++ b/src/screens/conversation/conversation.js
@@ -1,7 +1,6 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import Auth from '../../stores/Auth';
-import Reply from '../../stores/Reply'
import GenericScreenComponent from '../../components/generic/generic_screen'
import App from '../../stores/App'
@@ -11,7 +10,7 @@ export default class ConversationScreen extends React.Component{
render() {
return (
-
+
: null
}
diff --git a/src/screens/discover/discover.js b/src/screens/discover/discover.js
index 6c7fafad..0a9805c5 100644
--- a/src/screens/discover/discover.js
+++ b/src/screens/discover/discover.js
@@ -1,6 +1,7 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import Auth from './../../stores/Auth';
+import App from './../../stores/App';
import TagmojiBar from '../../components/discover/tagmoji_bar'
import Discover from '../../stores/Discover'
import GenericScreenComponent from '../../components/generic/generic_screen';
diff --git a/src/screens/help/help.js b/src/screens/help/help.js
index 7b31fd98..6bf5d82e 100644
--- a/src/screens/help/help.js
+++ b/src/screens/help/help.js
@@ -9,7 +9,10 @@ export default class HelpScreen extends React.Component{
render() {
return(
-
+
Need help with your blog or another issue on Micro.blog? Email us:
@@ -64,8 +67,8 @@ export default class HelpScreen extends React.Component{
-
- Version { getVersion() } (build { getBuildNumber() })
+
+ Version { getVersion() } (build { getBuildNumber() })
)
diff --git a/src/screens/login/apple_username.js b/src/screens/login/apple_username.js
new file mode 100644
index 00000000..6d433e3c
--- /dev/null
+++ b/src/screens/login/apple_username.js
@@ -0,0 +1,287 @@
+import * as React from 'react'
+import { observer } from 'mobx-react'
+import {
+ View,
+ Text,
+ TextInput,
+ ActivityIndicator,
+ Platform,
+ Keyboard,
+ Pressable,
+ StyleSheet,
+ ScrollView,
+} from 'react-native'
+import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
+import { SafeAreaView } from 'react-native-safe-area-context'
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withSpring,
+} from 'react-native-reanimated'
+import Login from './../../stores/Login'
+import App from '../../stores/App'
+import AuthBackground from '../../components/auth/AuthBackground'
+
+@observer
+export default class AppleUsernameScreen extends React.Component {
+ componentWillUnmount() {
+ Login.reset_apple_credentials()
+ }
+
+ render() {
+ const is_dark = App.is_dark_mode()
+ const accent = App.theme_accent_color()
+ const ink = is_dark ? '#fff7e8' : '#24180d'
+ const ink_soft = is_dark ? '#d9c0a8' : '#756657'
+ const line = is_dark ? 'rgba(255, 136, 0, 0.22)' : 'rgba(255, 136, 0, 0.2)'
+ const input_bg = is_dark ? '#1a120c' : '#fffaf0'
+ const canvas = is_dark ? '#15100b' : '#fffaf0'
+ const paper = is_dark ? '#21180f' : '#ffffff'
+
+ return (
+
+
+
+
+
+
+
+ Create account
+
+
+ Pick a username
+
+
+ Finish creating your Micro.blog account to continue.
+
+
+
+
+
+ Login.set_apple_username(text)}
+ onSubmitEditing={() => {
+ Login.register_apple_username()
+ Keyboard.dismiss()
+ }}
+ placeholder="Username"
+ placeholderTextColor={ink_soft}
+ returnKeyType="go"
+ selectionColor={accent}
+ style={[styles.input, { color: ink }]}
+ textContentType="username"
+ underlineColorAndroid="transparent"
+ value={Login.apple_username}
+ />
+
+
+ {
+ Login.register_apple_username()
+ Keyboard.dismiss()
+ }}
+ paper={paper}
+ />
+
+ {
+ Login.is_loading &&
+
+ }
+
+
+ Micro.blog will create a new hosted microblog for you to try. You can also use Micro.blog for free with an existing blog.
+
+
+
+
+
+
+ )
+ }
+}
+
+function PrimaryButton({
+ accent,
+ disabled = false,
+ is_dark = false,
+ is_loading = false,
+ label,
+ onPress,
+ paper,
+}) {
+ const scale = useSharedValue(1)
+
+ const animated_style = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ }
+ }, [])
+
+ function handle_press_in() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(0.985, {
+ damping: 18,
+ stiffness: 240,
+ })
+ }
+
+ function handle_press_out() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(1, {
+ damping: 16,
+ stiffness: 220,
+ })
+ }
+
+ return (
+
+ [
+ styles.primaryButton,
+ {
+ backgroundColor: disabled ? (is_dark ? '#2d2115' : '#fff3d2') : accent,
+ borderColor: disabled ? (is_dark ? 'rgba(255,136,0,0.15)' : 'rgba(255,136,0,0.18)') : 'transparent',
+ borderWidth: disabled ? 1 : 0,
+ opacity: pressed && !disabled ? 0.96 : 1,
+ },
+ ]}
+ >
+ {is_loading ? (
+
+ ) : null}
+
+ {label}
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ body: {
+ fontSize: 17,
+ lineHeight: 25,
+ maxWidth: 340,
+ },
+ content: {
+ flexGrow: 1,
+ gap: 28,
+ justifyContent: 'center',
+ paddingBottom: 28,
+ paddingHorizontal: 24,
+ paddingTop: 24,
+ },
+ flex: {
+ flex: 1,
+ },
+ footerNote: {
+ fontSize: 14,
+ fontWeight: '400',
+ lineHeight: 21,
+ textAlign: 'center',
+ },
+ form: {
+ gap: 14,
+ },
+ heroCopy: {
+ gap: 12,
+ },
+ input: {
+ fontSize: 17,
+ height: 52,
+ lineHeight: 22,
+ paddingVertical: 0,
+ },
+ inputWrap: {
+ borderCurve: 'continuous',
+ borderRadius: 18,
+ borderWidth: 1,
+ justifyContent: 'center',
+ paddingHorizontal: 16,
+ },
+ kicker: {
+ fontSize: 16,
+ fontWeight: '800',
+ lineHeight: 21,
+ },
+ loading: {
+ marginTop: 4,
+ },
+ primaryButton: {
+ alignItems: 'center',
+ borderCurve: 'continuous',
+ borderRadius: 22,
+ flexDirection: 'row',
+ gap: 10,
+ justifyContent: 'center',
+ minHeight: 56,
+ paddingHorizontal: 16,
+ },
+ primaryButtonLabel: {
+ fontSize: 17,
+ fontWeight: '800',
+ lineHeight: 22,
+ },
+ safeArea: {
+ flex: 1,
+ },
+ screen: {
+ flex: 1,
+ },
+ title: {
+ fontSize: 26,
+ fontWeight: '800',
+ lineHeight: 32,
+ maxWidth: 360,
+ },
+})
diff --git a/src/screens/login/login.js b/src/screens/login/login.js
index e70991c6..f6eefa90 100644
--- a/src/screens/login/login.js
+++ b/src/screens/login/login.js
@@ -1,118 +1,847 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { View, Text, TextInput, Button, ActivityIndicator, Platform, Keyboard, TouchableOpacity } from 'react-native';
-import { KeyboardAvoidingView } from "react-native-keyboard-controller";
-import Login from './../../stores/Login';
+import * as React from 'react'
+import { observer } from 'mobx-react'
+import {
+ View,
+ Text,
+ TextInput,
+ ActivityIndicator,
+ Platform,
+ TouchableOpacity,
+ Alert,
+ Pressable,
+ ScrollView,
+ StyleSheet,
+ Modal,
+ KeyboardAvoidingView as RNKeyboardAvoidingView,
+} from 'react-native'
+import { Image } from 'expo-image'
+import { SafeAreaView } from 'react-native-safe-area-context'
+import { appleAuth } from '@invertase/react-native-apple-authentication'
+import { SFSymbol } from 'react-native-sfsymbols'
+import Animated, {
+ Easing,
+ FadeInUp,
+ useAnimatedStyle,
+ useSharedValue,
+ withDelay,
+ withSpring,
+ withTiming,
+} from 'react-native-reanimated'
+import Login from './../../stores/Login'
import App from '../../stores/App'
+import Auth from '../../stores/Auth'
+import AuthBackground from '../../components/auth/AuthBackground'
+
+const APP_ICON = require('../../assets/app_icon.png')
+const MICRO_BLOG_LOGO = require('../../assets/mb_logo.png')
+const MICRO_BLOG_ORANGE = '#ff8800'
+const FOOTER_ENTRANCE_DELAY_MS = 420
@observer
-export default class LoginScreen extends React.Component{
-
+export default class LoginScreen extends React.Component {
+ apple_credential_revoked_unsubscribe = null
+ did_request_dismiss = false
+ state = {
+ is_token_modal_visible: false,
+ }
+
+ componentDidMount() {
+ if (Platform.OS === 'ios' && appleAuth.isSupported) {
+ this.apple_credential_revoked_unsubscribe = appleAuth.onCredentialRevoked(async () => {
+ console.warn('Apple credentials revoked')
+ })
+ }
+ this.dismiss_if_login_finished()
+ }
+
+ componentDidUpdate() {
+ this.dismiss_if_login_finished()
+ }
+
+ componentWillUnmount() {
+ if (this.apple_credential_revoked_unsubscribe != null) {
+ this.apple_credential_revoked_unsubscribe()
+ this.apple_credential_revoked_unsubscribe = null
+ }
+ }
+
+ dismiss_if_login_finished() {
+ const should_dismiss = !!(
+ !this.did_request_dismiss &&
+ Login.did_trigger_login_from_url &&
+ !Login.is_loading &&
+ !Login.show_error &&
+ Auth.is_logged_in() &&
+ !Auth.is_selecting_user
+ )
+
+ if (should_dismiss) {
+ this.did_request_dismiss = true
+ Login.reset()
+ if (this.has_signed_in_tabs_route()) {
+ App.reset_to_tabs()
+ }
+ }
+ }
+
+ has_signed_in_tabs_route() {
+ const navigation = App.navigation()
+ if (navigation == null) {
+ return false
+ }
+
+ const state = navigation.getRootState?.() || navigation.getState?.()
+ return !!state?.routes?.some(route => route.name === 'Tabs')
+ }
+
+ is_add_account_presentation() {
+ return Auth.is_logged_in()
+ }
+
+ open_token_modal = () => {
+ Login.clear_error()
+ Login.set_input_value('')
+ this.setState({
+ is_token_modal_visible: true,
+ })
+ }
+
+ close_token_modal = () => {
+ if (Login.is_loading) {
+ return
+ }
+
+ Login.clear_error()
+ Login.set_input_value('')
+ this.setState({
+ is_token_modal_visible: false,
+ })
+ }
+
+ handle_credential_value_change = (value = '') => {
+ Login.set_input_value(value)
+ }
+
+ handle_credential_submit = async () => {
+ const did_complete = await Login.submit_email_or_token(false)
+
+ if (did_complete) {
+ this.setState({
+ is_token_modal_visible: false,
+ })
+ Login.set_input_value('')
+ }
+ }
+
+ on_apple_button_press = async () => {
+ try {
+ Login.reset_apple_credentials()
+ const apple_auth_request_response = await appleAuth.performRequest({
+ requestedOperation: appleAuth.Operation.LOGIN,
+ requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL]
+ })
+
+ const credential_state = await appleAuth.getCredentialStateForUser(apple_auth_request_response.user)
+ if (credential_state === appleAuth.State.AUTHORIZED) {
+ const full_name = [
+ apple_auth_request_response.fullName?.givenName,
+ apple_auth_request_response.fullName?.familyName
+ ].filter(name => name != null && name.length > 0).join(' ')
+
+ await Login.login_with_apple_credentials({
+ user_id: apple_auth_request_response.user,
+ identity_token: apple_auth_request_response.identityToken,
+ email: apple_auth_request_response.email,
+ full_name
+ })
+ }
+ }
+ catch (error) {
+ if (error?.code !== appleAuth.Error.CANCELED) {
+ Alert.alert('Ooops', 'An error occured whilst trying to sign you in with Apple. Please try again.')
+ }
+ }
+ }
+
render() {
- return(
-
-
- Enter your email address to sign in to Micro.blog:
- Login.set_input_value(text)}
- onSubmitEditing={() => {Login.trigger_login(); Keyboard.dismiss()}}
- value={Login.input_value}
- />
- {Login.trigger_login(); Keyboard.dismiss()}}
- disabled={!Login.can_submit()}
+ const should_disable_controls = !!(
+ Login.did_trigger_login_from_url &&
+ !Login.is_loading &&
+ !Login.show_error &&
+ Auth.is_logged_in() &&
+ !Auth.is_selecting_user
+ )
+ const is_add_account = this.is_add_account_presentation()
+ const is_dark = App.is_dark_mode()
+ const accent = MICRO_BLOG_ORANGE
+ const ink = is_dark ? '#fff7e8' : '#24180d'
+ const ink_soft = is_dark ? '#d9c0a8' : '#756657'
+ const paper = is_dark ? '#21180f' : '#ffffff'
+ const line = is_dark ? 'rgba(255, 136, 0, 0.22)' : 'rgba(255, 136, 0, 0.2)'
+ const input_bg = is_dark ? '#1a120c' : '#fffaf0'
+ const canvas = is_dark ? '#15100b' : '#fffaf0'
+ const content_style = [
+ styles.content,
+ is_add_account ? styles.contentWithHeader : null,
+ ]
+ const footer_error_message = this.state.is_token_modal_visible ? null : (
+ Login.show_error ? Login.error_message : null
+ )
+ const modal_error_message = this.state.is_token_modal_visible ? (
+ Login.show_error ? Login.error_message : null
+ ) : null
+
+ return (
+
+
+
+
+
+ {
+ !is_add_account &&
+
+ }
+
+
+ Micro.blog
+
+
+ {
+ is_add_account
+ ? 'Add another account'
+ : 'Personal blogging, as social as you want.'
+ }
+
+
+ {
+ is_add_account
+ ? 'Sign in with Micro.blog to add an account.'
+ : 'Sign in with Micro.blog to get started.'
+ }
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+const LoginFooter = observer(function LoginFooter({
+ accent,
+ error_message = null,
+ ink,
+ ink_soft,
+ is_dark,
+ on_apple_press,
+ on_long_press_sign_in,
+ on_press_sign_in,
+ paper,
+}) {
+ const footer_opacity = useSharedValue(0)
+ const footer_translate_y = useSharedValue(26)
+
+ React.useEffect(() => {
+ footer_opacity.value = withDelay(
+ FOOTER_ENTRANCE_DELAY_MS,
+ withTiming(1, {
+ duration: 320,
+ easing: Easing.out(Easing.cubic),
+ }),
+ )
+ footer_translate_y.value = withDelay(
+ FOOTER_ENTRANCE_DELAY_MS,
+ withTiming(0, {
+ duration: 360,
+ easing: Easing.out(Easing.cubic),
+ }),
+ )
+ }, [footer_opacity, footer_translate_y])
+
+ const footer_animated_style = useAnimatedStyle(() => {
+ return {
+ opacity: footer_opacity.value,
+ transform: [{ translateY: footer_translate_y.value }],
+ }
+ }, [])
+
+ return (
+
+ {error_message ? (
+
+ {error_message}
+
+ ) : null}
+
+
+
+ {
+ Platform.OS === 'ios' && appleAuth.isSupported &&
+ { if (!Login.is_loading) { on_apple_press() } }}
+ />
+ }
+
+
+
+ By using the app you accept our{' '}
+
+ App.open_url(App.terms_url)}>
+ terms of service
+
+ ,
+ App.open_url(App.privacy_url)}>
+ privacy policy
+
+ , and
+ App.open_url(App.guidelines_url)}>
+ community guidelines
+
+ .
+
+
+ )
+})
+
+function AppleSignInButton({
+ disabled = false,
+ is_dark = false,
+ onPress,
+}) {
+ const scale = useSharedValue(1)
+ const background_color = is_dark ? '#ffffff' : '#000000'
+ const content_color = is_dark ? '#000000' : '#ffffff'
+
+ const animated_style = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ }
+ }, [])
+
+ function handle_press_in() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(0.985, {
+ damping: 18,
+ stiffness: 240,
+ })
+ }
+
+ function handle_press_out() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(1, {
+ damping: 16,
+ stiffness: 220,
+ })
+ }
+
+ return (
+
+ [
+ styles.primaryButton,
+ styles.appleButton,
+ {
+ backgroundColor: background_color,
+ opacity: disabled ? 0.55 : (pressed ? 0.96 : 1),
+ },
+ ]}
+ >
+
+
+
+
+ Sign in with Apple
+
+
+
+ )
+}
+
+function PrimaryButton({
+ accessibilityHint = null,
+ accent,
+ disabled = false,
+ is_dark = false,
+ is_loading = false,
+ label,
+ leading_icon_source = null,
+ onLongPress,
+ onPress,
+ paper,
+ variant = 'solid',
+}) {
+ const scale = useSharedValue(1)
+ const did_long_press_ref = React.useRef(false)
+ const is_light = variant === 'light'
+ const should_show_leading_icon = leading_icon_source != null
+ const solid_orange = accent || MICRO_BLOG_ORANGE
+ const label_color = disabled
+ ? (is_dark ? '#d9c0a8' : '#756657')
+ : (is_light ? (is_dark ? '#fff7e8' : '#24180d') : '#ffffff')
+ const spinner_color = is_light ? solid_orange : '#ffffff'
+
+ const animated_style = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ }
+ }, [])
+
+ function handle_press_in() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(0.985, {
+ damping: 18,
+ stiffness: 240,
+ })
+ }
+
+ function handle_press_out() {
+ if (disabled) {
+ return
+ }
+ scale.value = withSpring(1, {
+ damping: 16,
+ stiffness: 220,
+ })
+
+ if (did_long_press_ref.current) {
+ setTimeout(() => {
+ did_long_press_ref.current = false
+ }, 0)
+ }
+ }
+
+ function handle_press(event) {
+ if (disabled) {
+ return
+ }
+
+ if (did_long_press_ref.current) {
+ did_long_press_ref.current = false
+ return
+ }
+
+ onPress?.(event)
+ }
+
+ function handle_long_press(event) {
+ if (disabled || !onLongPress) {
+ return
+ }
+
+ did_long_press_ref.current = true
+ onLongPress(event)
+ }
+
+ return (
+
+ [
+ styles.primaryButton,
{
- Login.is_loading &&
-
+ {should_show_leading_icon ? (
+
+ {is_loading ? (
+
+ ) : (
+
+ )}
+
+ ) : (
+ is_loading ? : null
+ )}
+
+ {label}
+
+
+
+ )
+}
+
+function TokenSignInModal({
+ accent,
+ can_submit = false,
+ error_message = null,
+ ink,
+ ink_soft,
+ input_bg,
+ input_value = '',
+ is_dark,
+ is_signing_in = false,
+ line,
+ onCancel,
+ onChangeValue,
+ onSubmit,
+ paper,
+ submit_label = 'Continue',
+ visible = false,
+}) {
+ return (
+
+
+
+
+
+
+ Sign in with email or token
+
+
+ Enter your email address for a sign-in link, or paste a Micro.blog app token from your account page.
+
+
+
+ {
+ if (can_submit && !is_signing_in) {
+ onSubmit()
+ }
}}
+ placeholder="Email or app token"
+ placeholderTextColor={ink_soft}
+ returnKeyType="go"
+ selectionColor={accent}
+ style={[styles.modalInput, { color: ink }]}
+ textContentType="emailAddress"
+ value={input_value}
/>
- }
-
-
- By using the app you accept our
- App.open_url(App.terms_url)}>
- terms of service
-
- ,
- App.open_url(App.privacy_url)}>
- privacy policy
-
- , and
- App.open_url(App.guidelines_url)}>
- community guidelines
-
- .
+
+
+ {error_message ? (
+
+ {error_message}
+
+ ) : null}
+
+
+
+ [
+ styles.modalCancelAction,
+ pressed && !is_signing_in ? styles.pressed : null,
+ ]}
+ >
+
+ Cancel
+
+
-
- )
- }
-
+
+
+ )
}
+
+const styles = StyleSheet.create({
+ appIcon: {
+ borderRadius: 22,
+ height: 76,
+ width: 76,
+ },
+ appleButton: {
+ borderWidth: 0,
+ },
+ body: {
+ fontSize: 17,
+ lineHeight: 25,
+ maxWidth: 340,
+ },
+ content: {
+ flexGrow: 1,
+ gap: 32,
+ justifyContent: 'space-between',
+ paddingBottom: 24,
+ paddingHorizontal: 24,
+ paddingTop: 28,
+ },
+ contentWithHeader: {
+ paddingTop: 64,
+ },
+ errorMessage: {
+ fontSize: 14,
+ fontWeight: '600',
+ lineHeight: 20,
+ },
+ footer: {
+ gap: 14,
+ minHeight: 112,
+ paddingBottom: 4,
+ },
+ hero: {
+ gap: 22,
+ paddingTop: 8,
+ },
+ heroCopy: {
+ gap: 12,
+ },
+ kicker: {
+ fontSize: 16,
+ fontWeight: '800',
+ lineHeight: 21,
+ },
+ legal: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ marginTop: 4,
+ },
+ legalLink: {
+ fontSize: 13,
+ fontWeight: '700',
+ textDecorationLine: 'underline',
+ },
+ legalText: {
+ fontSize: 13,
+ fontWeight: '400',
+ lineHeight: 19,
+ textAlign: 'center',
+ },
+ modalActions: {
+ gap: 10,
+ },
+ modalBackdrop: {
+ ...StyleSheet.absoluteFillObject,
+ },
+ modalBody: {
+ fontSize: 16,
+ lineHeight: 23,
+ },
+ modalCancelAction: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ minHeight: 32,
+ },
+ modalCancelText: {
+ fontSize: 15,
+ fontWeight: '700',
+ },
+ modalCard: {
+ borderCurve: 'continuous',
+ borderRadius: 26,
+ borderWidth: 1,
+ gap: 16,
+ padding: 20,
+ },
+ modalError: {
+ fontSize: 14,
+ fontWeight: '600',
+ lineHeight: 20,
+ },
+ modalInput: {
+ fontSize: 16,
+ height: 50,
+ lineHeight: 22,
+ paddingVertical: 0,
+ },
+ modalInputWrap: {
+ borderCurve: 'continuous',
+ borderRadius: 17,
+ borderWidth: 1,
+ justifyContent: 'center',
+ paddingHorizontal: 16,
+ },
+ modalRoot: {
+ backgroundColor: 'rgba(18, 13, 7, 0.2)',
+ flex: 1,
+ justifyContent: 'center',
+ paddingHorizontal: 20,
+ },
+ modalTitle: {
+ fontSize: 26,
+ fontWeight: '800',
+ lineHeight: 31,
+ },
+ pressed: {
+ opacity: 0.72,
+ },
+ primaryButton: {
+ alignItems: 'center',
+ borderCurve: 'continuous',
+ borderRadius: 22,
+ flexDirection: 'row',
+ gap: 12,
+ justifyContent: 'center',
+ minHeight: 56,
+ paddingHorizontal: 16,
+ },
+ primaryButtonSolidShadow: {
+ boxShadow: '0 10px 18px rgba(255, 136, 0, 0.28)',
+ },
+ primaryButtonIcon: {
+ alignItems: 'center',
+ height: 26,
+ justifyContent: 'center',
+ width: 26,
+ },
+ primaryButtonLabel: {
+ fontSize: 17,
+ fontWeight: '800',
+ lineHeight: 22,
+ },
+ primaryButtonLogo: {
+ height: 26,
+ width: 26,
+ },
+ safeArea: {
+ flex: 1,
+ },
+ screen: {
+ flex: 1,
+ },
+ title: {
+ fontSize: 26,
+ fontWeight: '800',
+ lineHeight: 32,
+ maxWidth: 360,
+ },
+})
diff --git a/src/screens/mentions/mentions.js b/src/screens/mentions/mentions.js
index da01d78c..88b9c681 100644
--- a/src/screens/mentions/mentions.js
+++ b/src/screens/mentions/mentions.js
@@ -1,6 +1,7 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import Auth from './../../stores/Auth';
+import App from './../../stores/App';
import GenericScreenComponent from '../../components/generic/generic_screen'
@observer
diff --git a/src/screens/pages/edit.android.js b/src/screens/pages/edit.android.js
index efa1b8ee..7fb1daad 100644
--- a/src/screens/pages/edit.android.js
+++ b/src/screens/pages/edit.android.js
@@ -5,6 +5,7 @@ import { KeyboardAvoidingView, KeyboardStickyView } from "react-native-keyboard-
import Auth from '../../stores/Auth';
import App from '../../stores/App';
import PostToolbar from '../../components/keyboard/post_toolbar';
+import HighlightingText from '../../components/text/highlighting_text';
import LoadingComponent from '../../components/generic/loading';
@observer
@@ -24,7 +25,8 @@ export default class PageEditScreen extends React.Component{
fontSize: 18,
justifyContent: 'flex-start',
alignItems: 'flex-start',
- padding: 8,
+ minHeight: 56,
+ padding: 13,
marginBottom: 4,
fontWeight: '700',
borderColor: App.theme_border_color(),
@@ -46,12 +48,17 @@ export default class PageEditScreen extends React.Component{
/>
: null
}
- 0 ? 90 : 50,
}}
@@ -66,7 +73,8 @@ export default class PageEditScreen extends React.Component{
enablesReturnKeyAutomatically={true}
underlineColorAndroid={'transparent'}
value={posting.post_text}
- onChangeText={(text) => !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null}
+ selection={posting.text_selection_flat}
+ onChangeText={({ nativeEvent: { text } }) => !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null}
onSelectionChange={({ nativeEvent: { selection } }) => {
posting.set_text_selection(selection)
}}
diff --git a/src/screens/pages/edit.ios.js b/src/screens/pages/edit.ios.js
index f3e67f00..aeabe983 100644
--- a/src/screens/pages/edit.ios.js
+++ b/src/screens/pages/edit.ios.js
@@ -1,20 +1,54 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { TextInput } from 'react-native';
+import { TextInput, View } from 'react-native';
import { KeyboardStickyView } from "react-native-keyboard-controller";
import Auth from '../../stores/Auth';
import App from '../../stores/App'
+import EditorKeyboardAvoidingView from '../../components/keyboard/editor_keyboard_avoiding_view'
import PostToolbar from '../../components/keyboard/post_toolbar'
import HighlightingText from '../../components/text/highlighting_text';
import LoadingComponent from '../../components/generic/loading';
@observer
export default class PageEditScreen extends React.Component{
+ constructor(props) {
+ super(props)
+ this.state = {
+ editor_is_visible: false
+ }
+ this.text_editor_ref = React.createRef()
+ this.show_editor_timeout = null
+ }
+
+ componentDidMount() {
+ this.show_editor_timeout = setTimeout(() => {
+ this.setState({
+ editor_is_visible: true
+ }, () => {
+ requestAnimationFrame(() => {
+ this.text_editor_ref.current?.focus({ cursorToEnd: true })
+ })
+ })
+ }, 1000)
+ }
+
+ componentWillUnmount() {
+ if (this.show_editor_timeout) {
+ clearTimeout(this.show_editor_timeout)
+ }
+ }
render() {
const { posting } = Auth.selected_user
return(
- <>
+
+
!posting.is_sending_post ? posting.set_post_title(text) : null}
/>
posting.max_post_length() ? 150 : 0,
flex: 1,
- padding: 8,
+ padding: 13,
color: App.theme_text_color()
}}
editable={!posting.is_sending_post}
@@ -60,7 +96,6 @@ export default class PageEditScreen extends React.Component{
scrollEnabled={true}
returnKeyType={'default'}
keyboardType={'default'}
- autoFocus={false}
autoCorrect={true}
clearButtonMode={'while-editing'}
enablesReturnKeyAutomatically={true}
@@ -75,10 +110,11 @@ export default class PageEditScreen extends React.Component{
}}
/>
+
- >
+
)
}
diff --git a/src/screens/pages/pages.js b/src/screens/pages/pages.js
index f368800f..c8668263 100644
--- a/src/screens/pages/pages.js
+++ b/src/screens/pages/pages.js
@@ -1,25 +1,86 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { View, Text, FlatList, RefreshControl, TouchableOpacity, TextInput, Keyboard, Image, Platform, StyleSheet } from 'react-native';
+import { View, Text, FlatList, RefreshControl, TouchableOpacity, Keyboard, Image, Platform, StyleSheet } from 'react-native';
import Auth from './../../stores/Auth';
import LoginMessage from '../../components/info/login_message'
import App from '../../stores/App'
import PageCell from '../../components/cells/page_cell';
-import { SheetProvider } from "react-native-actions-sheet";
import SearchIcon from '../../assets/icons/nav/discover.png';
import SearchBar from '../../components/search_bar';
import { SFSymbol } from "react-native-sfsymbols";
-import { SvgXml } from 'react-native-svg';
+import { SafeAreaInsetsContext } from 'react-native-safe-area-context'
+import { tabBarScrollContentBottomPadding } from '../../utils/ui'
@observer
export default class PagesScreen extends React.Component{
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ is_user_refreshing_pages: false
+ }
+ this.requested_pages_key = null
+ this.is_mounted = false
+ }
componentDidMount(){
- Auth.selected_user.posting?.selected_service?.update_pages_for_active_destination()
+ this.is_mounted = true
+ this._refresh_pages_if_needed()
+ }
+
+ componentDidUpdate() {
+ this._refresh_pages_if_needed()
+ }
+
+ componentWillUnmount() {
+ this.is_mounted = false
+ }
+
+ _current_pages_context = () => {
+ const selected_service = Auth.selected_user.posting?.selected_service
+ const destination = selected_service?.config?.pages_destination()
+ return { selected_service, destination }
+ }
+
+ _pages_request_key = (selected_service, destination) => {
+ return `${selected_service?.id || ""}:${destination?.uid || ""}`
+ }
+
+ _refresh_pages_if_needed = () => {
+ const { selected_service, destination } = this._current_pages_context()
+ if (!selected_service || !destination) {
+ return
+ }
+
+ const request_key = this._pages_request_key(selected_service, destination)
+ if (request_key === this.requested_pages_key) {
+ return
+ }
+
+ this.requested_pages_key = request_key
+ selected_service.check_for_pages_for_destination(destination)
+ }
+
+ _refresh_pages_from_pull = () => {
+ const { selected_service, destination } = this._current_pages_context()
+ if (!selected_service || !destination || this.state.is_user_refreshing_pages) {
+ return
+ }
+
+ this.setState({ is_user_refreshing_pages: true })
+ this.requested_pages_key = this._pages_request_key(selected_service, destination)
+ Promise.resolve(selected_service.check_for_pages_for_destination(destination))
+ .catch((error) => console.log("PagesScreen:refresh:error", error))
+ .finally(() => {
+ if (this.is_mounted) {
+ this.setState({ is_user_refreshing_pages: false })
+ }
+ })
}
_return_header = () => {
const { config } = Auth.selected_user.posting.selected_service
+ const destination_name = config.posts_destination()?.name ?? "current destination"
return(
!App.page_search_is_open ?
- App.open_sheet("posts_destination_menu", { type: "pages" })}>
+ App.open_sheet("posts_destination_menu", { type: "pages" })}
+ accessibilityRole="button"
+ accessibilityLabel={`Choose pages destination, ${destination_name}`}
+ >
- {config.posts_destination()?.name}
+ {destination_name}
{
Platform.OS === "ios" ?
@@ -66,7 +133,7 @@ export default class PagesScreen extends React.Component{
:
{Keyboard.dismiss()}}
onChangeText={(text) => App.set_pages_query(text, config.posts_destination())}
value={App.page_search_query}
@@ -87,53 +154,65 @@ export default class PagesScreen extends React.Component{
_return_pages_list = () => {
const { selected_service } = Auth.selected_user.posting
const { config } = selected_service
+ const destination = config.pages_destination()
+ const pages = config.pages_for_destination()?.slice() || []
+ const list_key = `${selected_service.id}-${destination?.uid || "pages"}`
return(
-
+ {insets => {
+ const bottom_padding = tabBarScrollContentBottomPadding(insets?.bottom, 10)
+
+ return (
+
+ // No pages...
+ //
+ // }
+ ItemSeparatorComponent={
+
+ }
+ refreshControl={
+
+ }
+ />
+ )
}}
- // ListEmptyComponent={
- //
- // No pages...
- //
- // }
- ItemSeparatorComponent={
-
- }
- refreshControl={
- selected_service.check_for_pages_for_destination(config.pages_destination())}
- />
- }
- />
+
)
}
render() {
return (
-
-
- {
- Auth.is_logged_in() && !Auth.is_selecting_user ?
- <>
- {this._return_header()}
- {this._return_pages_list()}
- >
- :
-
- }
-
-
+
+ {
+ Auth.is_logged_in() && !Auth.is_selecting_user ?
+ <>
+ {this._return_header()}
+ {this._return_pages_list()}
+ >
+ :
+
+ }
+
)
}
diff --git a/src/screens/posts/edit.android.js b/src/screens/posts/edit.android.js
index c747cc2d..066de082 100644
--- a/src/screens/posts/edit.android.js
+++ b/src/screens/posts/edit.android.js
@@ -5,6 +5,7 @@ import { KeyboardAvoidingView, KeyboardStickyView } from "react-native-keyboard-
import Auth from '../../stores/Auth';
import App from '../../stores/App';
import PostToolbar from '../../components/keyboard/post_toolbar';
+import HighlightingText from '../../components/text/highlighting_text';
import LoadingComponent from '../../components/generic/loading';
@observer
@@ -24,7 +25,8 @@ export default class PostEditScreen extends React.Component{
fontSize: 18,
justifyContent: 'flex-start',
alignItems: 'flex-start',
- padding: 8,
+ minHeight: 56,
+ padding: 13,
marginBottom: 4,
fontWeight: '700',
borderColor: App.theme_border_color(),
@@ -46,12 +48,17 @@ export default class PostEditScreen extends React.Component{
/>
: null
}
- 0 ? 90 : 50,
}}
@@ -66,7 +73,8 @@ export default class PostEditScreen extends React.Component{
enablesReturnKeyAutomatically={true}
underlineColorAndroid={'transparent'}
value={posting.post_text}
- onChangeText={(text) => !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null}
+ selection={posting.text_selection_flat}
+ onChangeText={({ nativeEvent: { text } }) => !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null}
onSelectionChange={({ nativeEvent: { selection } }) => {
posting.set_text_selection(selection)
}}
diff --git a/src/screens/posts/edit.ios.js b/src/screens/posts/edit.ios.js
index e89dd034..f6b12c6a 100644
--- a/src/screens/posts/edit.ios.js
+++ b/src/screens/posts/edit.ios.js
@@ -1,20 +1,54 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { TextInput } from 'react-native';
+import { TextInput, View } from 'react-native';
import { KeyboardStickyView } from "react-native-keyboard-controller";
import Auth from '../../stores/Auth';
import App from '../../stores/App'
+import EditorKeyboardAvoidingView from '../../components/keyboard/editor_keyboard_avoiding_view'
import PostToolbar from '../../components/keyboard/post_toolbar'
import HighlightingText from '../../components/text/highlighting_text';
import LoadingComponent from '../../components/generic/loading';
@observer
export default class PostEditScreen extends React.Component{
+ constructor(props) {
+ super(props)
+ this.state = {
+ editor_is_visible: false
+ }
+ this.text_editor_ref = React.createRef()
+ this.show_editor_timeout = null
+ }
+
+ componentDidMount() {
+ this.show_editor_timeout = setTimeout(() => {
+ this.setState({
+ editor_is_visible: true
+ }, () => {
+ requestAnimationFrame(() => {
+ this.text_editor_ref.current?.focus({ cursorToEnd: true })
+ })
+ })
+ }, 1000)
+ }
+
+ componentWillUnmount() {
+ if (this.show_editor_timeout) {
+ clearTimeout(this.show_editor_timeout)
+ }
+ }
render() {
const { posting } = Auth.selected_user
return(
- <>
+
+
{
posting.should_show_title() ?
posting.max_post_length() ? 150 : 0,
+ paddingBottom: posting.post_text_length() > posting.max_post_length() ? 50 : 0,
flex: 1,
- padding: 8,
+ padding: 13,
color: App.theme_text_color()
}}
editable={!posting.is_sending_post}
@@ -64,7 +100,6 @@ export default class PostEditScreen extends React.Component{
scrollEnabled={true}
returnKeyType={'default'}
keyboardType={'default'}
- autoFocus={false}
autoCorrect={true}
clearButtonMode={'while-editing'}
enablesReturnKeyAutomatically={true}
@@ -77,13 +112,13 @@ export default class PostEditScreen extends React.Component{
onSelectionChange={({ nativeEvent: { selection } }) => {
posting.set_text_selection(selection)
}}
- inputAccessoryViewID={this.input_accessory_view_id}
/>
+
- >
+
)
}
diff --git a/src/screens/posts/image_crop.js b/src/screens/posts/image_crop.js
index a1710ffd..12113caa 100644
--- a/src/screens/posts/image_crop.js
+++ b/src/screens/posts/image_crop.js
@@ -42,6 +42,7 @@ class FilterThumbnail extends React.Component {
var new_asset = MediaAsset.create({
uri: this.filteredURI,
type: asset.type,
+ original_file_name: asset.original_file_name,
width: asset.width,
height: asset.height
})
@@ -144,6 +145,7 @@ export default class ImageCropScreen extends React.Component{
var media_asset = MediaAsset.create({
uri: url,
type: this.asset.type,
+ original_file_name: this.asset.original_file_name,
width: crop_size,
height: crop_size
})
diff --git a/src/screens/posts/image_options.js b/src/screens/posts/image_options.js
index b46fc08d..96dac8a6 100644
--- a/src/screens/posts/image_options.js
+++ b/src/screens/posts/image_options.js
@@ -6,8 +6,8 @@ import Auth from '../../stores/Auth';
import App from '../../stores/App'
import Clipboard from '@react-native-clipboard/clipboard';
import Video from 'react-native-video';
-import MicroPubApi from '../../api/MicroPubApi';
-import MBImage from '../../components/common/MBImage';
+import MicroPubApi from '../../api/MicroPubApi'
+import MBImage from '../../components/common/MBImage'
@observer
export default class ImageOptionsScreen extends React.Component{
@@ -41,9 +41,21 @@ export default class ImageOptionsScreen extends React.Component{
});
}
+ _current_asset = () => {
+ const { asset_uri } = this.props.route.params || {}
+ const posting = Auth.selected_user.posting
+ if (asset_uri) {
+ return posting.post_assets.find(file => file.uri === asset_uri)
+ }
+ return null
+ }
+
_check_uploads_for_text(results) {
- const { asset } = this.props.route.params;
- let found = false;
+ const asset = this._current_asset()
+ if (!asset) {
+ return false
+ }
+ let found = false
if (results.items != null) {
for (let item of results.items) {
@@ -79,7 +91,10 @@ export default class ImageOptionsScreen extends React.Component{
render() {
const { posting } = Auth.selected_user
- const { asset } = this.props.route.params
+ const asset = this._current_asset()
+ if (!asset) {
+ return null
+ }
const max_media_height = 250; // cap media height
const window_width = Dimensions.get('window').width;
@@ -95,6 +110,7 @@ export default class ImageOptionsScreen extends React.Component{
media_width = max_media_height * aspect_ratio;
}
}
+ const image_uri = asset.uri ? asset.uri : asset.remote_url
return(
@@ -120,12 +136,12 @@ export default class ImageOptionsScreen extends React.Component{
repeat
/>
:
-
+
}
{
asset.is_uploading ?
<>
-
+
>
: null
}
@@ -153,7 +169,7 @@ export default class ImageOptionsScreen extends React.Component{
{ this.state.isLoadingAlt ?
<>
🤖
-
+
>
:
<>
diff --git a/src/screens/posts/new.android.js b/src/screens/posts/new.android.js
index 4fd4bab8..7ed4a39c 100644
--- a/src/screens/posts/new.android.js
+++ b/src/screens/posts/new.android.js
@@ -7,6 +7,7 @@ import App from '../../stores/App';
import PostToolbar from '../../components/keyboard/post_toolbar';
import AssetToolbar from '../../components/keyboard/asset_toolbar';
import UsernameToolbar from '../../components/keyboard/username_toolbar';
+import HighlightingText from '../../components/text/highlighting_text';
import LoadingComponent from '../../components/generic/loading';
@observer
@@ -18,6 +19,7 @@ export default class PostingScreen extends React.Component{
render() {
const { posting } = Auth.selected_user
+ const is_editor_enabled = !posting.is_sending_post && !posting.is_closing_after_post
return(
<>
@@ -30,14 +32,15 @@ export default class PostingScreen extends React.Component{
fontSize: 18,
justifyContent: 'flex-start',
alignItems: 'flex-start',
- padding: 8,
+ minHeight: 56,
+ padding: 13,
marginBottom: 4,
fontWeight: '700',
borderColor: App.theme_border_color(),
borderBottomWidth: .5,
color: App.theme_text_color()
}}
- editable={!posting.is_sending_post}
+ editable={is_editor_enabled}
multiline={false}
scrollEnabled={false}
returnKeyType={'default'}
@@ -48,20 +51,25 @@ export default class PostingScreen extends React.Component{
enablesReturnKeyAutomatically={true}
underlineColorAndroid={'transparent'}
value={posting.post_title}
- onChangeText={(text) => !posting.is_sending_post ? posting.set_post_title(text) : null}
+ onChangeText={(text) => is_editor_enabled ? posting.set_post_title(text) : null}
/>
: null
}
- 0 ? 90 : 50
}}
- editable={!posting.is_sending_post}
+ editable={is_editor_enabled}
multiline={true}
scrollEnabled={true}
returnKeyType={'default'}
@@ -72,7 +80,8 @@ export default class PostingScreen extends React.Component{
enablesReturnKeyAutomatically={true}
underlineColorAndroid={'transparent'}
value={posting.post_text}
- onChangeText={(text) => !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null}
+ selection={posting.text_selection_flat}
+ onChangeText={({ nativeEvent: { text } }) => is_editor_enabled ? posting.set_post_text_from_typing(text) : null}
onSelectionChange={({ nativeEvent: { selection } }) => {
posting.set_text_selection(selection)
}}
diff --git a/src/screens/posts/new.ios.js b/src/screens/posts/new.ios.js
index bab6f006..4f4f01fe 100644
--- a/src/screens/posts/new.ios.js
+++ b/src/screens/posts/new.ios.js
@@ -1,9 +1,10 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { TextInput } from 'react-native';
+import { TextInput, View } from 'react-native';
import { KeyboardStickyView } from "react-native-keyboard-controller";
import Auth from '../../stores/Auth';
import App from '../../stores/App';
+import EditorKeyboardAvoidingView from '../../components/keyboard/editor_keyboard_avoiding_view';
import PostToolbar from '../../components/keyboard/post_toolbar';
import AssetToolbar from '../../components/keyboard/asset_toolbar';
import UsernameToolbar from '../../components/keyboard/username_toolbar';
@@ -12,20 +13,46 @@ import LoadingComponent from '../../components/generic/loading';
@observer
export default class PostingScreen extends React.Component{
-
constructor(props) {
- super(props)
- this.input_accessory_view_id = "input_toolbar";
+ super(props)
+ this.state = {
+ editor_is_visible: false
+ }
+ this.text_editor_ref = React.createRef()
+ this.show_editor_timeout = null
}
-
+
componentDidMount() {
Auth.selected_user?.prep_posting()
+ this.show_editor_timeout = setTimeout(() => {
+ this.setState({
+ editor_is_visible: true
+ }, () => {
+ requestAnimationFrame(() => {
+ this.text_editor_ref.current?.focus({ cursorToEnd: true })
+ })
+ })
+ }, 1000)
+ }
+
+ componentWillUnmount() {
+ if (this.show_editor_timeout) {
+ clearTimeout(this.show_editor_timeout)
+ }
}
render() {
const { posting } = Auth.selected_user
+ const is_editor_enabled = !posting.is_sending_post && !posting.is_closing_after_post
return(
- <>
+
+
{
posting.should_show_title() ?
!posting.is_sending_post ? posting.set_post_title(text) : null}
- inputAccessoryViewID={this.input_accessory_view_id}
+ onChangeText={(text) => is_editor_enabled ? posting.set_post_title(text) : null}
/>
: null
}
0 ? 260 : 50,
flex: 1,
- padding: 8,
+ padding: 13,
color: App.theme_text_color()
}}
- editable={!posting.is_sending_post}
+ editable={is_editor_enabled}
multiline={true}
scrollEnabled={true}
returnKeyType={'default'}
keyboardType={'default'}
- autoFocus={true}
autoCorrect={true}
clearButtonMode={'while-editing'}
enablesReturnKeyAutomatically={true}
@@ -84,20 +111,20 @@ export default class PostingScreen extends React.Component{
value={posting.post_text}
selection={posting.text_selection_flat}
onChangeText={({ nativeEvent: { text } }) => {
- !posting.is_sending_post ? posting.set_post_text_from_typing(text) : null
+ is_editor_enabled ? posting.set_post_text_from_typing(text) : null
}}
onSelectionChange={({ nativeEvent: { selection } }) => {
posting.set_text_selection(selection)
}}
- inputAccessoryViewID={this.input_accessory_view_id}
/>
+
- >
+
)
}
diff --git a/src/screens/posts/posts.js b/src/screens/posts/posts.js
index 0d092d51..5feb72e0 100644
--- a/src/screens/posts/posts.js
+++ b/src/screens/posts/posts.js
@@ -5,10 +5,11 @@ import Auth from './../../stores/Auth';
import LoginMessage from '../../components/info/login_message'
import App from '../../stores/App'
import PostCell from '../../components/cells/post_cell';
-import { SheetProvider } from "react-native-actions-sheet";
import SearchIcon from '../../assets/icons/nav/discover.png';
import SearchBar from '../../components/search_bar';
import { SFSymbol } from "react-native-sfsymbols";
+import { SafeAreaInsetsContext } from 'react-native-safe-area-context'
+import { tabBarScrollContentBottomPadding } from '../../utils/ui'
@observer
export default class PostsScreen extends React.Component{
@@ -16,29 +17,142 @@ export default class PostsScreen extends React.Component{
super(props);
this.state = {
is_showing_drafts_button: false,
- is_showing_drafts_posts: false
+ is_showing_drafts_posts: false,
+ post_search_text: App.post_search_query,
+ is_user_refreshing_posts: false
};
+ this.posts_search_timeout = null;
+ this.requested_posts_key = null
+ this.is_mounted = false
}
componentDidMount() {
- const { selected_service } = Auth.selected_user.posting;
- const { config } = selected_service;
-
- selected_service.update_posts_for_active_destination();
+ this.is_mounted = true
+ this._refresh_posts_if_needed()
this.focusListener = this.props.navigation.addListener('focus', () => {
- selected_service.check_for_posts_for_destination(config.posts_destination(), this.state.is_showing_drafts_posts);
+ this._refresh_posts()
});
}
+ componentDidUpdate() {
+ this._refresh_posts_if_needed()
+ }
+
componentWillUnmount() {
+ this.is_mounted = false
+ this._clear_posts_search_timeout();
if (this.focusListener) {
this.focusListener();
}
}
+
+ _open_search = () => {
+ this.setState({ post_search_text: App.post_search_query });
+ App.toggle_post_search_is_open();
+ }
+
+ _submit_search = (event) => {
+ const { selected_service } = Auth.selected_user.posting;
+ const { config } = selected_service;
+ const text = event?.nativeEvent?.text ?? this.state.post_search_text;
+
+ this._clear_posts_search_timeout();
+ Keyboard.dismiss();
+ this.setState({ post_search_text: text });
+ App.set_posts_query(text, config.posts_destination(), this.state.is_showing_drafts_posts);
+ }
+
+ _cancel_search = () => {
+ const { selected_service } = Auth.selected_user.posting;
+ const { config } = selected_service;
+
+ this._clear_posts_search_timeout();
+ this.setState({ post_search_text: '' });
+ App.toggle_post_search_is_open();
+ App.set_post_search_text('');
+ App.set_posts_query('', config.posts_destination(), this.state.is_showing_drafts_posts);
+ }
+
+ _clear_posts_search_timeout = () => {
+ if (this.posts_search_timeout) {
+ clearTimeout(this.posts_search_timeout);
+ this.posts_search_timeout = null;
+ }
+ }
+
+ _schedule_posts_search = (text) => {
+ const { selected_service } = Auth.selected_user.posting;
+ const { config } = selected_service;
+
+ this._clear_posts_search_timeout();
+ this.posts_search_timeout = setTimeout(() => {
+ App.set_posts_query(text, config.posts_destination(), this.state.is_showing_drafts_posts);
+ this.posts_search_timeout = null;
+ }, 2000);
+ }
+
+ _change_posts_search_text = (text) => {
+ this.setState({ post_search_text: text });
+ App.set_post_search_text(text);
+ this._schedule_posts_search(text);
+ }
+
+ _current_posts_context = () => {
+ const selected_service = Auth.selected_user.posting?.selected_service
+ const destination = selected_service?.config?.posts_destination()
+ return { selected_service, destination }
+ }
+
+ _posts_request_key = (selected_service, destination, show_drafts) => {
+ return `${selected_service?.id || ""}:${destination?.uid || ""}:${show_drafts ? "drafts" : "posts"}`
+ }
+
+ _refresh_posts_if_needed = () => {
+ const { selected_service, destination } = this._current_posts_context()
+ if (!selected_service || !destination) {
+ return
+ }
+
+ const request_key = this._posts_request_key(selected_service, destination, this.state.is_showing_drafts_posts)
+ if (request_key === this.requested_posts_key) {
+ return
+ }
+
+ this.requested_posts_key = request_key
+ selected_service.check_for_posts_for_destination(destination, this.state.is_showing_drafts_posts)
+ }
+
+ _refresh_posts = () => {
+ const { selected_service, destination } = this._current_posts_context()
+ if (!selected_service || !destination) {
+ return
+ }
+
+ this.requested_posts_key = this._posts_request_key(selected_service, destination, this.state.is_showing_drafts_posts)
+ selected_service.check_for_posts_for_destination(destination, this.state.is_showing_drafts_posts)
+ }
+
+ _refresh_posts_from_pull = () => {
+ const { selected_service, destination } = this._current_posts_context()
+ if (!selected_service || !destination || this.state.is_user_refreshing_posts) {
+ return
+ }
+
+ this.setState({ is_user_refreshing_posts: true })
+ this.requested_posts_key = this._posts_request_key(selected_service, destination, this.state.is_showing_drafts_posts)
+ Promise.resolve(selected_service.check_for_posts_for_destination(destination, this.state.is_showing_drafts_posts))
+ .catch((error) => console.log("PostsScreen:refresh:error", error))
+ .finally(() => {
+ if (this.is_mounted) {
+ this.setState({ is_user_refreshing_posts: false })
+ }
+ })
+ }
_return_header = () => {
const { config } = Auth.selected_user.posting.selected_service
+ const destination_name = config.posts_destination()?.name ?? "current destination"
return(
!App.post_search_is_open ?
- App.open_sheet("posts_destination_menu", { type: "posts" })}>
+ App.open_sheet("posts_destination_menu", { type: "posts" })}
+ accessibilityRole="button"
+ accessibilityLabel={`Choose posts destination, ${destination_name}`}
+ >
- {config.posts_destination()?.name}
+ {destination_name}
@@ -76,8 +194,15 @@ export default class PostsScreen extends React.Component{
this.setState({ is_showing_drafts_posts: new_is_drafts });
const { selected_service } = Auth.selected_user.posting;
const { config } = selected_service;
- selected_service.check_for_posts_for_destination(config.posts_destination(), new_is_drafts);
+ const destination = config.posts_destination()
+ if (destination) {
+ this.requested_posts_key = this._posts_request_key(selected_service, destination, new_is_drafts)
+ selected_service.check_for_posts_for_destination(destination, new_is_drafts);
+ }
}}
+ accessibilityRole="button"
+ accessibilityLabel="Show drafts"
+ accessibilityState={{ selected: this.state.is_showing_drafts_posts }}
>
Drafts
@@ -96,7 +221,9 @@ export default class PostsScreen extends React.Component{
borderRadius: 5,
marginLeft: 5,
}}
- onPress={App.toggle_post_search_is_open}
+ onPress={this._open_search}
+ accessibilityRole="button"
+ accessibilityLabel="Search posts"
>
{
Platform.OS === "ios" ?
@@ -113,13 +240,10 @@ export default class PostsScreen extends React.Component{
:
{Keyboard.dismiss()}}
- onChangeText={(text) => App.set_posts_query(text, config.posts_destination(), this.state.is_showing_drafts_posts)}
- value={App.post_search_query}
- onCancel={() => {
- App.toggle_post_search_is_open();
- App.set_posts_query("", null, this.state.is_showing_drafts_posts);
- }}
+ onSubmitEditing={this._submit_search}
+ onChangeText={this._change_posts_search_text}
+ value={this.state.post_search_text}
+ onCancel={this._cancel_search}
/>
)
}
@@ -139,57 +263,69 @@ export default class PostsScreen extends React.Component{
_return_posts_list = () => {
const { selected_service } = Auth.selected_user.posting;
const { config } = selected_service;
+ const destination = config.posts_destination()
+ const posts = config.posts_for_destination(this.state.is_showing_drafts_posts)?.slice() || []
+ const list_key = `${selected_service.id}-${destination?.uid || "posts"}-${this.state.is_showing_drafts_posts ? "drafts" : "posts"}`
setTimeout(() => {
// check if we need drafts button, but if set, never unset
- if (!this.state.is_showing_drafts_button) {
- const any_drafts = this._has_any_drafts(config.posts_for_destination(this.state.is_showing_drafts_posts));
+ if (!this.state.is_showing_drafts_button && posts.length) {
+ const any_drafts = this._has_any_drafts(posts);
this.setState({ is_showing_drafts_button: any_drafts });
}
}, 500);
return(
-
+ {insets => {
+ const bottom_padding = tabBarScrollContentBottomPadding(insets?.bottom, 10)
+
+ return (
+
+ }
+ refreshControl={
+
+ }
+ />
+ )
}}
- ItemSeparatorComponent={
-
- }
- refreshControl={
- selected_service.check_for_posts_for_destination(config.posts_destination(), this.state.is_showing_drafts_posts)}
- />
- }
- />
+
)
}
render() {
return (
-
-
- {
- Auth.is_logged_in() && !Auth.is_selecting_user ?
- <>
- {this._return_header()}
- {this._return_posts_list()}
- >
- :
-
- }
-
-
+
+ {
+ Auth.is_logged_in() && !Auth.is_selecting_user ?
+ <>
+ {this._return_header()}
+ {this._return_posts_list()}
+ >
+ :
+
+ }
+
)
}
diff --git a/src/screens/profile/profile.js b/src/screens/profile/profile.js
index 6c2a3b56..8a006e8e 100644
--- a/src/screens/profile/profile.js
+++ b/src/screens/profile/profile.js
@@ -4,7 +4,6 @@ import { View } from 'react-native';
import Auth from './../../stores/Auth';
import WebViewModule from '../../components/web/webview_module'
import LoginMessage from '../../components/info/login_message'
-import ImageModalModule from '../../components/images/image_modal'
import ProfileHeader from '../../components/profile/profile_header'
import MutedMessage from '../../components/info/muted_message'
import App from '../../stores/App'
@@ -28,7 +27,6 @@ export default class ProfileScreen extends React.Component{
:
}
-
)
}
diff --git a/src/screens/replies/replies.js b/src/screens/replies/replies.js
index cd333017..bca74ecb 100644
--- a/src/screens/replies/replies.js
+++ b/src/screens/replies/replies.js
@@ -1,18 +1,52 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { View, Text, ScrollView, RefreshControl } from 'react-native';
+import { View, Text, FlatList, RefreshControl, Platform } from 'react-native';
import Auth from './../../stores/Auth';
import LoginMessage from '../../components/info/login_message'
import App from '../../stores/App'
import Replies from '../../stores/Replies';
import ReplyCell from '../../components/cells/reply_cell';
+import { SafeAreaInsetsContext } from 'react-native-safe-area-context'
+import { tabBarScrollContentBottomPadding } from '../../utils/ui'
@observer
export default class RepliesScreen extends React.Component{
+ constructor(props) {
+ super(props)
+ this.requested_replies_key = null
+ }
+
componentDidMount() {
+ this._refresh_replies_if_needed()
+ }
+
+ componentDidUpdate() {
+ this._refresh_replies_if_needed()
+ }
+
+ _replies_request_key = () => {
+ return Auth.selected_user?.username || null
+ }
+
+ _refresh_replies_if_needed = () => {
+ const request_key = this._replies_request_key()
+ if (!Auth.is_logged_in() || Auth.is_selecting_user || !request_key) {
+ return
+ }
+
+ if (request_key === this.requested_replies_key) {
+ return
+ }
+
+ this.requested_replies_key = request_key
Replies.hydrate()
}
+
+ _refresh_replies = () => {
+ this.requested_replies_key = null
+ this._refresh_replies_if_needed()
+ }
_return_header = () => {
return(
@@ -33,26 +67,37 @@ export default class RepliesScreen extends React.Component{
}
_return_replies_list = () => {
+ const replies = Replies.replies.slice()
+ const list_key = `${this.requested_replies_key || "replies"}`
return(
-
+ {insets => {
+ const bottom_padding = tabBarScrollContentBottomPadding(insets?.bottom, 10)
+
+ return (
+ reply.id}
+ renderItem={({ item }) => }
+ style={{
+ backgroundColor: App.theme_background_color_secondary(),
+ width: '100%',
+ flex: 1
+ }}
+ contentContainerStyle={{ paddingBottom: bottom_padding }}
+ scrollIndicatorInsets={Platform.OS === 'ios' ? { bottom: bottom_padding } : undefined}
+ refreshControl={
+
+ }
+ />
+ )
}}
- refreshControl={
-
- }
- >
- {
- Replies.replies.map((reply) => {
- return
- })
- }
-
+
)
}
diff --git a/src/screens/settings/muting.js b/src/screens/settings/muting.js
index 5d4fb6fb..c8213810 100644
--- a/src/screens/settings/muting.js
+++ b/src/screens/settings/muting.js
@@ -113,7 +113,6 @@ export default class MutingScreen extends React.Component {
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
- paddingHorizontal: 10,
}}>
{title}
@@ -158,7 +157,7 @@ export default class MutingScreen extends React.Component {
{
is_active &&
-
+
this.state.input_ref = ref}
style={{
diff --git a/src/screens/settings/settings.js b/src/screens/settings/settings.js
index 81c8a841..38d4c521 100644
--- a/src/screens/settings/settings.js
+++ b/src/screens/settings/settings.js
@@ -31,6 +31,76 @@ export default class SettingsScreen extends React.Component {
});
}
+ _set_auto_android_theme = async (enabled) => {
+ await Settings.set_auto_android_theme(enabled)
+ await App.sync_current_theme()
+ }
+
+ _render_appearance_settings = () => {
+ if (Platform.OS !== "android") {
+ return null
+ }
+
+ return (
+
+
+ Appearance
+
+
+
+
+ Use Material You accent colour
+
+
+
+
+ Light and dark mode always follow Android. Turn this on to use your system accent colour for the interface.
+
+
+
+ )
+ }
+
_render_browser_settings = () => {
return (
@@ -39,7 +109,6 @@ export default class SettingsScreen extends React.Component {
fontWeight: "500",
marginBottom: 10,
marginTop: 15,
- marginLeft: 10,
color: App.theme_text_color(),
}}
>
@@ -123,6 +192,8 @@ export default class SettingsScreen extends React.Component {
};
_render_push_settings = () => {
+ const show_user_identity = Auth.users.length > 1
+
return (
@@ -139,11 +209,10 @@ export default class SettingsScreen extends React.Component {
{!this.state.push_permissions && (
-
+ {show_user_identity && (
+
+ )}
- @{user.username}
+ {show_user_identity ? `@${user.username}` : "Enable push notifications"}
{user.toggling_push && (
)}
@@ -230,6 +301,8 @@ export default class SettingsScreen extends React.Component {
};
_render_posting_settings = () => {
+ const show_user_identity = Auth.users.length > 1
+
return (
@@ -247,7 +319,6 @@ export default class SettingsScreen extends React.Component {
style={{
fontWeight: "300",
marginBottom: 10,
- marginLeft: 10,
color: App.theme_text_color(),
}}
>
@@ -266,6 +337,7 @@ export default class SettingsScreen extends React.Component {
componentId={this.props.componentId}
user={user}
index={index}
+ show_user_identity={show_user_identity}
key={`user-${user.username}-${index}`}
/>
);
@@ -276,6 +348,8 @@ export default class SettingsScreen extends React.Component {
};
_render_muting_settings = () => {
+ const show_user_identity = Auth.users.length > 1
+
return (
@@ -301,6 +374,7 @@ export default class SettingsScreen extends React.Component {
key={`user-${user.username}-${index}`}
user={user}
index={index}
+ show_user_identity={show_user_identity}
/>
))}
@@ -317,6 +391,7 @@ export default class SettingsScreen extends React.Component {
backgroundColor: App.theme_background_color(),
}}
>
+ {this._render_appearance_settings()}
{this._render_browser_settings()}
{this._render_posting_settings()}
{this._render_muting_settings()}
diff --git a/src/screens/share/image_options.js b/src/screens/share/image_options.js
index f7bec559..bcbb92df 100644
--- a/src/screens/share/image_options.js
+++ b/src/screens/share/image_options.js
@@ -1,6 +1,7 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { View, Text, TouchableOpacity, ScrollView, Image as RNImage, ActivityIndicator, TextInput, KeyboardAvoidingView, Dimensions } from 'react-native';
+import { View, Text, TouchableOpacity, ActivityIndicator, TextInput, Dimensions } from 'react-native';
+import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
import App from '../../stores/App';
import Share from '../../stores/Share';
import Clipboard from '@react-native-clipboard/clipboard';
@@ -12,32 +13,11 @@ export default class ShareImageOptionsScreen extends React.Component{
constructor(props) {
super(props);
- const { asset } = this.props
-
- const max_media_height = 250; // cap media height
- const window_width = Dimensions.get('window').width;
- let media_width = window_width;
- let media_height = window_width; // default to 1:1
-
this.state = {
- media_width: media_width,
- media_height: media_height,
isLoadingAlt: Share.selected_user.is_using_ai,
generatedAltText: "",
copyButtonTitle: "Copy Text"
};
-
- RNImage.getSize(asset.uri, (width, height) => {
- const aspect_ratio = width / height;
- media_height = window_width / aspect_ratio;
-
- if (media_height > max_media_height) {
- media_height = max_media_height;
- media_width = max_media_height * aspect_ratio;
- }
-
- this.setState({ media_width: media_width, media_height: media_height });
- });
if (Share.selected_user.is_using_ai) {
this._download_image_info();
@@ -62,6 +42,9 @@ export default class ShareImageOptionsScreen extends React.Component{
_check_uploads_for_text(results) {
const { asset } = this.props;
+ if (!asset) {
+ return false
+ }
let found = false;
if (results.items != null) {
@@ -95,120 +78,159 @@ export default class ShareImageOptionsScreen extends React.Component{
return found;
}
+
+ _media_dimensions(asset) {
+ const max_media_height = 250; // cap media height
+ const window_width = Dimensions.get('window').width;
+ let media_width = window_width;
+ let media_height = window_width; // default to 1:1
+
+ if (asset?.width && asset?.height) {
+ const aspect_ratio = asset.width / asset.height;
+ media_height = window_width / aspect_ratio;
+
+ if (media_height > max_media_height) {
+ media_height = max_media_height;
+ media_width = max_media_height * aspect_ratio;
+ }
+ }
+
+ return { media_width, media_height }
+ }
+
+ _image_uri(asset) {
+ return asset?.uri ? asset.uri : asset?.remote_url
+ }
render() {
const { posting } = Share.selected_user
const { asset } = this.props
+ if (!asset) {
+ return null
+ }
+ const { media_width, media_height } = this._media_dimensions(asset)
+ const image_uri = this._image_uri(asset)
return(
-
-
-
-
- {
- asset.is_uploading ?
- <>
-
-
- >
- : null
+
+
+ {
+ image_uri ?
+
+ : null
+ }
+ {
+ asset.is_uploading ?
+ <>
+
+
+ >
+ : null
+ }
+
+
+ { (this.state.isLoadingAlt || (this.state.generatedAltText.length > 0)) &&
+
+ { this.state.isLoadingAlt ?
+ <>
+ 🤖
+
+ >
+ :
+ <>
+ 🤖 {this.state.generatedAltText}
+ {
+ Clipboard.setString(this.state.generatedAltText);
+ this.setState({ copyButtonTitle: "✓ Copied" });
+ }}
+ >
+ {this.state.copyButtonTitle}
+
+ >
}
+ }
- { (this.state.isLoadingAlt || (this.state.generatedAltText.length > 0)) &&
-
- { this.state.isLoadingAlt ?
- <>
- 🤖
-
- >
- :
- <>
- 🤖 {this.state.generatedAltText}
- {
- Clipboard.setString(this.state.generatedAltText);
- this.setState({ copyButtonTitle: "✓ Copied" });
- }}
- >
- {this.state.copyButtonTitle}
-
- >
- }
-
- }
-
- {
- !asset.is_video &&
- !posting.is_sending_post ? asset.set_alt_text(text) : null}
- />
- }
-
-
+ fontWeight: '400',
+ color: App.theme_text_color(),
+ textAlignVertical: 'top',
+ width: "100%",
+ }}
+ editable={!posting.is_sending_post}
+ multiline={true}
+ scrollEnabled={true}
+ returnKeyType={'default'}
+ keyboardType={'default'}
+ autoFocus={true}
+ autoCorrect={true}
+ clearButtonMode={'while-editing'}
+ enablesReturnKeyAutomatically={true}
+ underlineColorAndroid={'transparent'}
+ value={asset.alt_text}
+ onChangeText={(text) => !posting.is_sending_post ? asset.set_alt_text(text) : null}
+ />
+ }
+
)
}
diff --git a/src/screens/share/index.js b/src/screens/share/index.js
index 436a3c7d..a7d79f1a 100644
--- a/src/screens/share/index.js
+++ b/src/screens/share/index.js
@@ -1,80 +1,94 @@
import { observer } from 'mobx-react'
import * as React from 'react'
-import { ActivityIndicator, Button, Text, View, Platform } from 'react-native'
+import { ActivityIndicator, Button, Text, View, Platform, useColorScheme } from 'react-native'
+import { KeyboardProvider } from "react-native-keyboard-controller"
import App from '../../stores/App'
import Share from '../../stores/Share'
import SharePostScreen from './post'
import ShareImageOptionsScreen from './image_options'
import ShareHeaderComponent from '../../components/share/header'
+const ShareThemeSync = ({ colorScheme }) => {
+ const current_color_scheme = useColorScheme()
+
+ React.useEffect(() => {
+ App.change_current_theme(colorScheme || current_color_scheme)
+ }, [ colorScheme, current_color_scheme ])
+
+ return null
+}
+
@observer
export default class ShareScreen extends React.Component {
componentDidMount() {
console.log('ShareScreen:componentDidMount')
- Platform.OS === "ios" && Share.hydrate()
+ Platform.OS === "ios" && Share.hydrate(null, this.props.colorScheme)
}
render() {
return (
-
- {
- Share.is_loading ?
-
-
- {
- Platform.OS === "ios" ?
- {Share.temp_direct_shared_data}
- : null
- }
-
- :
- Share.is_logged_in() ?
-
-
+
+
+
+ {
+ Share.is_loading ?
+
+
{
- Share.image_options_open ?
-
- :
- <>
-
- {
- Share.selected_user?.posting.is_sending_post || Share.selected_user?.posting.is_adding_bookmark ?
-
-
-
- { Share.selected_user?.posting.is_sending_post ? "Sending post..." : "Saving bookmark..." }
-
-
- : null
- }
- >
+ Platform.OS === "ios" ?
+ {Share.temp_direct_shared_data}
+ : null
}
:
-
- Using the Micro.blog app, please sign in before using the share extension.
-
-
- }
-
+ Share.is_logged_in() ?
+
+
+ {
+ Share.image_options_open ?
+
+ :
+ <>
+
+ {
+ Share.selected_user?.posting.is_sending_post || Share.selected_user?.posting.is_adding_bookmark ?
+
+
+
+ { Share.selected_user?.posting.is_sending_post ? "Sending post..." : "Saving bookmark..." }
+
+
+ : null
+ }
+ >
+ }
+
+ :
+
+ Using the Micro.blog app, please sign in before using the share extension.
+
+
+ }
+
+
)
}
diff --git a/src/screens/share/post.android.js b/src/screens/share/post.android.js
index e16413b9..833b9536 100644
--- a/src/screens/share/post.android.js
+++ b/src/screens/share/post.android.js
@@ -1,19 +1,20 @@
import * as React from 'react'
import { observer } from 'mobx-react'
import { View, Text } from 'react-native'
-import { TextInput } from 'react-native';
import { KeyboardAvoidingView, KeyboardStickyView } from "react-native-keyboard-controller";
import Share from '../../stores/Share'
import App from '../../stores/App'
import Auth from '../../stores/Auth';
import AssetToolbar from '../../components/keyboard/asset_toolbar'
import PostToolbar from '../../components/keyboard/post_toolbar'
+import HighlightingText from '../../components/text/highlighting_text'
@observer
export default class SharePostScreen extends React.Component {
render() {
const { selected_user } = Auth
+ const selection_flat = `${Share.text_selection.start} ${Share.text_selection.end}`
return (
Share.is_logged_in() && selected_user?.posting != null ?
<>
@@ -32,12 +33,17 @@ export default class SharePostScreen extends React.Component {
}
- !selected_user?.posting.is_sending_post ? Share.set_post_text(text) : null}
+ selection={selection_flat}
+ onChangeText={({ nativeEvent: { text } }) => !selected_user?.posting.is_sending_post ? Share.set_post_text(text) : null}
onSelectionChange={({ nativeEvent: { selection } }) => {
Share.set_text_selection(selection)
}}
diff --git a/src/screens/share/post.ios.js b/src/screens/share/post.ios.js
index 4f40880c..7bb27336 100644
--- a/src/screens/share/post.ios.js
+++ b/src/screens/share/post.ios.js
@@ -1,73 +1,96 @@
import * as React from 'react'
import { observer } from 'mobx-react'
-import { InputAccessoryView, View, Text } from 'react-native'
+import { View, Text } from 'react-native'
+import { KeyboardStickyView } from "react-native-keyboard-controller"
import Share from '../../stores/Share'
import App from '../../stores/App'
import AssetToolbar from '../../components/keyboard/asset_toolbar'
+import EditorKeyboardAvoidingView from '../../components/keyboard/editor_keyboard_avoiding_view'
import PostToolbar from '../../components/keyboard/post_toolbar'
-import HighlightingText from '../../components/text/highlighting_text';
+import HighlightingText from '../../components/text/highlighting_text'
@observer
export default class SharePostScreen extends React.Component {
- constructor (props) {
- super(props)
- this.input_accessory_view_id = "input_toolbar"
- }
-
render() {
const { selected_user } = Share
+ const posting = selected_user?.posting
+ const selection_flat = `${Share.text_selection.start} ${Share.text_selection.end}`
return (
- Share.is_logged_in() && selected_user?.posting != null ?
-
- {
- Share.error_message != null &&
-
- {Share.error_message}
-
- }
- {
- Share.can_save_as_bookmark() &&
-
-
- You can save this URL as a bookmark or keep editing to create a new post.
-
+ Share.is_logged_in() && posting != null ?
+ <>
+
+ {
+ Share.error_message != null &&
+
+ {Share.error_message}
- }
- !selected_user?.posting.is_sending_post ? Share.set_post_text(text) : null}
- onSelectionChange={({ nativeEvent: { selection } }) => {
- Share.set_text_selection(selection)
- }}
- inputAccessoryViewID={this.input_accessory_view_id}
- />
-
-
-
-
-
+ }
+ {
+ Share.can_save_as_bookmark() &&
+
+
+ You can save this URL as a bookmark or keep editing to create a new post.
+
+
+ }
+
+ !posting.is_sending_post ? Share.set_post_text(text) : null}
+ onSelectionChange={({ nativeEvent: { selection } }) => {
+ Share.set_text_selection(selection)
+ }}
+ />
+
+
+
+
+ {
+ !posting.post_title &&
+ posting.max_post_length() ? '#a94442' : App.theme_text_color() }}>{posting.post_text_length()}/{posting.max_post_length()}
+ }
+
+
+
+
+ >
: null
)
}
diff --git a/src/screens/stacks/AuthNavigator.js b/src/screens/stacks/AuthNavigator.js
new file mode 100644
index 00000000..5033d10d
--- /dev/null
+++ b/src/screens/stacks/AuthNavigator.js
@@ -0,0 +1,85 @@
+import * as React from 'react'
+import { Platform, StatusBar } from 'react-native'
+import { observer } from 'mobx-react'
+import { NavigationContainer, DefaultTheme } from '@react-navigation/native'
+import { createNativeStackNavigator } from '@react-navigation/native-stack'
+import { SheetProvider } from 'react-native-actions-sheet'
+import App from '../../stores/App'
+import LoginScreen from '../login/login'
+import AppleUsernameScreen from '../login/apple_username'
+import BackButton from '../../components/header/back'
+import { android_status_bar_options, headerLeftElement } from '../../utils/navigation'
+
+const Stack = createNativeStackNavigator()
+const renderBackButton = () =>
+
+@observer
+export default class AuthNavigator extends React.Component {
+ render() {
+ return (
+ <>
+ {
+ Platform.OS === 'android' &&
+
+ }
+ {
+ App.set_navigation_ready(true)
+ }}
+ theme={{
+ dark: App.is_dark_mode(),
+ colors: {
+ background: App.theme_background_color(),
+ text: App.theme_text_color(),
+ card: App.theme_navbar_background_color()
+ },
+ fonts: DefaultTheme.fonts
+ }}
+ ref={navigationRef => {
+ App.set_navigation(navigationRef)
+ }}
+ >
+
+
+
+
+
+
+
+ >
+ )
+ }
+}
diff --git a/src/screens/stacks/BookmarksStack.js b/src/screens/stacks/BookmarksStack.js
index 7389e58f..267d7f6f 100644
--- a/src/screens/stacks/BookmarksStack.js
+++ b/src/screens/stacks/BookmarksStack.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { Platform, View } from 'react-native';
+import { View } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import BookmarksScreen from '../bookmarks/bookmarks';
import ProfileImage from './../../components/header/profile_image';
@@ -11,6 +11,7 @@ import { getSharedScreens } from './SharedStack'
import TagsButton from '../../components/header/tags_button';
import BackButton from '../../components/header/back';
import App from '../../stores/App'
+import { android_status_bar_options, headerItemGroupStyle, headerLeftElement, headerRightElement } from '../../utils/navigation'
const BookmarksStack = createNativeStackNavigator();
@@ -24,25 +25,25 @@ export default class Bookmarks extends React.Component{
screenOptions={{
headerTintColor: App.theme_text_color(),
headerBackVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
({
- headerLeft: () => ,
- headerRight: () => (
-
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => (
+
- )
+ ))
})}
/>
({
- headerLeft: () => ,
+ ...headerLeftElement(() => ),
headerBackTitleVisible: false
})}
>
@@ -52,7 +53,7 @@ export default class Bookmarks extends React.Component{
component={BookmarkScreen}
options={({ }) => ({
headerTitle: `Bookmark`,
- headerRight: () =>
+ ...headerRightElement(() => )
})}
/>
diff --git a/src/screens/stacks/DiscoverStack.js b/src/screens/stacks/DiscoverStack.js
index 34aa6578..a063ea70 100644
--- a/src/screens/stacks/DiscoverStack.js
+++ b/src/screens/stacks/DiscoverStack.js
@@ -1,6 +1,5 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { Platform } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import DiscoverScreen from '../discover/discover';
import ProfileImage from './../../components/header/profile_image';
@@ -9,9 +8,35 @@ import BackButton from '../../components/header/back';
import DiscoverTopicScreen from '../../screens/discover/topic';
import { getSharedScreens } from './SharedStack'
import App from '../../stores/App'
+import Auth from '../../stores/Auth'
+import { android_status_bar_options, headerLeftElement, headerRightElement } from '../../utils/navigation'
+import { isLiquidGlass } from '../../utils/ui'
const DiscoverStack = createNativeStackNavigator();
+function newPostHeaderItem() {
+ if (Auth.selected_user == null || !Auth.selected_user.posting?.posting_enabled()) {
+ return null
+ }
+
+ return {
+ type: 'button',
+ label: 'New Post',
+ icon: {
+ type: 'sfSymbol',
+ name: 'square.and.pencil'
+ },
+ identifier: 'new-post',
+ tintColor: App.theme_text_color(),
+ width: 28,
+ onPress: () => App.navigate_to_screen("Posting")
+ }
+}
+
+function newPostHeaderItems() {
+ return [newPostHeaderItem()].filter(Boolean)
+}
+
@observer
export default class Discover extends React.Component{
@@ -22,20 +47,20 @@ export default class Discover extends React.Component{
screenOptions={{
headerTintColor: App.theme_text_color(),
headerBackVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
({
- headerLeft: () => ,
- headerRight: () =>
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => )
})}
/>
({
- headerLeft: () => ,
+ ...headerLeftElement(() => ),
headerBackTitleVisible: false
})}
>
@@ -45,7 +70,13 @@ export default class Discover extends React.Component{
component={DiscoverTopicScreen}
options={({ route }) => ({
headerTitle: `${route.params?.topic.emoji} ${route.params?.topic.title}`,
- headerRight: () =>
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: newPostHeaderItems
+ }
+ :
+ headerRightElement(() => )
+ )
})}
/>
diff --git a/src/screens/stacks/MentionsStack.js b/src/screens/stacks/MentionsStack.js
index 1d0282db..87c77f3d 100644
--- a/src/screens/stacks/MentionsStack.js
+++ b/src/screens/stacks/MentionsStack.js
@@ -1,6 +1,5 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { Platform } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MentionsScreen from '../mentions/mentions';
import ProfileImage from './../../components/header/profile_image';
@@ -8,6 +7,7 @@ import NewPostButton from '../../components/header/new_post';
import BackButton from '../../components/header/back';
import { getSharedScreens } from './SharedStack'
import App from '../../stores/App'
+import { android_status_bar_options, headerLeftElement, headerRightElement } from '../../utils/navigation'
const MentionsStack = createNativeStackNavigator();
@@ -21,20 +21,20 @@ export default class Mentions extends React.Component{
screenOptions={{
headerTintColor: App.theme_text_color(),
headerBackVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
({
- headerLeft: () => ,
- headerRight: () =>
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => )
})}
/>
({
- headerLeft: () => ,
+ ...headerLeftElement(() => ),
headerBackTitleVisible: false
})}
>
diff --git a/src/screens/stacks/PostEditStack.js b/src/screens/stacks/PostEditStack.js
index 97beed38..d60a5490 100644
--- a/src/screens/stacks/PostEditStack.js
+++ b/src/screens/stacks/PostEditStack.js
@@ -1,12 +1,12 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { Platform } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import PostEditScreen from '../posts/edit';
import PostingOptionsScreen from '../../screens/posts/options';
import ClosePostClearButton from '../../components/header/close_post_clear';
import UpdatePostButton from '../../components/header/update_post';
import App from '../../stores/App'
+import { android_status_bar_options, headerLeftElement, headerRightElement } from '../../utils/navigation'
const PostingEditStack = createNativeStackNavigator();
@@ -23,7 +23,7 @@ export default class PostEditStack extends React.Component{
headerBackTitle: "Back",
headerTintColor: App.theme_text_color(),
headerBackTitleVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
,
- headerLeft: () =>
+ ...headerRightElement(() => ),
+ ...headerLeftElement(() => )
}}
/>
{
+ Auth.selected_user.posting.selected_service?.pick_image(config?.temporary_destination())
+ }
+ },
+ {
+ type: 'action',
+ label: 'Files',
+ icon: {
+ type: 'sfSymbol',
+ name: 'folder'
+ },
+ onPress: () => {
+ Auth.selected_user.posting.selected_service?.pick_file(config?.temporary_destination())
+ }
+ }
+ ]
+ }
+ }
+}
+
+function uploadsHeaderItems() {
+ const items = []
+ if (Auth.selected_user?.posting?.selected_service?.is_loading_uploads) {
+ items.push({
+ type: 'custom',
+ element: ,
+ hidesSharedBackground: true
+ })
+ }
+
+ const upload_item = uploadMenuHeaderItem()
+ if (upload_item != null) {
+ items.push(upload_item)
+ }
+
+ return items
+}
+
@observer
export default class Posting extends React.Component{
@@ -29,7 +96,7 @@ export default class Posting extends React.Component{
headerBackTitle: "Back",
headerTintColor: App.theme_text_color(),
headerBackTitleVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
,
- headerRight: () =>
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => )
}}
/>
+ ...headerLeftElement(() => )
}}
/>
({
headerTitle: "Image Options",
- headerLeft: () => ,
- headerRight: () =>
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => )
})}
/>
,
- headerRight: () => {
- return (
-
-
-
-
- )
- }
+ ...headerLeftElement(() => ),
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: uploadsHeaderItems
+ }
+ :
+ headerRightElement(() => {
+ return (
+
+
+
+
+ )
+ })
+ )
}}
/>
diff --git a/src/screens/stacks/SharedStack.js b/src/screens/stacks/SharedStack.js
index dac5d236..74d4855b 100644
--- a/src/screens/stacks/SharedStack.js
+++ b/src/screens/stacks/SharedStack.js
@@ -15,7 +15,139 @@ import MutingScreen from '../settings/muting';
import CollectionsScreen from '../uploads/collections'
import NewPostButton from '../../components/header/new_post';
import NewCollectionButton from '../../components/header/new_collection';
+import Auth from '../../stores/Auth'
import App from '../../stores/App'
+import Replies from '../../stores/Replies'
+import { headerItemGroupStyle, headerRightElement } from '../../utils/navigation'
+import { isLiquidGlass } from '../../utils/ui'
+
+function newPostHeaderItem() {
+ if (Auth.selected_user == null || !Auth.selected_user.posting?.posting_enabled()) {
+ return null
+ }
+
+ return {
+ type: 'button',
+ label: 'New Post',
+ icon: {
+ type: 'sfSymbol',
+ name: 'square.and.pencil'
+ },
+ identifier: 'new-post',
+ tintColor: App.theme_text_color(),
+ width: 28,
+ onPress: () => App.navigate_to_screen("Posting")
+ }
+}
+
+function newPostHeaderItems() {
+ return [newPostHeaderItem()].filter(Boolean)
+}
+
+function refreshIsLoading(type, user = null) {
+ const selected_service = Auth.selected_user?.posting?.selected_service
+ switch (type) {
+ case "posts":
+ return !!(selected_service?.is_loading_posts || App.is_searching_posts)
+ case "pages":
+ return !!(selected_service?.is_loading_pages || App.is_searching_pages)
+ case "uploads":
+ return !!selected_service?.is_loading_uploads
+ case "muting":
+ return !!(user?.muting?.is_loading || user?.muting?.is_sending_mute || user?.muting?.is_sending_unmute)
+ default:
+ return !!Replies.is_loading
+ }
+}
+
+function refreshHeaderItems(type, user = null) {
+ if (!isLiquidGlass()) {
+ return headerRightElement(
+ () => ,
+ { hidesSharedBackground: true }
+ )
+ }
+
+ return {
+ unstable_headerRightItems: () => {
+ if (!refreshIsLoading(type, user)) {
+ return []
+ }
+
+ return [
+ {
+ type: 'custom',
+ element: ,
+ hidesSharedBackground: true
+ }
+ ]
+ }
+ }
+}
+
+function uploadMenuHeaderItem() {
+ if (Auth.selected_user == null || !Auth.selected_user.posting?.posting_enabled()) {
+ return null
+ }
+
+ const { config } = Auth.selected_user.posting.selected_service
+
+ return {
+ type: 'menu',
+ label: 'Upload',
+ icon: {
+ type: 'sfSymbol',
+ name: 'icloud.and.arrow.up'
+ },
+ identifier: 'new-upload',
+ tintColor: App.theme_text_color(),
+ width: 28,
+ menu: {
+ items: [
+ {
+ type: 'action',
+ label: 'Photo library',
+ icon: {
+ type: 'sfSymbol',
+ name: 'photo'
+ },
+ onPress: () => {
+ Auth.selected_user.posting.selected_service?.pick_image(config?.temporary_destination())
+ }
+ },
+ {
+ type: 'action',
+ label: 'Files',
+ icon: {
+ type: 'sfSymbol',
+ name: 'folder'
+ },
+ onPress: () => {
+ Auth.selected_user.posting.selected_service?.pick_file(config?.temporary_destination())
+ }
+ }
+ ]
+ }
+ }
+}
+
+function uploadsHeaderItems() {
+ const items = []
+ if (refreshIsLoading("uploads")) {
+ items.push({
+ type: 'custom',
+ element: ,
+ hidesSharedBackground: true
+ })
+ }
+
+ const upload_item = uploadMenuHeaderItem()
+ if (upload_item != null) {
+ items.push(upload_item)
+ }
+
+ return items
+}
export const getSharedScreens = (Stack, tab_name) => {
return [
@@ -25,7 +157,13 @@ export const getSharedScreens = (Stack, tab_name) => {
component={ProfileScreen}
options={({ route }) => ({
headerTitle: `@${route.params?.username}`,
- headerRight: () =>
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: newPostHeaderItems
+ }
+ :
+ headerRightElement(() => )
+ )
})}
/>,
{
component={ConversationScreen}
options={({ route }) => ({
headerTitle: `Conversation`,
- headerRight: () =>
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: () => [
+ {
+ type: 'button',
+ label: 'Reply',
+ icon: {
+ type: 'sfSymbol',
+ name: 'arrowshape.turn.up.left.fill'
+ },
+ identifier: 'conversation-reply',
+ tintColor: App.theme_text_color(),
+ width: 28,
+ onPress: () => {
+ if (route.params?.conversation_id != null) {
+ App.open_sheet("reply_sheet", { conversation_id: route.params.conversation_id })
+ }
+ }
+ }
+ ]
+ }
+ :
+ headerRightElement(() => )
+ )
})}
listeners={() => ({
focus: () => {
@@ -51,7 +212,13 @@ export const getSharedScreens = (Stack, tab_name) => {
component={FollowingScreen}
options={({ route }) => ({
headerTitle: `Following`,
- headerRight: () =>
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: newPostHeaderItems
+ }
+ :
+ headerRightElement(() => )
+ )
})}
/>,
{
component={UploadsScreen}
options={({ route }) => ({
headerTitle: `Uploads`,
- headerRight: () => {
- return (
-
-
-
-
- )
- }
+ ...(isLiquidGlass() ?
+ {
+ unstable_headerRightItems: uploadsHeaderItems
+ }
+ :
+ headerRightElement(() => {
+ return (
+
+
+
+
+ )
+ })
+ )
})}
/>,
{
component={RepliesScreen}
options={({ route }) => ({
headerTitle: "Replies",
- headerRight: () =>
+ ...refreshHeaderItems("replies")
})}
/>,
{
component={PagesScreen}
options={({ route }) => ({
headerTitle: "Pages",
- headerRight: () =>
+ ...refreshHeaderItems("pages")
})}
/>,
{
component={PostsScreen}
options={({ route }) => ({
headerTitle: "Posts",
- headerRight: () =>
+ ...refreshHeaderItems("posts")
})}
/>,
{
component={CollectionsScreen}
options={({ route }) => ({
headerTitle: "Collections",
- headerRight: () =>
+ ...headerRightElement(() => )
})}
/>,
{
component={MutingScreen}
options={({ route }) => ({
headerTitle: "Muting",
- headerRight: () =>
+ ...refreshHeaderItems("muting", route.params?.user)
})}
/>,
]
diff --git a/src/screens/stacks/TabNavigator.js b/src/screens/stacks/TabNavigator.js
index 1b11ab20..eba09ae1 100644
--- a/src/screens/stacks/TabNavigator.js
+++ b/src/screens/stacks/TabNavigator.js
@@ -1,40 +1,106 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
-import App from '../../stores/App';
-import TimelineStack from './TimelineStack';
-import MentionsStack from './MentionsStack';
-import BookmarksStack from './BookmarksStack';
-import DiscoverStack from './DiscoverStack';
-import TabIcon from '../../components/tabs/tab';
+import * as React from 'react'
+import { Platform } from 'react-native'
+import { observer } from 'mobx-react'
+import { createNativeBottomTabNavigator } from '@react-navigation/bottom-tabs/unstable'
+import App from '../../stores/App'
+import TimelineStack from './TimelineStack'
+import MentionsStack from './MentionsStack'
+import BookmarksStack from './BookmarksStack'
+import DiscoverStack from './DiscoverStack'
+import TimelineIcon from './../../assets/icons/tab_bar/timeline.png'
+import MentionsIcon from './../../assets/icons/tab_bar/mentions.png'
+import DiscoverIcon from './../../assets/icons/tab_bar/discover.png'
+import BookmarksIcon from './../../assets/icons/nav/bookmarks.png'
+import { isLiquidGlass, liquidGlassTintColor } from '../../utils/ui'
-const Tab = createBottomTabNavigator();
+const Tab = createNativeBottomTabNavigator()
+
+const TAB_ICONS = {
+ TimelineStack: {
+ ios: {
+ default: 'bubble.left.and.bubble.right',
+ selected: 'bubble.left.and.bubble.right'
+ },
+ image: TimelineIcon
+ },
+ MentionsStack: {
+ ios: {
+ default: 'at',
+ selected: 'at'
+ },
+ image: MentionsIcon
+ },
+ BookmarksStack: {
+ ios: {
+ default: 'star',
+ selected: 'star'
+ },
+ image: BookmarksIcon
+ },
+ DiscoverStack: {
+ ios: {
+ default: 'magnifyingglass',
+ selected: 'magnifyingglass'
+ },
+ image: DiscoverIcon
+ }
+}
@observer
export default class TabNavigator extends React.Component {
+ tab_icon(route_name, focused) {
+ const icon = TAB_ICONS[route_name]
+
+ if (Platform.OS === 'ios') {
+ return {
+ type: 'sfSymbol',
+ name: focused ? icon.ios.selected : icon.ios.default
+ }
+ }
+
+ return {
+ type: 'image',
+ source: icon.image
+ }
+ }
+
+ tab_tint_color() {
+ return App.theme_accent_color()
+ }
+
+ inactive_tab_tint_color() {
+ return isLiquidGlass() ? liquidGlassTintColor() : App.theme_text_color()
+ }
+
+ tab_bar_style() {
+ if (isLiquidGlass()) {
+ return undefined
+ }
+
+ return {
+ backgroundColor: App.theme_navbar_background_color(),
+ shadowColor: App.theme_tabbar_divider_color()
+ }
+ }
+
render() {
return (
({
- tabBarStyle: {
- borderTopColor: App.theme_tabbar_divider_color(),
- borderTopWidth: 0.5,
- backgroundColor: App.theme_navbar_background_color(),
- elevation: 0,
- shadowOpacity: 0
- },
- tabBarIcon: ({ focused, color, size }) => {
- return ;
+ tabBarStyle: this.tab_bar_style(),
+ tabBarIcon: ({ focused }) => {
+ return this.tab_icon(route.name, focused)
},
headerShown: false,
- tabBarActiveTintColor: App.theme_accent_color()
+ tabBarActiveTintColor: this.tab_tint_color(),
+ tabBarInactiveTintColor: this.inactive_tab_tint_color()
})}
screenListeners={{
state: (e) => {
- App.set_current_tab_index(e.data.state.index)
+ App.set_current_tab_index(e.data?.state?.index)
},
focus: (e) => {
App.set_current_tab_key(e.target)
diff --git a/src/screens/stacks/TimelineStack.js b/src/screens/stacks/TimelineStack.js
index dad6729d..d5252788 100644
--- a/src/screens/stacks/TimelineStack.js
+++ b/src/screens/stacks/TimelineStack.js
@@ -1,7 +1,6 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { Platform } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import TimelineScreen from '../timeline/timeline';
import ProfileImage from './../../components/header/profile_image';
@@ -9,6 +8,7 @@ import NewPostButton from '../../components/header/new_post';
import BackButton from '../../components/header/back';
import { getSharedScreens } from './SharedStack'
import App from '../../stores/App'
+import { android_status_bar_options, headerLeftElement, headerRightElement } from '../../utils/navigation'
const TimelineStack = createNativeStackNavigator();
@@ -22,20 +22,20 @@ export default class Timeline extends React.Component{
screenOptions={{
headerTintColor: App.theme_text_color(),
headerBackVisible: false,
- headerStatusBarHeight: Platform.OS === 'android' ? 0 : undefined
+ ...android_status_bar_options()
}}
>
({
- headerLeft: () => ,
- headerRight: () => ,
+ ...headerLeftElement(() => ),
+ ...headerRightElement(() => ),
})}
/>
({
- headerLeft: () => ,
+ ...headerLeftElement(() => ),
headerBackTitleVisible: false,
})}
>
diff --git a/src/screens/uploads/add_collection.js b/src/screens/uploads/add_collection.js
index c27e9533..8a867dd6 100644
--- a/src/screens/uploads/add_collection.js
+++ b/src/screens/uploads/add_collection.js
@@ -73,7 +73,7 @@ export default class AddCollectionScreen extends React.Component{
style={{
backgroundColor: App.theme_input_contrast_background_color(),
fontSize: 17,
- borderColor: '#f80',
+ borderColor: App.theme_accent_color(),
borderWidth: 1,
height: 50,
width: "100%",
@@ -86,7 +86,7 @@ export default class AddCollectionScreen extends React.Component{
/>
diff --git a/src/screens/uploads/uploads.js b/src/screens/uploads/uploads.js
index e0f136da..41e36d86 100644
--- a/src/screens/uploads/uploads.js
+++ b/src/screens/uploads/uploads.js
@@ -1,17 +1,17 @@
import * as React from 'react';
import { observer } from 'mobx-react';
-import { View, Text, FlatList, RefreshControl, TouchableOpacity, TextInput, Keyboard, Dimensions, Image, Platform } from 'react-native';
+import { View, Text, FlatList, RefreshControl, TouchableOpacity, Keyboard, Dimensions, Image, Platform } from 'react-native';
import Auth from './../../stores/Auth';
import LoginMessage from '../../components/info/login_message'
import App from '../../stores/App'
-import { SheetProvider } from "react-native-actions-sheet";
import UploadCell from '../../components/cells/upload_cell'
import TempUploadCell from '../../components/cells/temp_upload_cell'
import SearchIcon from '../../assets/icons/nav/discover.png';
import SearchBar from '../../components/search_bar';
import { SFSymbol } from "react-native-sfsymbols";
-import { SvgXml } from 'react-native-svg';
import DeviceInfo from 'react-native-device-info';
+import { SafeAreaInsetsContext } from 'react-native-safe-area-context'
+import { tabBarScrollContentBottomPadding } from '../../utils/ui'
@observer
export default class UploadsScreen extends React.Component{
@@ -20,13 +20,43 @@ export default class UploadsScreen extends React.Component{
super(props)
this.flatListRef = React.createRef();
+ this.requested_uploads_key = null
this.state = {
num_columns: DeviceInfo.isTablet() ? 4 : 3
}
}
componentDidMount() {
- Auth.selected_user.posting?.selected_service?.update_uploads_for_active_destination()
+ this._refresh_uploads_if_needed()
+ }
+
+ componentDidUpdate() {
+ this._refresh_uploads_if_needed()
+ }
+
+ _current_uploads_context = () => {
+ const selected_service = Auth.selected_user.posting?.selected_service
+ const destination = selected_service?.config?.uploads_destination()
+ return { selected_service, destination }
+ }
+
+ _uploads_request_key = (selected_service, destination) => {
+ return `${selected_service?.id || ""}:${destination?.uid || ""}`
+ }
+
+ _refresh_uploads_if_needed = () => {
+ const { selected_service, destination } = this._current_uploads_context()
+ if (!selected_service || !destination) {
+ return
+ }
+
+ const request_key = this._uploads_request_key(selected_service, destination)
+ if (request_key === this.requested_uploads_key) {
+ return
+ }
+
+ this.requested_uploads_key = request_key
+ selected_service.check_for_uploads_for_destination(destination)
}
_scroll_to_top = () => {
@@ -37,6 +67,7 @@ export default class UploadsScreen extends React.Component{
_return_header = () => {
const { config } = Auth.selected_user.posting.selected_service
+ const destination_name = config.posts_destination()?.name ?? "current destination"
return(
!App.uploads_search_is_open ?
- !this.props.did_open_from_editor ? App.open_sheet("posts_destination_menu", { type: "uploads" }) : null}>
+ !this.props.did_open_from_editor ? App.open_sheet("posts_destination_menu", { type: "uploads" }) : null}
+ disabled={!!this.props.did_open_from_editor}
+ accessibilityRole="button"
+ accessibilityLabel={`Choose uploads destination, ${destination_name}`}
+ accessibilityState={{ disabled: !!this.props.did_open_from_editor }}
+ >
- {config.posts_destination()?.name}
+ {destination_name}
{
Platform.OS === "ios" ?
@@ -114,29 +153,43 @@ export default class UploadsScreen extends React.Component{
_return_uploads_list = () => {
const { selected_service } = Auth.selected_user.posting
const { config } = selected_service
+ const destination = config.uploads_destination()
+ const uploads = config.uploads_for_destination()?.slice() || []
+ const list_key = `${selected_service.id}-${destination?.uid || "uploads"}-${this.state.num_columns}`
return(
-
+ {insets => {
+ const bottom_padding = tabBarScrollContentBottomPadding(insets?.bottom, 10)
+
+ return (
+ destination && selected_service.check_for_uploads_for_destination(destination)}
+ />
+ }
+ />
+ )
}}
- numColumns={this.state.num_columns}
- removeClippedSubviews={true}
- initialNumToRender={12}
- maxToRenderPerBatch={6}
- windowSize={5}
- refreshControl={
- selected_service.check_for_uploads_for_destination(config.posts_destination())}
- />
- }
- />
+
)
}
@@ -151,22 +204,26 @@ export default class UploadsScreen extends React.Component{
_return_temp_uploads_list = () => {
const { selected_service } = Auth.selected_user.posting
const { config } = selected_service
- const dimension = (Dimensions.get("screen")?.width / 4) + 5
- if (config.temp_uploads_for_destination()?.length) {
+ const temp_uploads = config.temp_uploads_for_destination()?.slice() || []
+ const cell_dimension = Dimensions.get("screen")?.width / 4 - 10
+ const row_height = cell_dimension + 22
+ if (temp_uploads.length) {
return (
)
@@ -178,20 +235,18 @@ export default class UploadsScreen extends React.Component{
render() {
return (
-
-
- {
- Auth.is_logged_in() && !Auth.is_selecting_user ?
- <>
- {this._return_header()}
- {this._return_temp_uploads_list()}
- {this._return_uploads_list()}
- >
- :
-
- }
-
-
+
+ {
+ Auth.is_logged_in() && !Auth.is_selecting_user ?
+ <>
+ {this._return_header()}
+ {this._return_temp_uploads_list()}
+ {this._return_uploads_list()}
+ >
+ :
+
+ }
+
)
}
diff --git a/src/stores/App.js b/src/stores/App.js
index 76b8c196..92b15e74 100644
--- a/src/stores/App.js
+++ b/src/stores/App.js
@@ -1,5 +1,5 @@
import { types, flow } from 'mobx-state-tree';
-import { Linking, Appearance, AppState, Platform, Dimensions, Alert, Keyboard } from 'react-native'
+import { Linking, Appearance, AppState, Platform, Dimensions, Alert, Keyboard, NativeModules } from 'react-native'
import MicroBlogApi, { API_ERROR } from '../api/MicroBlogApi';
import Toast from 'react-native-simple-toast';
import * as WebBrowser from 'expo-web-browser';
@@ -15,17 +15,50 @@ import Settings from "./Settings"
import Services from './Services';
import Contact from './models/posting/Contact'
import Push from './Push'
+import { should_remount_webview_on_android_resume } from '../utils/webview_recovery'
+import {
+ DEFAULT_ACCENT_COLOR,
+ normalise_accent_color,
+ normalise_theme,
+ resolve_app_accent_color,
+ resolve_app_theme,
+} from '../utils/theme'
let SCROLLING_TIMEOUT = null
let CURRENT_WEB_VIEW_REF = null
let PUBLISHING_PROGRESS_TIMEOUT = null
+let SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = false
+let WEBVIEW_HEALTH_PING_ID = null
+let WEBVIEW_HEALTH_PING_TIMEOUT = null
+let WEBVIEW_CONVERSATION_CLICK_SUPPRESS_UNTIL = 0
+
+const WEBVIEW_HEALTH_PING_PREFIX = '__webview_health_ping__'
+const WEBVIEW_HEALTH_PING_TIMEOUT_MS = 1500
+const WEBVIEW_HEALTH_PING_DELAY_MS = 400
+const WEBVIEW_CONVERSATION_CLICK_SUPPRESSION_MS = 700
+
+const suppress_next_webview_conversation_click = () => {
+ WEBVIEW_CONVERSATION_CLICK_SUPPRESS_UNTIL = Date.now() + WEBVIEW_CONVERSATION_CLICK_SUPPRESSION_MS
+}
+
+const consume_suppressed_webview_conversation_click = () => {
+ if (Date.now() < WEBVIEW_CONVERSATION_CLICK_SUPPRESS_UNTIL) {
+ WEBVIEW_CONVERSATION_CLICK_SUPPRESS_UNTIL = 0
+ return true
+ }
+
+ return false
+}
export default App = types.model('App', {
- is_loading: types.optional(types.boolean, false),
+ is_loading: types.optional(types.boolean, true),
+ navigation_ready: types.optional(types.boolean, false),
+ app_state: types.optional(types.string, AppState.currentState || 'active'),
image_modal_is_open: types.optional(types.boolean, false),
current_image_url: types.maybeNull(types.string),
is_scrolling: types.optional(types.boolean, false),
theme: types.optional(types.string, 'light'),
+ accent_color: types.optional(types.string, DEFAULT_ACCENT_COLOR),
is_switching_theme: types.optional(types.boolean, false),
post_modal_is_open: types.optional(types.boolean, false),
font_scale: types.optional(types.number, 1),
@@ -59,7 +92,8 @@ export default App = types.model('App', {
is_publishing: types.optional(types.boolean, false),
latest_published_url: types.maybeNull(types.string),
keyboard_height: types.optional(types.number, 0),
- is_keyboard_visible: types.optional(types.boolean, false)
+ is_keyboard_visible: types.optional(types.boolean, false),
+ web_view_epoch: types.optional(types.number, 0),
})
.volatile(self => ({
navigation_ref: null,
@@ -67,6 +101,12 @@ export default App = types.model('App', {
current_raw_tab_key: null,
keyboard_show_listener: null,
keyboard_hide_listener: null,
+ app_state_listener: null,
+ appearance_listener: null,
+ font_scale_listener: null,
+ url_listener: null,
+ previous_app_state: AppState.currentState,
+ post_search_request_id: 0,
}))
.actions(self => ({
@@ -74,49 +114,69 @@ export default App = types.model('App', {
console.log("App:set_navigation", navigation != null)
if (navigation) {
self.navigation_ref = navigation
+ self.navigation_ready = typeof navigation.isReady === 'function' ? navigation.isReady() : false
+ Push.replay_pending_notification()
+ if (SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY && self.navigation_ready) {
+ App.reset_to_tabs()
+ }
+ }
+ else{
+ self.navigation_ref = null
+ self.navigation_ready = false
+ }
+ }),
+
+ set_navigation_ready: flow(function*(ready = true) {
+ console.log("App:set_navigation_ready", ready)
+ self.navigation_ready = ready
+ if (ready) {
+ Push.replay_pending_notification()
+ if (SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY) {
+ App.reset_to_tabs()
+ }
}
}),
hydrate: flow(function*() {
console.log("App:hydrate")
self.is_loading = true
+ self.navigation_ready = false
+ self.app_state = AppState.currentState || 'active'
+ Push.set_auth_ready(false)
+ yield Settings.hydrate()
yield App.set_current_initial_theme()
+ yield App.sync_current_accent_color()
yield App.set_current_initial_font_scale()
- yield Push.hydrate()
-
- Settings.hydrate()
- App.set_is_loading(false)
App.set_up_url_listener()
App.setup_keyboard_listeners()
try{
yield Auth.hydrate()
console.log("App:hydrate:started:is_logged_in", Auth.is_logged_in())
- yield App.set_current_initial_theme()
- yield App.set_current_initial_font_scale()
- yield Push.hydrate()
-
- Settings.hydrate()
- App.set_is_loading(false)
- App.set_up_url_listener()
- App.setup_keyboard_listeners()
if (Auth.is_logged_in()) {
- Push.handle_first_notification()
- }
- else if (!Auth.is_logged_in()) {
- App.navigate_to_screen("Login")
+ Push.replay_pending_notification()
}
}
catch(error){
console.error(error)
}
+ finally{
+ Push.set_auth_ready(true)
+ Push.replay_pending_notification()
+ App.set_is_loading(false)
+ }
}),
- prep_and_hydrate_share_extension: flow(function*() {
+ prep_and_hydrate_share_extension: flow(function*(color_scheme = null) {
console.log("App:prep_and_hydrate_share_extension")
self.is_share_extension = true
yield App.set_current_initial_theme()
+ yield App.sync_current_accent_color()
+ if (color_scheme != null) {
+ yield App.change_current_theme(color_scheme)
+ yield App.sync_current_accent_color()
+ }
}),
dehydrate_share_extension: flow(function*() {
@@ -136,6 +196,10 @@ export default App = types.model('App', {
setup_keyboard_listeners: flow(function*() {
console.log("App:setup_keyboard_listeners")
+ if (self.keyboard_show_listener != null || self.keyboard_hide_listener != null) {
+ return
+ }
+
if (Platform.OS === 'android') {
self.keyboard_show_listener = Keyboard.addListener('keyboardDidShow', (event) => {
console.log("Keyboard shown with height:", event.endCoordinates.height)
@@ -163,9 +227,13 @@ export default App = types.model('App', {
set_up_url_listener: flow(function*() {
console.log("App:set_up_url_listener")
- Linking.addEventListener('url', (event) => {
+ if (self.url_listener != null) {
+ return
+ }
+
+ self.url_listener = Linking.addEventListener('url', (event) => {
console.log("App:set_up_url_listener:event", event)
- if (event?.url && event?.url.indexOf('/signin/') > -1) {
+ if (event?.url && Login.can_handle_open_url(event.url)) {
Login.trigger_login_from_url(event.url)
}
else if (event?.url && event?.url.includes('/post?text=') && Auth.is_logged_in()) {
@@ -181,7 +249,7 @@ export default App = types.model('App', {
})
Linking.getInitialURL().then((value) => {
console.log("App:set_up_url_listener:getInitialURL", value)
- if (value?.indexOf('/signin/') > -1) {
+ if (value && Login.can_handle_open_url(value)) {
Login.trigger_login_from_url(value)
}
else if (value?.includes('/post?text=') && Auth.is_logged_in()) {
@@ -200,13 +268,13 @@ export default App = types.model('App', {
close_sheet: flow(function*(sheet_name = null) {
console.log("App:close_sheet", sheet_name)
if (sheet_name != null) {
- SheetManager.hide(sheet_name)
+ yield SheetManager.hide(sheet_name)
}
}),
close_all_sheets: flow(function*() {
console.log("App:close_all_sheets")
- SheetManager.hideAll()
+ yield SheetManager.hideAll()
}),
set_current_tab_key: flow(function*(tab_key) {
@@ -257,6 +325,15 @@ export default App = types.model('App', {
console.log("App:handle_url:action", action, action_data)
if (action != null && action_data != null) {
+ if (action === "open" && consume_suppressed_webview_conversation_click()) {
+ console.log("App:handle_url:suppressed_open_after_link", action_data)
+ return
+ }
+
+ if (action != "open") {
+ suppress_next_webview_conversation_click()
+ }
+
if (action === "user" || action === "photo" || action === "open" || action === "reply") {
self.navigate_to_screen(action, action_data)
}
@@ -271,7 +348,8 @@ export default App = types.model('App', {
navigate_to_screen: flow(function*(screen_name = null, action_data = null, from_listener = false) {
console.log("App:navigate_to_screen", screen_name, action_data, from_listener)
- if (screen_name != null && self.navigation_ref != null && !self.is_scrolling) {
+ const navigation_is_ready = self.navigation_ref != null && self.navigation_ready && (typeof self.navigation_ref.isReady !== 'function' || self.navigation_ref.isReady())
+ if (screen_name != null && navigation_is_ready && !self.is_scrolling) {
switch (screen_name) {
case "photo":
return App.set_image_modal_data_and_activate(action_data)
@@ -349,6 +427,7 @@ export default App = types.model('App', {
case "Pages":
return self.navigation_ref.navigate(`${self.current_tab_key}-Pages`)
case "Posting":
+ Auth.selected_user?.posting.clear_post()
if (action_data != null && !from_listener) {
// Action data is usually markdown text from a highlight,
// unless it's from the url listener.
@@ -392,9 +471,49 @@ export default App = types.model('App', {
}
}),
+ reset_to_tabs: flow(function*() {
+ const reset_state = {
+ index: 0,
+ routes: [{ name: "Tabs" }]
+ }
+ const navigation_is_ready = self.navigation_ref != null && self.navigation_ready && (typeof self.navigation_ref.isReady !== 'function' || self.navigation_ref.isReady())
+
+ if (!navigation_is_ready) {
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = true
+ return false
+ }
+
+ if (self.navigation_ref?.resetRoot != null) {
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = false
+ self.navigation_ref.resetRoot(reset_state)
+ return true
+ }
+
+ if (self.navigation_ref?.reset != null) {
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = false
+ self.navigation_ref.reset(reset_state)
+ return true
+ }
+
+ if (self.navigation_ref?.dispatch != null) {
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = false
+ self.navigation_ref.dispatch(CommonActions.reset(reset_state))
+ return true
+ }
+
+ if (self.navigation_ref?.navigate != null) {
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = false
+ self.navigation_ref.navigate("Tabs")
+ return true
+ }
+
+ SHOULD_RESET_TO_TABS_WHEN_NAVIGATION_READY = true
+ return false
+ }),
+
navigate_to_screen_from_menu: flow(function*(screen) {
console.log("App:navigate_to_screen_from_menu", screen)
- App.close_sheet("main_sheet")
+ yield App.close_sheet("main_sheet")
switch (screen) {
case "Help":
return self.navigate_to_screen("Help")
@@ -408,6 +527,8 @@ export default App = types.model('App', {
return self.navigate_to_screen("Pages")
case "Uploads":
return self.navigate_to_screen("uploads")
+ case "Login":
+ return self.navigate_to_screen("Login")
case "PostService":
return self.navigate_to_screen("post_service", Auth.selected_user)
}
@@ -445,6 +566,21 @@ export default App = types.model('App', {
return
}
+ if (url.indexOf('microblog://') === 0) {
+ App.handle_url(url)
+ return
+ }
+
+ const open_url_from_webview = (webview_url) => {
+ suppress_next_webview_conversation_click()
+ App.open_url(webview_url)
+ }
+
+ const navigate_from_webview = (screen_name, action_data = null) => {
+ suppress_next_webview_conversation_click()
+ App.navigate_to_screen(screen_name, action_data)
+ }
+
// This is going to be messy. Don't judge.
if (url.indexOf('https://micro.blog/') > -1 || url.indexOf('http://micro.blog/') > -1) {
const after_mb_link = url.replace('https://micro.blog/', '').replace('http://micro.blog/', '');
@@ -456,12 +592,13 @@ export default App = types.model('App', {
}
if (after_mb_link.includes("hybrid/discover")) {
+ suppress_next_webview_conversation_click()
App.navigate_to_screen_from_menu("Discover")
return
}
if (parts.length === 3 && parts[0] === 'discover' && parts[1] !== null && parts[2] === "grid") {
- App.open_url(url)
+ open_url_from_webview(url)
}
else if (parts.length === 2) {
// This is probably a convo
@@ -472,47 +609,52 @@ export default App = types.model('App', {
if (parts[0] === 'discover' && parts[1] !== null) {
if (parts[1].indexOf('search') > -1) {
// This going to be a user search, but for now we just link it
- App.open_url(url)
+ open_url_from_webview(url)
}
else {
const topic = Discover.topic_by_slug(parts[1])
if (topic != null) {
- App.navigate_to_screen("discover/topic", topic)
+ navigate_from_webview("discover/topic", topic)
}
else {
- App.open_url(url)
+ open_url_from_webview(url)
}
}
}
else {
- App.open_url(url)
+ open_url_from_webview(url)
}
}
else if (parts[0] === "books") {
- App.open_url(url)
+ open_url_from_webview(url)
}
else if (parts[0] === "bookmarks") {
- App.navigate_to_screen("bookmark", number[0])
+ navigate_from_webview("bookmark", number[0])
}
else {
+ if (consume_suppressed_webview_conversation_click()) {
+ console.log("App:handle_url_from_webview:suppressed_open_after_link", url)
+ return
+ }
+
App.navigate_to_screen("open", number[0])
}
}
else if (parts?.length >= 4) {
- App.open_url(url)
+ open_url_from_webview(url)
}
else if (parts?.length === 1 && (parts[0] === "summer" || parts[0] === "about")) {
- App.open_url(url)
+ open_url_from_webview(url)
}
else {
const username = url.replace('https://micro.blog/', '').replace('http://micro.blog/', '')
- App.navigate_to_screen("user", username)
+ navigate_from_webview("user", username)
}
}
else {
// It's just a normal URL
- App.open_url(url)
+ open_url_from_webview(url)
}
}),
@@ -527,7 +669,7 @@ export default App = types.model('App', {
if (supported) {
if (!open_external && !Settings.open_links_in_external_browser) {
WebBrowser.openBrowserAsync(url, {
- controlsColor: '#f80',
+ controlsColor: App.theme_accent_color(),
dismissButtonStyle: 'close',
readerMode: Settings.open_links_with_reader_mode
})
@@ -554,8 +696,25 @@ export default App = types.model('App', {
CURRENT_WEB_VIEW_REF = current_ref
}),
+ clear_current_web_view_ref: flow(function*(current_ref = null) {
+ console.log("App:clear_current_web_view_ref")
+ if (current_ref == null || CURRENT_WEB_VIEW_REF === current_ref) {
+ CURRENT_WEB_VIEW_REF = null
+ }
+ }),
+
handle_web_view_message: flow(function*(message) {
console.log("App:handle_web_view_message", message)
+ if (`${message}`.startsWith(WEBVIEW_HEALTH_PING_PREFIX)) {
+ const ping_id = `${message}`.slice(WEBVIEW_HEALTH_PING_PREFIX.length)
+ if (ping_id === WEBVIEW_HEALTH_PING_ID) {
+ console.log("App:check_web_view_health_on_resume:pong", ping_id)
+ clearTimeout(WEBVIEW_HEALTH_PING_TIMEOUT)
+ WEBVIEW_HEALTH_PING_TIMEOUT = null
+ WEBVIEW_HEALTH_PING_ID = null
+ }
+ return
+ }
if (message === "bookmark_added") {
Toast.showWithGravity("Bookmark added!", Toast.SHORT, Toast.CENTER)
}
@@ -599,36 +758,158 @@ export default App = types.model('App', {
}
}),
+ reload_current_web_view: flow(function*() {
+ if (!CURRENT_WEB_VIEW_REF) {
+ return
+ }
+
+ try {
+ CURRENT_WEB_VIEW_REF.reload()
+ } catch (error) {
+ console.log("App:reload_current_web_view:error", error)
+ }
+ }),
+
+ check_web_view_health_on_resume: flow(function*() {
+ if (Platform.OS !== 'ios' || !Auth.did_load_one_or_more_webviews || !CURRENT_WEB_VIEW_REF) {
+ return
+ }
+
+ const ping_id = `${Date.now()}`
+ WEBVIEW_HEALTH_PING_ID = ping_id
+ clearTimeout(WEBVIEW_HEALTH_PING_TIMEOUT)
+
+ try {
+ CURRENT_WEB_VIEW_REF.injectJavaScript(`
+ (function() {
+ try {
+ window.ReactNativeWebView.postMessage('${WEBVIEW_HEALTH_PING_PREFIX}${ping_id}')
+ } catch (error) {}
+ })()
+ true
+ `)
+ } catch (error) {
+ console.log("App:check_web_view_health_on_resume:inject_error", error)
+ WEBVIEW_HEALTH_PING_ID = null
+ App.bump_web_view_epoch()
+ return
+ }
+
+ WEBVIEW_HEALTH_PING_TIMEOUT = setTimeout(() => {
+ if (WEBVIEW_HEALTH_PING_ID !== ping_id) {
+ return
+ }
+
+ console.log("App:check_web_view_health_on_resume:timeout", ping_id)
+ WEBVIEW_HEALTH_PING_ID = null
+ WEBVIEW_HEALTH_PING_TIMEOUT = null
+ App.bump_web_view_epoch()
+ }, WEBVIEW_HEALTH_PING_TIMEOUT_MS)
+ }),
+
set_current_initial_theme: flow(function*() {
- console.log("App:set_current_theme", Appearance.getColorScheme())
- self.theme = Appearance.getColorScheme()
+ const color_scheme = App.resolved_theme()
+ console.log("App:set_current_theme", color_scheme)
+ self.theme = color_scheme
App.set_up_appearance_listener()
}),
+ handle_app_state_change: flow(function*(nextAppState) {
+ console.log("App:handle_app_state_change", self.previous_app_state, "->", nextAppState)
+ const should_force_web_view_remount = App.should_follow_system_theme() && should_remount_webview_on_android_resume({
+ next_app_state: nextAppState,
+ platform_os: Platform.OS,
+ previous_app_state: self.previous_app_state,
+ system_theme: Appearance.getColorScheme(),
+ theme: self.theme,
+ })
+
+ self.previous_app_state = nextAppState
+ self.app_state = nextAppState
+
+ if (nextAppState === "active") {
+ const color_scheme = App.resolved_theme()
+ if (self.theme !== color_scheme || should_force_web_view_remount) {
+ yield App.change_current_theme(color_scheme, should_force_web_view_remount)
+ }
+ yield App.sync_current_accent_color()
+ Push.replay_pending_notification()
+ if (Platform.OS === 'ios') {
+ setTimeout(() => App.check_web_view_health_on_resume(), WEBVIEW_HEALTH_PING_DELAY_MS)
+ }
+ }
+ }),
+
+ handle_appearance_change: flow(function*(colorScheme) {
+ if (AppState.currentState === "background" || AppState.currentState === "inactive") {
+ return
+ }
+
+ if (!App.should_follow_system_theme()) {
+ return
+ }
+
+ console.log("App:set_up_appearance_listener:change", colorScheme)
+ yield App.change_current_theme(colorScheme)
+ yield App.sync_current_accent_color()
+ }),
+
set_up_appearance_listener: flow(function*() {
console.log("App:set_up_appearance_listener")
- AppState.addEventListener("change", nextAppState => {
- if (nextAppState === "active") {
- const colorScheme = Appearance.getColorScheme()
- if (self.theme !== colorScheme) {
- App.change_current_theme(colorScheme)
- }
- }
+ if (self.app_state_listener != null || self.appearance_listener != null) {
+ return
+ }
+
+ self.previous_app_state = AppState.currentState
+
+ self.app_state_listener = AppState.addEventListener("change", nextAppState => {
+ App.handle_app_state_change(nextAppState)
})
- Appearance.addChangeListener(({ colorScheme }) => {
- if (AppState.currentState === "background" || AppState.currentState === "inactive") {
- return
- }
- console.log("App:set_up_appearance_listener:change", colorScheme)
- App.change_current_theme(colorScheme)
+ self.appearance_listener = Appearance.addChangeListener(({ colorScheme }) => {
+ App.handle_appearance_change(colorScheme)
})
}),
- change_current_theme: flow(function*(color) {
- console.log("App:change_current_theme", color)
- self.is_switching_theme = true
- self.theme = color
- self.is_switching_theme = false
+ bump_web_view_epoch: flow(function*() {
+ self.web_view_epoch = self.web_view_epoch + 1
+ }),
+
+ change_current_theme: flow(function*(color, should_force_web_view_remount = false) {
+ const next_theme = normalise_theme(color)
+ console.log("App:change_current_theme", next_theme, should_force_web_view_remount)
+
+ if (self.theme === next_theme && !should_force_web_view_remount) {
+ return
+ }
+
+ self.theme = next_theme
+
+ if (should_force_web_view_remount) {
+ App.bump_web_view_epoch()
+ }
+ }),
+
+ sync_current_theme: flow(function*(should_force_web_view_remount = false) {
+ console.log("App:sync_current_theme", should_force_web_view_remount)
+ yield App.change_current_theme(App.resolved_theme(), should_force_web_view_remount)
+ yield App.sync_current_accent_color()
+ }),
+
+ sync_current_accent_color: flow(function*() {
+ console.log("App:sync_current_accent_color")
+ if (Platform.OS !== "android" || !Settings.auto_android_theme) {
+ self.accent_color = DEFAULT_ACCENT_COLOR
+ return
+ }
+
+ try {
+ const accent_color = yield NativeModules.MBAndroidTheme?.getAccentColor(self.theme)
+ self.accent_color = normalise_accent_color(accent_color)
+ }
+ catch(error) {
+ console.log("App:sync_current_accent_color:error", error)
+ self.accent_color = DEFAULT_ACCENT_COLOR
+ }
}),
set_current_initial_font_scale: flow(function*() {
@@ -640,7 +921,11 @@ export default App = types.model('App', {
set_up_font_scale_listener: flow(function*() {
console.log("App:set_up_font_scale_listener")
- Dimensions.addEventListener(
+ if (self.font_scale_listener != null) {
+ return
+ }
+
+ self.font_scale_listener = Dimensions.addEventListener(
"change",
({ screen }) => {
console.log("App:set_up_font_scale_listener:change", screen?.fontScale)
@@ -779,25 +1064,46 @@ export default App = types.model('App', {
console.log("App:toggle_uploads_search_is_open")
self.uploads_search_is_open = !self.uploads_search_is_open
}),
+
+ set_post_search_text(text) {
+ self.post_search_request_id += 1
+ self.post_search_query = text
+ self.is_searching_posts = false
+ },
set_posts_query: flow(function*(text, destination, show_drafts = false) {
console.log("App:set_posts_query", text, show_drafts)
+ self.post_search_request_id += 1
+ const request_id = self.post_search_request_id
self.post_search_query = text
+
if (text?.length > 2) {
self.is_searching_posts = true
const results = yield MicroBlogApi.search_posts_and_pages(text, destination?.uid, false, show_drafts)
+ if (request_id !== self.post_search_request_id || text !== self.post_search_query) {
+ return
+ }
if (results !== API_ERROR && results.items != null) {
if (show_drafts) {
- destination.set_drafts(results.items);
+ destination?.set_drafts(results.items);
}
else {
- destination.set_posts(results.items);
+ destination?.set_posts(results.items);
}
}
self.is_searching_posts = false
}
- else if (self.post_search_query == "") {
- Auth.selected_user.posting?.selected_service?.update_posts_for_active_destination()
+ else {
+ if (request_id !== self.post_search_request_id || text !== self.post_search_query) {
+ return
+ }
+ self.is_searching_posts = false
+ if (destination != null) {
+ Auth.selected_user.posting?.selected_service?.check_for_posts_for_destination(destination, show_drafts)
+ }
+ else if (self.post_search_query == "") {
+ Auth.selected_user.posting?.selected_service?.update_posts_for_active_destination()
+ }
}
}),
@@ -841,8 +1147,11 @@ export default App = types.model('App', {
trigger_logout_for_user: flow(function*(user) {
console.log("App:trigger_logout_for_user")
Alert.alert(`Please sign in again`, `Your token for, @${user.username}, is no longer valid.`)
+ Login.reset_apple_credentials()
yield Auth.logout_user(user)
- App.navigate_to_screen("Login")
+ if (Auth.is_logged_in()) {
+ App.navigate_to_screen("Login")
+ }
}),
set_is_loading_bookmarks: flow(function*(loading) {
@@ -920,11 +1229,23 @@ export default App = types.model('App', {
}))
.views(self => ({
+ should_follow_system_theme() {
+ return true
+ },
+ resolved_theme() {
+ return resolve_app_theme({
+ system_theme: Appearance.getColorScheme(),
+ })
+ },
is_dark_mode() {
return self.theme === "dark"
},
theme_accent_color() {
- return "#f80"
+ return resolve_app_accent_color({
+ platform_os: Platform.OS,
+ auto_android_theme: Settings.auto_android_theme,
+ system_accent_color: self.accent_color,
+ })
},
theme_warning_text_color() {
return "red"
@@ -933,6 +1254,9 @@ export default App = types.model('App', {
// return self.theme === "dark" ? "#1d2530" : "#fff"
return self.theme === "dark" ? "#1F2937" : "#fff"
},
+ theme_loading_banner_background_color() {
+ return self.theme === "dark" ? "#1b232f" : "#fafafa"
+ },
theme_navigation_icon_color() {
return self.theme === "dark" ? "#9CA3AF" : "#000"
},
@@ -1000,10 +1324,10 @@ export default App = types.model('App', {
return self.theme === "dark" ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.6)"
},
theme_chars_background_color() {
- return self.theme === "dark" ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.6)"
+ return self.theme === "dark" ? "rgba(28,32,40,0.8)" : "rgba(255,255,255,0.8)"
},
theme_settings_group_background_color() {
- return self.theme === "dark" ? "#1a232e" : "#e8eaed"
+ return self.theme === "dark" ? "#1a232e" : "#F3F4F6"
},
theme_autocomplete_background_color() {
return self.theme === "dark" ? "#1c2028" : "#f4f6f8"
@@ -1061,7 +1385,7 @@ export default App = types.model('App', {
},
should_reload_web_view() {
// When it returns true, this will trigger a reload of the webviews
- return self.is_switching_theme || self.is_changing_font_scale
+ return self.is_changing_font_scale
},
now() {
let now = new Date()
diff --git a/src/stores/Auth.js b/src/stores/Auth.js
index 8799f6e4..24534d46 100644
--- a/src/stores/Auth.js
+++ b/src/stores/Auth.js
@@ -7,6 +7,7 @@ import CookieManager from '@react-native-cookies/cookies';
import Push from './Push'
import App from './App'
import Toast from 'react-native-simple-toast';
+import { invalidate_webview_bootstrap_state } from '../utils/webview_recovery'
export default Auth = types.model('Auth', {
users: types.optional(types.array(User), []),
@@ -116,10 +117,8 @@ export default Auth = types.model('Auth', {
self.is_selecting_user = false
}
else{
- // As we have no users left, let's also delete all tokens.
Tokens.destroy_all_token_data()
App.close_sheet("main_sheet")
- App.navigate_to_screen("Login")
}
}),
@@ -141,6 +140,11 @@ export default Auth = types.model('Auth', {
self.did_load_one_or_more_webviews = false
}),
+ invalidate_webview_bootstrap: flow(function* () {
+ console.log("Auth:invalidate_webview_bootstrap")
+ invalidate_webview_bootstrap_state(self)
+ }),
+
set_did_load_one_or_more_webviews: flow(function* () {
console.log("Auth:set_did_load_one_or_more_webviews")
self.did_load_one_or_more_webviews = true
diff --git a/src/stores/Login.js b/src/stores/Login.js
index 5e4dc9eb..47a8c232 100644
--- a/src/stores/Login.js
+++ b/src/stores/Login.js
@@ -1,117 +1,392 @@
-import { flow, types, applySnapshot } from 'mobx-state-tree';
-import MicroBlogApi, { LOGIN_SUCCESS, LOGIN_ERROR, LOGIN_INCORRECT, LOGIN_TOKEN_INVALID } from './../api/MicroBlogApi';
-import StringChecker from './../utils/string_checker';
-import { Alert, Platform } from 'react-native';
-import Auth from './Auth';
-import App from './App';
-import { SheetManager } from "react-native-actions-sheet";
-
-export default Login = types.model('Login', {
+import { flow, types, applySnapshot } from 'mobx-state-tree'
+import * as WebBrowser from 'expo-web-browser'
+import MicroBlogApi, { LOGIN_SUCCESS, LOGIN_ERROR, LOGIN_INCORRECT, LOGIN_TOKEN_INVALID, APPLE_USERNAME_REQUIRED } from './../api/MicroBlogApi'
+import {
+ build_micro_blog_auth_url,
+ create_oauth_state,
+ exchange_micro_blog_code,
+ extract_micro_blog_callback_params,
+ extract_signin_token,
+ get_micro_blog_redirect_uri,
+ is_micro_blog_callback_url,
+ is_signin_token_url,
+} from './../api/MicroBlogAuth'
+import StringChecker from './../utils/string_checker'
+import { Alert, Platform } from 'react-native'
+import Auth from './Auth'
+import App from './App'
+
+const Login = types.model('Login', {
input_value: types.optional(types.string, ""),
is_loading: types.optional(types.boolean, false),
message: types.maybeNull(types.string),
show_error: types.optional(types.boolean, false),
error_message: types.maybeNull(types.string),
- did_trigger_login_from_url: types.optional(types.boolean, false)
+ did_trigger_login_from_url: types.optional(types.boolean, false),
+ pending_oauth_state: types.maybeNull(types.string),
+ apple_user_id: types.maybeNull(types.string),
+ apple_identity_token: types.maybeNull(types.string),
+ apple_email: types.maybeNull(types.string),
+ apple_full_name: types.maybeNull(types.string),
+ apple_username: types.optional(types.string, "")
})
.actions(self => ({
-
+
set_input_value: flow(function* (value) {
self.input_value = value
if(self.show_error){
self.reset_errors()
}
}),
-
+
+ set_apple_username: flow(function* (value) {
+ self.apple_username = value
+ if(self.show_error){
+ self.reset_errors()
+ }
+ }),
+
+ reset_apple_credentials() {
+ self.apple_user_id = null
+ self.apple_identity_token = null
+ self.apple_email = null
+ self.apple_full_name = null
+ self.apple_username = ""
+ },
+
+ clear_error() {
+ self.show_error = false
+ self.error_message = null
+ },
+
+ set_error(message = null) {
+ self.show_error = !!message
+ self.error_message = message
+ },
+
+ can_handle_open_url(raw_url = '') {
+ return is_micro_blog_callback_url(raw_url) || is_signin_token_url(raw_url)
+ },
+
+ handle_open_url: flow(function* (raw_url = '') {
+ if (is_signin_token_url(raw_url)) {
+ const token = extract_signin_token(raw_url)
+ self.pending_oauth_state = null
+ return yield self.login_with_token(true, token)
+ }
+
+ if (!is_micro_blog_callback_url(raw_url)) {
+ return false
+ }
+
+ if (!self.pending_oauth_state) {
+ return false
+ }
+
+ return yield self.complete_sign_in_callback(raw_url)
+ }),
+
trigger_login_from_url: flow(function* (url) {
- console.log("LOGIN:trigger_login_from_url", url)
- const token = url.split('/signin/')[1]
- if(token){
- console.log("LOGIN:trigger_login_from_url:token", token)
- self.did_trigger_login_from_url = true
- self.input_value = token
- self.trigger_login()
- App.close_sheet("login-message-sheet")
+ console.log("LOGIN:trigger_login_from_url")
+ self.did_trigger_login_from_url = true
+ const did_handle = yield self.handle_open_url(url)
+ App.close_sheet("login-message-sheet").catch(() => {})
+ return did_handle
+ }),
+
+ sign_in_with_micro_blog: flow(function* () {
+ if (self.is_loading) {
+ return false
+ }
+
+ self.clear_error()
+ self.is_loading = true
+ self.message = null
+
+ try {
+ const oauth_state = create_oauth_state()
+ if (!oauth_state) {
+ self.set_error('We could not prepare Micro.blog sign in. Please try again.')
+ return false
+ }
+
+ self.pending_oauth_state = oauth_state
+
+ const redirect_uri = get_micro_blog_redirect_uri()
+ const auth_url = build_micro_blog_auth_url({
+ redirect_uri,
+ state: oauth_state,
+ })
+ const auth_result = yield WebBrowser.openAuthSessionAsync(auth_url, redirect_uri)
+
+ if (auth_result?.type === 'success' && auth_result?.url) {
+ const did_handle_callback = yield self.handle_open_url(auth_result.url)
+
+ if (did_handle_callback) {
+ return true
+ }
+ }
+
+ if (!self.pending_oauth_state) {
+ return !self.show_error
+ }
+
+ self.pending_oauth_state = null
+
+ if (auth_result?.type === 'cancel' || auth_result?.type === 'dismiss') {
+ self.clear_error()
+ }
+ else if (!self.show_error) {
+ self.set_error('Micro.blog sign in did not complete. Please try again.')
+ }
+
+ return false
+ }
+ catch (error) {
+ console.log("LOGIN:sign_in_with_micro_blog:error", error)
+ self.pending_oauth_state = null
+ self.set_error('We could not open Micro.blog sign in. Please try again.')
+ return false
+ }
+ finally {
+ self.is_loading = false
+ }
+ }),
+
+ dismiss_open_auth_session() {
+ try {
+ WebBrowser.dismissAuthSession()
+ }
+ catch (error) {
+ console.log("LOGIN:dismiss_open_auth_session", error)
+ }
+ },
+
+ complete_sign_in_callback: flow(function* (raw_url = '') {
+ const { code, state } = extract_micro_blog_callback_params(raw_url)
+ const expected_state = self.pending_oauth_state
+
+ if (!code) {
+ self.set_error('Micro.blog did not return an authorization code. Please try again.')
+ return false
+ }
+
+ if (!state || !expected_state || state !== expected_state) {
+ self.set_error('Micro.blog sign in could not be verified. Please try again.')
+ return false
+ }
+
+ self.pending_oauth_state = null
+
+ try {
+ const token_payload = yield exchange_micro_blog_code({ code })
+ const access_token = `${token_payload?.access_token || ''}`.trim()
+
+ if (!access_token) {
+ self.set_error('Micro.blog did not return an access token. Please try again.')
+ self.dismiss_open_auth_session()
+ return false
+ }
+
+ const did_login = yield self.login_with_token(true, access_token)
+ self.dismiss_open_auth_session()
+ return did_login
+ }
+ catch (error) {
+ console.log("LOGIN:complete_sign_in_callback:error", error)
+ self.set_error('We could not finish signing you in. Please try again.')
+ self.dismiss_open_auth_session()
+ return false
}
}),
- trigger_login: flow(function* () {
- console.log("LOGIN:trigger_login", self)
+ submit_email_or_token: flow(function* (reset_navigation = false) {
+ if (self.is_loading || !self.can_submit_credentials()) {
+ return false
+ }
+
+ if (self.is_valid_email_address()) {
+ return yield self.login_with_email()
+ }
+
+ if (self.is_valid_token()) {
+ return yield self.login_with_token(reset_navigation, self.input_value)
+ }
+
+ self.set_error('Enter a valid email address or app token.')
+ return false
+ }),
+
+ login_with_email: flow(function* (email = self.input_value) {
+ const trimmed_email = `${email || ''}`.trim()
+ if (!StringChecker._validate_email(trimmed_email)) {
+ self.set_error('Enter a valid email address.')
+ return false
+ }
+
self.is_loading = true
self.message = null
-
- if(self.show_error){
- self.reset_errors()
+ self.clear_error()
+
+ try {
+ const login = yield MicroBlogApi.login_with_email(trimmed_email)
+ console.log("LOGIN:login_with_email:login", login)
+ if (login === LOGIN_SUCCESS) {
+ self.message = `Email sent! Check your email on this device and tap the "Open in Micro.blog for ${Platform.OS === 'ios' ? "iOS" : "Android"}" button.`
+ App.open_sheet("login-message-sheet")
+ return true
+ }
+ else if (login === LOGIN_INCORRECT) {
+ self.set_error("Your sign in details were incorrect. Please double check and try again.")
+ return false
+ }
+ else {
+ self.set_error("An error occured whilst trying to sign you in. Please try again.")
+ return false
+ }
+ }
+ catch (error) {
+ console.log("LOGIN:login_with_email:error", error)
+ self.set_error("An error occured whilst trying to sign you in. Please try again.")
+ return false
+ }
+ finally {
+ self.is_loading = false
}
-
- if(self.can_submit()){
- if(self.is_valid_email_address()){
- console.log("LOGIN:trigger_login:email_login")
- yield self.login_with_email()
+ }),
+
+ login_with_token: flow(function* (reset_navigation = false, token = self.input_value) {
+ const trimmed_token = `${token || ''}`.trim()
+ if (!trimmed_token) {
+ self.set_error('Enter a Micro.blog token to sign in.')
+ return false
+ }
+
+ self.is_loading = true
+ self.message = null
+ self.clear_error()
+
+ try {
+ const login = yield MicroBlogApi.login_with_token(trimmed_token)
+ console.log("LOGIN:login_with_token:login", login)
+ if(login !== LOGIN_ERROR && login !== LOGIN_TOKEN_INVALID){
+ console.log("LOGIN:login_with_token:SUCCESS")
+ yield self.finish_login_with_data(login, reset_navigation)
+ return true
}
- else if(self.is_valid_token()){
- console.log("LOGIN:trigger_login:token_login")
- yield self.login_with_token()
+ else if(login === LOGIN_TOKEN_INVALID){
+ self.set_error("That Micro.blog token is not valid. Please try again.")
+ return false
+ }
+ else{
+ self.set_error("We could not sign you in with that token. Please try again.")
+ return false
}
}
+ finally {
+ self.is_loading = false
+ }
+ }),
+
+ login_with_apple_credentials: flow(function* ({ user_id, identity_token, email = "", full_name = "" }) {
+ console.log("LOGIN:login_with_apple_credentials", user_id)
+ self.reset_apple_credentials()
+ self.is_loading = true
+ self.message = null
+ self.clear_error()
+
+ self.apple_user_id = user_id || null
+ self.apple_identity_token = identity_token || null
+ self.apple_email = email || ""
+ self.apple_full_name = full_name || ""
+
+ if(self.apple_user_id == null || self.apple_identity_token == null){
+ self.set_error("An error occured whilst trying to sign you in with Apple. Please try again.")
+ Alert.alert("Ooops", self.error_message)
+ self.is_loading = false
+ return false
+ }
+
+ const login = yield MicroBlogApi.login_with_apple({
+ user_id: self.apple_user_id,
+ identity_token: self.apple_identity_token,
+ email: self.apple_email,
+ full_name: self.apple_full_name
+ })
+ yield self.handle_apple_login_result(login)
self.is_loading = false
-
}),
-
- login_with_email: flow(function* () {
- const login = yield MicroBlogApi.login_with_email(self.input_value)
- console.log("LOGIN:trigger_login:email_login:login", login)
- if(login === LOGIN_SUCCESS){
- console.log("LOGIN:trigger_login:email_login:SUCCESS")
- self.message = `Email sent! Check your email on this device and tap the "Open in Micro.blog for ${Platform.OS === 'ios' ? "iOS" : "Android"}" button.`
- App.open_sheet("login-message-sheet")
+
+ register_apple_username: flow(function* () {
+ console.log("LOGIN:register_apple_username", self.apple_username)
+ if(self.is_loading || !self.can_submit_apple_username()){
+ return false
+ }
+
+ self.is_loading = true
+ self.message = null
+ self.clear_error()
+
+ const login = yield MicroBlogApi.login_with_apple({
+ user_id: self.apple_user_id,
+ identity_token: self.apple_identity_token,
+ username: self.apple_username
+ })
+ yield self.handle_apple_login_result(login, true)
+ self.is_loading = false
+ }),
+
+ handle_apple_login_result: flow(function* (login, reset_navigation = false) {
+ console.log("LOGIN:handle_apple_login_result", login)
+ if(login?.error != null){
+ self.set_error(login.error)
+ Alert.alert("Unable to sign in with Apple", self.error_message)
+ }
+ else if(login === APPLE_USERNAME_REQUIRED){
+ App.navigate_to_screen("AppleUsername")
+ }
+ else if(login !== LOGIN_ERROR && login !== LOGIN_INCORRECT){
+ yield self.finish_login_with_data(login, reset_navigation)
}
else if(login === LOGIN_INCORRECT){
- self.show_error = true
- self.error_message = "Your sign in details were incorrect. Please double check and try again."
+ self.set_error("Your sign in details were incorrect. Please double check and try again.")
Alert.alert("Wrong details", self.error_message)
}
else{
- self.show_error = true
- self.error_message = "An error occured whilst trying to sign you in. Please try again."
+ self.set_error("An error occured whilst trying to sign you in with Apple. Please try again.")
Alert.alert("Ooops", self.error_message)
}
}),
-
- login_with_token: flow(function* () {
- const login = yield MicroBlogApi.login_with_token(self.input_value)
- console.log("LOGIN:trigger_login:login_with_token:login", login)
- if(login !== LOGIN_ERROR && login !== LOGIN_TOKEN_INVALID){
- console.log("LOGIN:trigger_login:login_with_token:login:SUCCESS")
- const result = yield Auth.handle_new_login(login)
- if(result){
- // THIS IS ALWAYS TRUE FOR NOW 😇
- App.close_sheet("main_sheet")
- App.navigation().goBack()
- self.reset()
- }
- else{
- self.show_error = true
- self.error_message = "An error occured whilst trying to sign you in. Please try again."
- Alert.alert("Ooops", self.error_message)
+
+ finish_login_with_data: flow(function* (login, reset_navigation = false) {
+ const was_already_logged_in = Auth.is_logged_in()
+ const result = yield Auth.handle_new_login(login)
+ if(result){
+ yield App.bump_web_view_epoch()
+ App.close_sheet("main_sheet").catch(() => {})
+ if (was_already_logged_in) {
+ const navigation = App.navigation()
+ if(reset_navigation){
+ yield App.reset_to_tabs()
+ }
+ else if(navigation?.canGoBack?.()){
+ navigation.goBack()
+ }
+ else{
+ navigation?.navigate?.("Tabs")
+ }
}
- }
- else if(login === LOGIN_TOKEN_INVALID){
- self.show_error = true
- self.error_message = "Your sign in details were incorrect. Please double check and try again."
- Alert.alert("Invalid token", self.error_message)
+ self.reset()
}
else{
- self.show_error = true
- self.error_message = "An error occured whilst trying to sign you in. Please try again."
+ self.set_error("An error occured whilst trying to sign you in. Please try again.")
Alert.alert("Ooops", self.error_message)
}
}),
reset_errors: flow(function* () {
console.log("LOGIN:reset_errors")
- self.show_error = false
- self.error_message = null
+ self.clear_error()
}),
reset: flow(function* () {
@@ -121,22 +396,50 @@ export default Login = types.model('Login', {
}))
.views(self => ({
-
+
is_valid_email_address(){
- return StringChecker._validate_email(self.input_value)
+ return StringChecker._validate_email(`${self.input_value || ''}`.trim())
},
-
+
is_valid_token(){
- return StringChecker._validate_is_token(self.input_value)
+ return StringChecker._validate_is_token(`${self.input_value || ''}`.trim())
},
-
- credentials_seem_valid(){
- return this.is_valid_token() || this.is_valid_email_address()
+
+ can_submit_credentials(){
+ return !self.is_loading && (this.is_valid_email_address() || this.is_valid_token())
},
-
- can_submit(){
- return this.credentials_seem_valid() && self.input_value !== "" && self.input_value.length > 0
+
+ submit_button_label(){
+ if (self.is_loading) {
+ if (this.is_valid_email_address()) {
+ return 'Sending email...'
+ }
+ if (this.is_valid_token()) {
+ return 'Checking token...'
+ }
+ return 'Signing in...'
+ }
+
+ if (this.is_valid_token()) {
+ return 'Sign in with token'
+ }
+
+ if (this.is_valid_email_address()) {
+ return 'Continue'
+ }
+
+ return 'Continue'
+ },
+
+ can_submit_apple_username(){
+ return !self.is_loading &&
+ self.apple_user_id != null &&
+ self.apple_identity_token != null &&
+ self.apple_username != null &&
+ self.apple_username.length > 0
}
}))
-.create();
\ No newline at end of file
+.create();
+
+export default Login
diff --git a/src/stores/Push.js b/src/stores/Push.js
index b2ad6b8a..3896aa2a 100644
--- a/src/stores/Push.js
+++ b/src/stores/Push.js
@@ -1,48 +1,46 @@
-import { types, flow, destroy } from 'mobx-state-tree';
+import { types, flow, destroy } from 'mobx-state-tree'
import MicroBlogApi, { API_ERROR } from '../api/MicroBlogApi'
-import PushNotification from "react-native-push-notification";
+import PushNotification from "react-native-push-notification"
import Notification from './models/Notification'
import Auth from './Auth'
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
+import PushNotificationIOS from "@react-native-community/push-notification-ios"
import { Platform, PermissionsAndroid } from 'react-native'
import App from './App'
import Device from './models/Device'
+import {
+ normalise_notification_payload,
+ determine_notification_action,
+ determine_pending_notification_action
+} from '../utils/push_notifications'
-export default Push = types.model('Push', {
+function check_push_permissions() {
+ if (Platform.OS === 'ios') {
+ return new Promise((resolve) => {
+ PushNotificationIOS.checkPermissions((permissions) => {
+ resolve(!!permissions?.alert)
+ })
+ })
+ }
+
+ return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS)
+ .then((hasPermission) => hasPermission)
+ .catch(() => false)
+}
+
+const Push = types.model('Push', {
token: types.optional(types.string, ""),
notifications: types.optional(types.array(Notification), []),
notificiations_open: types.optional(types.boolean, false),
- devices: types.optional(types.map(types.array(Device)), {})
+ devices: types.optional(types.map(types.array(Device)), {}),
+ pending_notification: types.maybeNull(types.frozen()),
+ auth_ready: types.optional(types.boolean, false)
})
.actions(self => ({
- hydrate: flow(function* () {
+ hydrate: flow(function* () {
console.log("Push:hydrate")
-
- PushNotification.configure({
- onRegister: function (data) {
- console.log("Push:hydrate:onRegister:data", data);
- Push.set_token(data.token)
- },
- onRegistrationError: function (error) {
- console.log("Push:hydrate:onRegistrationError:error", error);
- },
- onNotification: function(data) {
- Push.handle_notification(data)
- },
- onAction: function(data){
- console.log("Push:onAction:data", data);
- },
- permissions: {
- alert: true,
- badge: true,
- sound: true
- },
- popInitialNotification: true,
- requestPermissions: true
- })
}),
-
+
set_token: flow(function* (token) {
console.log("Push::set_token", token)
self.token = token
@@ -58,8 +56,8 @@ export default Push = types.model('Push', {
return true
}
}
- else if(self.token == "" && Auth.is_logged_in() && self.has_push_permissions){
- Push.request_permissions()
+ else if(self.token == "" && Auth.is_logged_in() && (yield self.has_push_permissions())){
+ Push.request_permissions()
}
return false
}),
@@ -74,7 +72,7 @@ export default Push = types.model('Push', {
}
}
else if(self.token == ""){
- return true
+ return true
}
return false
}),
@@ -114,25 +112,123 @@ export default Push = types.model('Push', {
}
}),
- handle_notification: flow(function* (notification) {
- console.log("Push::handle_notification", notification, Auth.selected_user)
- const nice_notification_object = {
- id: Platform.OS === 'ios' ? notification.data?.notificationId != null ? notification.data?.notificationId : notification.data?.post_id : notification.id,
- message: notification.message,
- post_id: notification.data?.post_id,
- to_username: Platform.OS === 'ios' ? notification.data?.to_user?.username : JSON.parse(notification.data?.to_user)?.username,
- from_username: Platform.OS === 'ios' ? notification.data?.from_user?.username : JSON.parse(notification.data?.from_user)?.username,
- should_open: Platform.OS === 'ios' && notification.foreground === false,
- did_load_before_user_was_loaded: Auth.selected_user == null
- }
- // Check if we have an existing notification in our array.
- // This will never happen, except for DEV for sending through test messages.
- const existing_notification = self.notifications.find(notification => notification.id === nice_notification_object.id)
+ set_pending_notification: flow(function* (notification) {
+ console.log("Push:set_pending_notification", notification)
+ self.pending_notification = notification
+ }),
+
+ clear_pending_notification: flow(function* () {
+ console.log("Push:clear_pending_notification")
+ self.pending_notification = null
+ }),
+
+ set_auth_ready: flow(function* (ready = true) {
+ console.log("Push:set_auth_ready", ready)
+ self.auth_ready = ready
+ }),
+
+ store_notification: flow(function* (notification) {
+ console.log("Push:store_notification", notification)
+ const existing_notification = self.notifications.find(item => item.id === notification.id)
if (existing_notification) {
destroy(existing_notification)
}
- self.notifications.push(Notification.create(nice_notification_object))
- Push.open_notification_sheet()
+ self.notifications.push(Notification.create(notification))
+ }),
+
+ open_notification: flow(function* (notification) {
+ console.log("Push:open_notification", notification)
+ const local_user = self.local_user_for_notification(notification)
+ if (local_user != null && notification?.post_id != null) {
+ if (Auth.selected_user !== local_user) {
+ yield Auth.select_user(local_user)
+ }
+ App.navigate_to_screen("open", notification.post_id)
+ Push.close_notification_sheet()
+ return true
+ }
+ return false
+ }),
+
+ schedule_pending_notification_replay: flow(function* () {
+ console.log("Push:schedule_pending_notification_replay")
+ ;[0, 250, 500, 1000, 1500, 2500, 4000, 6000].forEach((delay) => {
+ setTimeout(() => {
+ Push.replay_pending_notification()
+ }, delay)
+ })
+ }),
+
+ handle_notification: flow(function* (notification) {
+ console.log("Push::handle_notification", notification, Auth.selected_user)
+ const nice_notification_object = normalise_notification_payload(notification, Platform.OS)
+ const action = determine_notification_action({
+ notification: nice_notification_object,
+ has_navigation: self.has_navigation(),
+ has_local_user: self.local_user_for_notification(nice_notification_object) != null,
+ is_app_active: self.is_app_active()
+ })
+
+ if (action === 'open') {
+ return yield Push.open_notification(nice_notification_object)
+ }
+
+ if (action === 'queue') {
+ yield Push.set_pending_notification(nice_notification_object)
+ yield Push.schedule_pending_notification_replay()
+ return false
+ }
+
+ yield Push.store_notification({
+ ...nice_notification_object,
+ should_open: false
+ })
+ yield Push.open_notification_sheet()
+ return false
+ }),
+
+ replay_pending_notification: flow(function* () {
+ console.log("Push:replay_pending_notification", self.pending_notification)
+ if (self.pending_notification == null) {
+ return false
+ }
+
+ if (!self.auth_ready) {
+ console.log("Push:replay_pending_notification:waiting_for_auth")
+ return false
+ }
+
+ const pending_notification = self.pending_notification
+ const action = determine_pending_notification_action({
+ notification: pending_notification,
+ has_navigation: self.has_navigation(),
+ has_local_user: self.local_user_for_notification(pending_notification) != null,
+ is_app_active: self.is_app_active()
+ })
+ console.log("Push:replay_pending_notification:action", action, {
+ has_navigation: self.has_navigation(),
+ has_local_user: self.local_user_for_notification(pending_notification) != null,
+ is_app_active: self.is_app_active(),
+ navigation_ready: App.navigation_ready,
+ app_state: App.app_state
+ })
+
+ if (action === 'wait') {
+ return false
+ }
+
+ yield Push.clear_pending_notification()
+
+ if (action === 'open') {
+ return yield Push.open_notification(pending_notification)
+ }
+
+ yield Push.store_notification({
+ ...pending_notification,
+ should_open: false
+ })
+ yield Push.open_notification_sheet()
+ return false
}),
open_notification_sheet: flow(function* () {
@@ -163,9 +259,10 @@ export default Push = types.model('Push', {
yield PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS)
}
- if(self.has_push_permissions() && user_token){
- return yield self.register_token(user_token)
+ if(user_token && (yield self.has_push_permissions())){
+ return yield self.register_token(user_token)
}
+ return false
}),
set_devices_for_user: flow(function* (username, devices) {
@@ -181,28 +278,41 @@ export default Push = types.model('Push', {
},
handle_first_notification() {
- return self.notifications.find(n => n.did_load_before_user_was_loaded)?.handle_action()
+ return self.pending_notification != null ? Push.replay_pending_notification() : null
},
has_push_permissions(callback) {
- if (Platform.OS === 'ios') {
- PushNotificationIOS.checkPermissions((permissions) => {
- callback(permissions.alert)
+ const permissions_promise = check_push_permissions()
+ if (typeof callback === 'function') {
+ permissions_promise.then((has_permissions) => {
+ callback(has_permissions)
})
- } else {
- PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS)
- .then((hasPermission) => {
- callback(hasPermission)
- })
- .catch(() => {
- callback(false)
- })
}
+ return permissions_promise
},
is_registered_device_for_user(username) {
return self.devices.get(username)?.some(device => device.token === Push.token)
+ },
+
+ local_user_for_notification(notification) {
+ if (notification?.to_username == null) {
+ return null
+ }
+ return Auth.users.find(u => u.username === notification.to_username)
+ },
+
+ has_navigation() {
+ return App.navigation_ref != null
+ && App.navigation_ready
+ && (typeof App.navigation_ref.isReady !== 'function' || App.navigation_ref.isReady())
+ },
+
+ is_app_active() {
+ return App.app_state === 'active'
}
}))
.create({})
+
+export default Push
diff --git a/src/stores/Reply.js b/src/stores/Reply.js
index 63251126..912101d0 100644
--- a/src/stores/Reply.js
+++ b/src/stores/Reply.js
@@ -104,6 +104,7 @@ export default Reply = types.model('Reply', {
if (data !== POST_ERROR || data === DUPLICATE_REPLY) {
self.reply_text = ""
self.is_sending_reply = false
+ App.reload_current_web_view()
return true
}
else {
diff --git a/src/stores/Settings.js b/src/stores/Settings.js
index 02d59afa..d6f3a073 100644
--- a/src/stores/Settings.js
+++ b/src/stores/Settings.js
@@ -3,7 +3,8 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
export default Settings = types.model('Settings', {
open_links_in_external_browser: types.optional(types.boolean, false),
- open_links_with_reader_mode: types.optional(types.boolean, false)
+ open_links_with_reader_mode: types.optional(types.boolean, false),
+ auto_android_theme: types.optional(types.boolean, true)
})
.actions(self => ({
@@ -24,7 +25,12 @@ export default Settings = types.model('Settings', {
toggle_open_links_with_reader_mode: flow(function* () {
console.log("Settings:open_links_with_reader_mode")
self.open_links_with_reader_mode = !self.open_links_with_reader_mode
+ }),
+
+ set_auto_android_theme: flow(function* (enabled = true) {
+ console.log("Settings:set_auto_android_theme", enabled)
+ self.auto_android_theme = enabled
})
}))
-.create();
\ No newline at end of file
+.create();
diff --git a/src/stores/Share.js b/src/stores/Share.js
index 8d6d66e3..a51fe1a8 100644
--- a/src/stores/Share.js
+++ b/src/stores/Share.js
@@ -5,11 +5,12 @@ import MicroBlogApi, { LOGIN_ERROR, LOGIN_TOKEN_INVALID, LOGIN_INCORRECT } from
import Tokens from "./Tokens";
import User from './models/User';
import string_checker from '../utils/string_checker'
+import { share_type_for_mime_type } from '../utils/share_intents'
import { Platform, Keyboard } from 'react-native'
import App from "./App"
import Auth from './Auth';
-export default Share = types.model('Share', {
+const Share = types.model('Share', {
is_loading: types.optional(types.boolean, true),
share_type: types.optional(types.string, "text"),
share_data: types.optional(types.string, ""),
@@ -31,14 +32,15 @@ export default Share = types.model('Share', {
toolbar_select_user_open: types.optional(types.boolean, false),
error_message: types.maybeNull(types.string),
image_options_open: types.optional(types.boolean, false),
+ image_options_asset_uri: types.maybeNull(types.string),
temp_direct_shared_data: types.optional(types.string, ""),
})
.actions(self => ({
- hydrate: flow(function* (shared_data = null) {
+ hydrate: flow(function* (shared_data = null, color_scheme = null) {
console.log('Share:hydrate', self)
yield self.trigger_loading()
- yield App.prep_and_hydrate_share_extension()
+ yield App.prep_and_hydrate_share_extension(color_scheme)
const store = yield AsyncStorage.getItem('Share')
if (store) {
applySnapshot(self, JSON.parse(store))
@@ -46,9 +48,11 @@ export default Share = types.model('Share', {
self.share_type = "text"
self.share_data = ""
self.share_text = ""
+ self.share_url = ""
self.share_image_data = null
self.error_message = null
self.image_options_open = false
+ self.image_options_asset_uri = null
self.temp_direct_shared_data = ""
}
if (Platform.OS === "ios") {
@@ -67,7 +71,7 @@ export default Share = types.model('Share', {
yield self.set_data(shared_data)
self.trigger_loading(false)
}),
-
+
hydrate_android_share: flow(function* (shared_data) {
console.log('Share:hydrate_android_share', shared_data)
Share.hydrate(shared_data)
@@ -96,7 +100,7 @@ export default Share = types.model('Share', {
// }
if (direct_data != null) {
console.log('Share:set_data:direct_data', direct_data)
- if (direct_data.data.includes("#:~:text=")) {
+ if (typeof direct_data.data === "string" && direct_data.data.includes("#:~:text=")) {
// Messy...
mime_type = "application/json"
data = JSON.stringify({
@@ -106,27 +110,34 @@ export default Share = types.model('Share', {
})
}
else {
- data = direct_data.data.split("#:~:text=")[0]
+ data = typeof direct_data.data === "string" ? direct_data.data.split("#:~:text=")[0] : direct_data.data
mime_type = direct_data.mimeType
}
}
else {
const share_data = yield ShareMenuReactView.data()
console.log('Share:set_data', share_data)
- data_array = share_data.data
+ const data_array = share_data.data
data = data_array[ 0 ].data
mime_type = data_array[ 0 ].mimeType
}
- self.share_data = data
- console.log('Share:set_data:data', data, mime_type)
- if (mime_type === "image/jpeg" || mime_type === "image/png") {
- self.share_type = "image"
- }
- else if(mime_type === "application/json"){
- self.share_type = "json"
+ const normalized_data = typeof data === "string" ? data : ""
+ self.share_data = normalized_data
+ console.log('Share:set_data:data', normalized_data, mime_type)
+ const share_type = share_type_for_mime_type(mime_type)
+ if (share_type == null) {
+ self.share_type = "unsupported"
+ self.share_text = ""
+ self.share_url = ""
+ self.share_image_data = null
+ self.error_message = "Micro.blog can only share text, links, and photos."
+ self.is_loading = false
+ return
}
+
+ self.share_type = share_type
if (self.share_type === "text") {
- self.share_text = string_checker._validate_url(data) ? data : `> ${ data }`
+ self.share_text = string_checker._validate_url(normalized_data) ? normalized_data : `> ${ normalized_data }`
if (Platform.OS === "ios") {
self.users.forEach(user => {
user.posting.set_post_text(self.share_text)
@@ -140,7 +151,7 @@ export default Share = types.model('Share', {
}
else if (self.share_type === "image") {
const image_data = {
- uri: data,
+ uri: normalized_data,
type: mime_type,
mime: mime_type
}
@@ -158,13 +169,13 @@ export default Share = types.model('Share', {
}
else if (self.share_type === "json"){
// Because we're dealing with JSON, we need to check a few things and add the correct share_text
- const parsed_data = JSON.parse(data)
-
+ const parsed_data = JSON.parse(normalized_data)
+
let share_text = ""
if (parsed_data.title && parsed_data.url) {
share_text += `[${parsed_data.title}](${parsed_data.url})`
self.share_url = parsed_data.url
- }
+ }
else if (parsed_data.url) {
share_text += `[](${parsed_data.url})`
self.share_url = parsed_data.url
@@ -172,7 +183,7 @@ export default Share = types.model('Share', {
if (parsed_data.text) {
share_text += `\n\n> ${parsed_data.text}\n\n`
}
-
+
self.share_text = share_text
if (Platform.OS === "ios") {
self.users.forEach(user => {
@@ -190,7 +201,7 @@ export default Share = types.model('Share', {
self.is_loading = false
}
}),
-
+
login_account: flow(function* (account_with_token = null) {
console.log("Share:login_account")
if (account_with_token) {
@@ -229,7 +240,7 @@ export default Share = types.model('Share', {
Auth.selected_user?.fetch_data()
})
}
-
+
if (self.toolbar_select_user_open) {
self.toolbar_select_user_open = false
}
@@ -332,19 +343,21 @@ export default Share = types.model('Share', {
clear_error_message: flow(function* () {
self.error_message = null
}),
-
+
trigger_image_options: flow(function* (asset) {
console.log('Share:trigger_image_options', asset)
if (asset.is_uploading) {
console.log('Share:trigger_image_options:blocked - upload in progress')
return false
}
+ self.image_options_asset_uri = asset.uri
self.image_options_open = true
}),
-
+
close_image_options: flow(function* () {
console.log('Share:close_image_options')
self.image_options_open = false
+ self.image_options_asset_uri = null
}),
}))
@@ -363,8 +376,8 @@ export default Share = types.model('Share', {
sorted_users() {
if (Platform.OS === "ios") {
return self.users.slice().sort((a, b) => {
- const a_is_selected = Share.selected_user?.username === a.username
- const b_is_selected = Share.selected_user?.username === b.username
+ const a_is_selected = self.selected_user?.username === a.username
+ const b_is_selected = self.selected_user?.username === b.username
return b_is_selected - a_is_selected
})
}
@@ -375,6 +388,18 @@ export default Share = types.model('Share', {
return b_is_selected - a_is_selected
})
}
+ },
+ image_options_asset() {
+ const assets = self.selected_user?.posting?.post_assets
+ if (!assets?.length) {
+ return null
+ }
+ if (self.image_options_asset_uri) {
+ return assets.find(asset => asset.uri === self.image_options_asset_uri) || assets[0]
+ }
+ return assets[0]
}
}))
- .create()
\ No newline at end of file
+ .create()
+
+export default Share
diff --git a/src/stores/Tokens.js b/src/stores/Tokens.js
index 4a1a3fc7..5afaf385 100644
--- a/src/stores/Tokens.js
+++ b/src/stores/Tokens.js
@@ -1,6 +1,5 @@
import { types, flow, applySnapshot, destroy } from 'mobx-state-tree';
import Token from './models/Token'
-import SFInfo from 'react-native-sensitive-info'
import * as SecureStore from 'expo-secure-store';
export default Tokens = types.model('Tokens', {
@@ -10,7 +9,7 @@ export default Tokens = types.model('Tokens', {
hydrate: flow(function* (return_data = false) {
console.log("Tokens:hydrate")
- const data = yield SFInfo.getItem('Tokens', {}) // TODO: Replace with when the time comes: yield SecureStore.getItemAsync('Tokens')
+ const data = yield SecureStore.getItemAsync('Tokens')
if (data) {
applySnapshot(self, JSON.parse(data))
console.log("Tokens:hydrate:with_data")
@@ -22,7 +21,6 @@ export default Tokens = types.model('Tokens', {
destroy_all_token_data: flow(function* () {
console.log("Tokens:destroy_all_token_data")
- yield SFInfo.deleteItem("Tokens", {})
yield SecureStore.deleteItemAsync("Tokens")
}),
diff --git a/src/stores/models/Muting.js b/src/stores/models/Muting.js
index 392504eb..5ec64432 100644
--- a/src/stores/models/Muting.js
+++ b/src/stores/models/Muting.js
@@ -11,10 +11,11 @@ const MutedItem = types.model("MutedItem", {
is_hiding_other_replies: types.optional(types.boolean, false)
})
-export default Muting = types.model('Muting', {
+const Muting = types.model('Muting', {
username: types.identifier,
is_sending_mute: types.optional(types.boolean, false),
is_sending_unmute: types.optional(types.boolean, false),
+ is_loading: types.optional(types.boolean, false),
muted_items: types.optional(types.array(MutedItem), [])
})
.actions(self => ({
@@ -25,13 +26,15 @@ export default Muting = types.model('Muting', {
hydrate: flow(function* () {
console.log("Muting:hydrate")
- self.muted_items = [];
- const muted_items = yield MicroBlogApi.get_muted_users(self.token());
+ self.is_loading = true
+ self.muted_items = []
+ const muted_items = yield MicroBlogApi.get_muted_users(self.token())
if (muted_items && muted_items !== API_ERROR) {
- self.muted_items = muted_items;
+ self.muted_items = muted_items
}
- self.is_sending_mute = false;
- self.is_sending_unmute = false;
+ self.is_loading = false
+ self.is_sending_mute = false
+ self.is_sending_unmute = false
}),
mute_user: flow(function* (username, should_block = false) {
@@ -111,3 +114,5 @@ export default Muting = types.model('Muting', {
}
}))
+
+export default Muting
diff --git a/src/stores/models/Notification.js b/src/stores/models/Notification.js
index a6633451..d2bbd6fb 100644
--- a/src/stores/models/Notification.js
+++ b/src/stores/models/Notification.js
@@ -28,12 +28,7 @@ export default Notification = types.model('Notification', {
}),
afterCreate: flow(function* () {
- if (self.should_open != null && self.should_open) {
- self.handle_action()
- }
- else{
- self.hydrate()
- }
+ self.hydrate()
}),
handle_action: flow(function* () {
@@ -59,10 +54,13 @@ export default Notification = types.model('Notification', {
},
can_show_notification() {
- return self.message && this.local_user() != null && !self.should_open
+ return self.message && !self.should_open
},
trimmed_message() {
+ if (!self.message) {
+ return null
+ }
if (self.message.length > 255) {
return `${self.message.slice(0, 250)}...`
}
diff --git a/src/stores/models/Posting.js b/src/stores/models/Posting.js
index e4f31247..84abaa76 100644
--- a/src/stores/models/Posting.js
+++ b/src/stores/models/Posting.js
@@ -1,4 +1,4 @@
-import { types, flow, destroy } from 'mobx-state-tree';
+import { types, flow, destroy, isAlive } from 'mobx-state-tree';
import Service from './posting/Service';
import { blog_services } from './../enums/blog_services';
import { Alert, Platform, Linking, NativeModules } from 'react-native';
@@ -14,6 +14,8 @@ import md from 'markdown-it';
const parser = md({ html: true });
const { MBClipboardHelper } = NativeModules;
+const should_show_publishing_progress = post_status => post_status != "draft"
+
export default Posting = types.model('Posting', {
username: types.identifier,
services: types.optional(types.array(Service), []),
@@ -27,6 +29,7 @@ export default Posting = types.model('Posting', {
post_syndicates: types.optional(types.array(types.string), []),
post_status: types.optional(types.string, "published"),
is_adding_bookmark: types.optional(types.boolean, false),
+ is_closing_after_post: types.optional(types.boolean, false),
text_selection: types.optional(
types.model('Selection', {
start: types.optional(types.number, 0),
@@ -68,6 +71,7 @@ export default Posting = types.model('Posting', {
}
self.post_assets = []
self.is_sending_post = false
+ self.is_closing_after_post = false
self.is_adding_bookmark = false
self.is_editing_post = false
self.text_selection_flat = ""
@@ -111,6 +115,7 @@ export default Posting = types.model('Posting', {
hydrate_post_with_markdown: flow(function* (markdown) {
console.log("hydrate_post_with_markdown", markdown)
+ self.is_closing_after_post = false
self.post_text = markdown
}),
@@ -119,15 +124,18 @@ export default Posting = types.model('Posting', {
}),
set_post_text: flow(function* (value) {
+ self.is_closing_after_post = false
self.post_text = value
}),
set_post_text_from_typing: flow(function* (value) {
+ self.is_closing_after_post = false
self.post_text = value
App.check_usernames(self.post_text)
}),
set_post_text_from_action: flow(function* (value) {
+ self.is_closing_after_post = false
self.post_text = decodeURI(value.replace("microblog://post?text=", "").replace(/%3A/g, ":"))
}),
@@ -181,7 +189,7 @@ export default Posting = types.model('Posting', {
return false
}
self.is_sending_post = true
- const was_draft = (self.post_status == "draft");
+ const should_show_progress = should_show_publishing_progress(self.post_status)
const post_success = self.selected_service.type === "xmlrpc" ?
yield XMLRPCApi.send_post(self.selected_service.service_object(), self.post_text, self.post_title, self.post_assets, self.post_categories, self.post_status)
: yield MicroPubApi.send_post(self.selected_service.service_object(), self.post_text, self.post_title, self.post_assets, self.post_categories, self.post_status, self.post_syndicates.length === self.selected_service.active_destination()?.syndicates?.length ? null : self.post_syndicates, self.summary)
@@ -201,7 +209,8 @@ export default Posting = types.model('Posting', {
self.post_syndicates = syndicate_targets
}
self.is_sending_post = false
- if (!was_draft) {
+ self.is_closing_after_post = true
+ if (should_show_progress) {
App.show_publishing_progress();
}
return true
@@ -286,6 +295,7 @@ export default Posting = types.model('Posting', {
return
}
if (result.assets) {
+ self.is_closing_after_post = false
result.assets.forEach((asset) => {
let is_video = self.is_asset_video(asset.type)
console.log("Posting:handle_asset_action:asset", asset, is_video)
@@ -320,6 +330,9 @@ export default Posting = types.model('Posting', {
upload_video_asset: flow(function* (media_asset) {
try {
+ if (!isAlive(media_asset)) {
+ return false
+ }
console.log("Posting:upload_video_asset:start", media_asset?.uri)
const service_object = self.selected_service?.service_object()
if (!service_object) {
@@ -330,14 +343,33 @@ export default Posting = types.model('Posting', {
media_asset.progress = 0
media_asset.cancelled = false
media_asset.cancel_source = create_cancel_source()
+ const upload_media = {
+ uri: media_asset.uri,
+ type: media_asset.type,
+ original_file_name: media_asset.original_file_name,
+ fileSize: media_asset.fileSize,
+ file_size: media_asset.file_size,
+ cached_uri: media_asset.cached_uri
+ }
const result = yield upload_large_media_task({
- media: media_asset,
+ media: upload_media,
service_object,
cancel_source: media_asset.cancel_source,
- is_cancelled: () => media_asset.cancelled,
- on_progress: progress => media_asset.update_progress(progress),
- on_local_uri: uri => media_asset.set_cached_uri(uri)
+ is_cancelled: () => !isAlive(media_asset) || media_asset.cancelled,
+ on_progress: progress => {
+ if (isAlive(media_asset)) {
+ media_asset.update_progress(progress)
+ }
+ },
+ on_local_uri: uri => {
+ if (isAlive(media_asset)) {
+ media_asset.set_cached_uri(uri)
+ }
+ }
})
+ if (!isAlive(media_asset)) {
+ return false
+ }
media_asset.remote_url = result.url
if (result.poster) {
media_asset.remote_poster_url = result.poster
@@ -345,24 +377,29 @@ export default Posting = types.model('Posting', {
media_asset.did_upload = true
}
catch (error) {
- const was_cancelled = media_asset.cancelled
+ const was_cancelled = !isAlive(media_asset) || media_asset.cancelled
console.log("Posting:upload_video_asset:error", error)
if (!was_cancelled) {
Alert.alert("Upload Failed", error?.message || "Could not upload video.")
}
- media_asset.did_upload = false
- if (!was_cancelled) {
- media_asset.progress = 0
+ if (isAlive(media_asset)) {
+ media_asset.did_upload = false
+ if (!was_cancelled) {
+ media_asset.progress = 0
+ }
}
}
finally {
- media_asset.is_uploading = false
- media_asset.cancel_source = null
+ if (isAlive(media_asset)) {
+ media_asset.is_uploading = false
+ media_asset.cancel_source = null
+ }
}
}),
create_and_attach_asset: flow(function* (asset) {
console.log("Posting:create_and_attach_asset", asset)
+ self.is_closing_after_post = false
const existing_asset = self.post_assets.find(file => file.uri === asset.uri)
if(existing_asset == null){
const media_asset = MediaAsset.create(asset)
@@ -371,6 +408,7 @@ export default Posting = types.model('Posting', {
}),
attach_asset: flow(function* (asset) {
+ self.is_closing_after_post = false
self.post_assets.push(asset)
// Always upload immediately for better user feedback
asset.upload(self.selected_service.service_object())
@@ -406,7 +444,7 @@ export default Posting = types.model('Posting', {
console.log("Posting:asset_option_screen:blocked - upload in progress")
return false
}
- return App.navigate_to_screen("ImageOptions", { asset: asset, index: index })
+ return App.navigate_to_screen("ImageOptions", { asset_uri: asset.uri, index: index })
}),
remove_asset: flow(function* (media_index) {
@@ -471,11 +509,14 @@ export default Posting = types.model('Posting', {
return false
}
self.is_sending_post = true
+ const should_show_progress = should_show_publishing_progress(self.post_status)
const post_success = yield MicroPubApi.post_update(self.selected_service.service_object(), self.post_text, self.post_url, self.post_title, self.post_categories, self.post_status)
self.is_sending_post = false
if(post_success !== POST_ERROR){
self.clear_post()
- App.show_publishing_progress()
+ if (should_show_progress) {
+ App.show_publishing_progress()
+ }
return true
}
return false
@@ -488,6 +529,8 @@ export default Posting = types.model('Posting', {
self.post_categories = []
self.summary = null
self.new_category_text = ""
+ self.is_sending_post = false
+ self.is_closing_after_post = false
self.is_editing_post = false
self.post_url = null
self.show_title = false
@@ -657,13 +700,7 @@ export default Posting = types.model('Posting', {
},
post_chars_offset(is_post_edit) {
- var offset = 0
- if (is_post_edit) {
- offset = -55
- }
- else {
- offset = -35
- }
+ var offset = -35
if (App.found_users.length > 0) {
// if usernames bar, move chars counter higher
diff --git a/src/stores/models/User.js b/src/stores/models/User.js
index 72a310b0..e0dbe3e0 100644
--- a/src/stores/models/User.js
+++ b/src/stores/models/User.js
@@ -245,7 +245,7 @@ export default User = types
);
if (data !== API_ERROR) {
App.set_is_loading_bookmarks(true);
- App.close_sheet("add_tags_sheet");
+ yield App.close_sheet("add_tags_sheet");
}
self.fetch_recent_tags();
setTimeout(() => {
diff --git a/src/stores/models/posting/Destination.js b/src/stores/models/posting/Destination.js
index 466fe4a3..40612a05 100644
--- a/src/stores/models/posting/Destination.js
+++ b/src/stores/models/posting/Destination.js
@@ -1,4 +1,4 @@
-import { types, flow } from "mobx-state-tree"
+import { types, flow, isAlive } from "mobx-state-tree"
import { Alert } from "react-native"
import Post from "./Post"
import Page from "./Page"
@@ -6,10 +6,20 @@ import Upload from "./Upload"
import TempUpload from "./TempUpload"
import Auth from "../../Auth"
import App from "../../App"
-import MicroPubApi, { POST_ERROR } from "../../../api/MicroPubApi"
import { upload_large_media_task, create_cancel_source } from "./uploadLargeMediaTask"
-export default Destination = types.model('Destination', {
+const upload_from_temp_upload = temp_upload => ({
+ url: temp_upload.url,
+ published: "",
+ poster: temp_upload.poster || "",
+ preview_uri: temp_upload.cached_uri || temp_upload.uri,
+ alt: "",
+ is_ai: false,
+ sizes: {},
+ cdn: {}
+})
+
+const Destination = types.model('Destination', {
uid: types.identifier,
name: types.maybeNull(types.string),
"microblog-audio": types.optional(types.boolean, false),
@@ -133,15 +143,20 @@ export default Destination = types.model('Destination', {
const url = entry.url || ""
const published = entry.published || ""
const poster = entry.poster || ""
+ const existing_upload = self.uploads.find(upload => upload.url === url)
+ const preview_uri = entry.preview_uri || existing_upload?.preview_uri
const alt = entry.alt || ""
const is_ai = entry["microblog-ai"]
+ const sizes = entry.sizes || {}
const cdn = entry.cdn || {}
const upload = {
url: url,
published: published,
poster: poster,
+ preview_uri: preview_uri,
alt: alt,
is_ai: is_ai,
+ sizes: sizes,
cdn: cdn
}
if (url === "") {
@@ -181,51 +196,71 @@ export default Destination = types.model('Destination', {
media,
service_object,
cancel_source,
- is_cancelled: () => temp_upload.cancelled,
- on_progress: progress => temp_upload.update_progress(progress),
- on_local_uri: uri => temp_upload.set_cached_uri(uri)
+ is_cancelled: () => !isAlive(temp_upload) || temp_upload.cancelled,
+ on_progress: progress => {
+ if (isAlive(temp_upload)) {
+ temp_upload.update_progress(progress)
+ }
+ },
+ on_local_uri: uri => {
+ if (isAlive(temp_upload)) {
+ temp_upload.set_cached_uri(uri)
+ }
+ }
})
+ if (!isAlive(self) || !isAlive(temp_upload)) {
+ return
+ }
temp_upload.url = result.url
if (result.poster) {
temp_upload.poster = result.poster
}
temp_upload.did_upload = true
-
- const upload_entry = {
- url: temp_upload.url,
- poster: temp_upload.poster
- }
+ const uploaded_url = temp_upload.url
+ const upload_entry = upload_from_temp_upload(temp_upload)
self.uploads.unshift(upload_entry)
- yield service.check_for_uploads_for_destination(self)
+ yield service.check_for_uploads_for_destination(self, false)
- const asset = self.uploads.find(a => a.url === temp_upload.url)
+ if (!isAlive(self)) {
+ return
+ }
+ if (!self.uploads.find(a => a.url === uploaded_url)) {
+ self.uploads.unshift(upload_entry)
+ }
+ const asset = self.uploads.find(a => a.url === uploaded_url)
if (App.post_modal_is_open && asset) {
Auth.selected_user.posting?.add_to_post_text(asset.best_post_markup())
}
- const temp_index = self.temp_uploads.indexOf(temp_upload)
- if (temp_index >= 0) {
- self.temp_uploads.splice(temp_index, 1)
+ if (isAlive(temp_upload)) {
+ const temp_index = self.temp_uploads.indexOf(temp_upload)
+ if (temp_index >= 0) {
+ self.temp_uploads.splice(temp_index, 1)
+ }
}
}
catch (error) {
- const was_cancelled = temp_upload.cancelled
+ const was_cancelled = !isAlive(temp_upload) || temp_upload.cancelled
console.log("Destination:upload_large_media:error", error)
if (!was_cancelled) {
Alert.alert("Upload Failed", error?.message || "Could not upload video.")
}
- temp_upload.did_upload = false
- yield temp_upload.update_progress(0)
- const temp_index = self.temp_uploads.indexOf(temp_upload)
- if (temp_index >= 0) {
- self.temp_uploads.splice(temp_index, 1)
+ if (isAlive(temp_upload)) {
+ temp_upload.did_upload = false
+ yield temp_upload.update_progress(0)
+ const temp_index = self.temp_uploads.indexOf(temp_upload)
+ if (temp_index >= 0) {
+ self.temp_uploads.splice(temp_index, 1)
+ }
}
}
finally {
- temp_upload.is_uploading = false
- temp_upload.cancel_source = null
+ if (isAlive(temp_upload)) {
+ temp_upload.is_uploading = false
+ temp_upload.cancel_source = null
+ }
}
}),
@@ -234,26 +269,32 @@ export default Destination = types.model('Destination', {
const temp_upload = TempUpload.create(media)
self.temp_uploads.push(temp_upload)
const result = yield temp_upload.upload(service.service_object(), self)
- if (result) {
+ if (result && isAlive(self) && isAlive(temp_upload)) {
console.log("Destination:upload_media:success", result)
- const upload = {
- url: temp_upload.url,
- poster: temp_upload.poster,
- }
+ const uploaded_url = temp_upload.url
+ const upload = upload_from_temp_upload(temp_upload)
self.uploads.unshift(upload)
self.temp_uploads.remove(temp_upload)
+ yield service.check_for_uploads_for_destination(self, false)
+ if (!isAlive(self)) {
+ return
+ }
+ if (!self.uploads.find(a => a.url === uploaded_url)) {
+ self.uploads.unshift(upload)
+ }
// Because we're uploading from within the post editor, we also
// want to automatically set the upload within the post.
if(App.post_modal_is_open){
- const asset = self.uploads.find(a => a.url === upload.url)
+ const asset = self.uploads.find(a => a.url === uploaded_url)
if(asset){
Auth.selected_user.posting?.add_to_post_text(asset.best_post_markup())
// TODO: Should we bring this back?
// Navigation.pop(UPLOADS_MODAL_SCREEN)
}
}
- //service.check_for_uploads_for_destination(self)
}
})
}))
+
+export default Destination
diff --git a/src/stores/models/posting/MediaAsset.js b/src/stores/models/posting/MediaAsset.js
index 94507e30..b947310f 100644
--- a/src/stores/models/posting/MediaAsset.js
+++ b/src/stores/models/posting/MediaAsset.js
@@ -1,6 +1,7 @@
-import { types, flow } from 'mobx-state-tree';
+import { types, flow, isAlive } from 'mobx-state-tree';
import MicroPubApi, { POST_ERROR } from './../../../api/MicroPubApi';
import XMLRPCApi, { XML_ERROR } from '../../../api/XMLRPCApi';
+import { originalFileNameFromMedia } from '../../../utils/file_names'
const FS = require("react-native-fs")
import axios from 'axios';
@@ -17,12 +18,17 @@ export default MediaAsset = types.model('MediaAsset', {
alt_text: types.maybe(types.string),
progress: types.optional(types.number, 0),
base64: types.maybe(types.string),
+ original_file_name: types.maybe(types.string),
upload_id: types.maybe(types.number),
is_video: types.optional(types.boolean, false),
is_inline: types.optional(types.boolean, false),
cached_uri: types.maybe(types.string),
cancelled: types.optional(types.boolean, false)
})
+.preProcessSnapshot(snapshot => ({
+ ...snapshot,
+ original_file_name: originalFileNameFromMedia(snapshot) || undefined
+}))
.volatile(() => ({
cancel_source: null
}))
@@ -33,7 +39,21 @@ export default MediaAsset = types.model('MediaAsset', {
self.is_uploading = true
if (service_object.type !== "xmlrpc") {
self.cancel_source = axios.CancelToken.source()
- const response = yield MicroPubApi.upload_image(service_object, self)
+ const upload_file = {
+ uri: self.uri,
+ type: self.type,
+ original_file_name: self.original_file_name,
+ cancel_source: self.cancel_source,
+ update_progress: progress => {
+ if (isAlive(self)) {
+ self.update_progress(progress)
+ }
+ }
+ }
+ const response = yield MicroPubApi.upload_image(service_object, upload_file)
+ if (!isAlive(self)) {
+ return false
+ }
console.log("MediaAsset:upload", response)
if (response !== POST_ERROR && response.success) {
const upload_url = response.headers?.location || response.data?.url
@@ -56,7 +76,15 @@ export default MediaAsset = types.model('MediaAsset', {
}
else {
console.log("MediaAsset:upload:base64", self.base64 != null)
- const response = yield XMLRPCApi.upload_image(service_object, self)
+ const upload_file = {
+ type: self.type,
+ mime: self.mime,
+ base64: self.base64
+ }
+ const response = yield XMLRPCApi.upload_image(service_object, upload_file)
+ if (!isAlive(self)) {
+ return false
+ }
console.log("MediaAsset:upload", response)
if (response !== XML_ERROR && response.success) {
if (response.link && response.link.trim() !== "") {
@@ -75,6 +103,7 @@ export default MediaAsset = types.model('MediaAsset', {
}
self.is_uploading = false
+ return self.did_upload && self.remote_url != null
}),
set_alt_text: flow(function* (text) {
@@ -116,6 +145,7 @@ export default MediaAsset = types.model('MediaAsset', {
var new_asset = MediaAsset.create({
uri: "file://" + new_path,
type: self.type,
+ original_file_name: self.original_file_name,
width: self.width,
height: self.height
})
diff --git a/src/stores/models/posting/MediaAsset.test.js b/src/stores/models/posting/MediaAsset.test.js
new file mode 100644
index 00000000..6c247a0a
--- /dev/null
+++ b/src/stores/models/posting/MediaAsset.test.js
@@ -0,0 +1,36 @@
+import MediaAsset from "./MediaAsset"
+
+jest.mock("../../../api/MicroPubApi", () => ({
+ __esModule: true,
+ default: {},
+ POST_ERROR: 3
+}))
+
+jest.mock("../../../api/XMLRPCApi", () => ({
+ __esModule: true,
+ default: {},
+ XML_ERROR: 2
+}))
+
+jest.mock("react-native-fs", () => ({}))
+
+describe("MediaAsset filenames", () => {
+ test("preserves the image picker fileName in the model", () => {
+ const asset = MediaAsset.create({
+ uri: "file:///tmp/picker-video.mov",
+ type: "video/quicktime",
+ fileName: "Family Clip.MOV"
+ })
+
+ expect(asset.original_file_name).toBe("Family Clip.MOV")
+ })
+
+ test("falls back to the URI basename", () => {
+ const asset = MediaAsset.create({
+ uri: "file:///tmp/Shared%20Photo.JPG",
+ type: "image/jpeg"
+ })
+
+ expect(asset.original_file_name).toBe("Shared Photo.JPG")
+ })
+})
diff --git a/src/stores/models/posting/Service.js b/src/stores/models/posting/Service.js
index 73347115..7f9ed76a 100644
--- a/src/stores/models/posting/Service.js
+++ b/src/stores/models/posting/Service.js
@@ -9,7 +9,7 @@ import { pick, types as pickerTypes } from '@react-native-documents/picker'
import { launchImageLibrary } from 'react-native-image-picker'
import App from '../../App'
-export default Service = types.model('Service', {
+const Service = types.model('Service', {
id: types.identifier,
name: types.maybeNull(types.string),
url: types.maybeNull(types.string),
@@ -139,6 +139,20 @@ export default Service = types.model('Service', {
self.check_for_posts_for_destination(active_destination)
}
}),
+
+ refresh_posts_for_current_view: flow(function* (post = null) {
+ const active_destination = self.config.posts_destination()
+ const show_drafts = post?.is_draft ? post.is_draft() : false
+
+ if(active_destination){
+ if(App.post_search_query?.length > 2){
+ yield App.set_posts_query(App.post_search_query, active_destination, show_drafts)
+ }
+ else {
+ yield self.check_for_posts_for_destination(active_destination, show_drafts)
+ }
+ }
+ }),
set_active_destination: flow(function* (destination, type = null) {
if(destination){
@@ -202,7 +216,7 @@ export default Service = types.model('Service', {
const status = yield MicroPubApi.delete_post(self.service_object(), post.url)
if(status !== DELETE_ERROR){
App.show_toast("Post was deleted.")
- self.update_posts_for_active_destination()
+ yield self.refresh_posts_for_current_view(post)
}
else{
Alert.alert("Whoops", "Could not delete post. Please try again.")
@@ -214,7 +228,7 @@ export default Service = types.model('Service', {
const status = yield MicroPubApi.delete_post(self.service_object(), page.url)
if(status !== DELETE_ERROR){
App.show_toast("Page was deleted.")
- self.update_pages_for_active_destination()
+ yield self.refresh_pages_for_current_view()
}
else{
Alert.alert("Whoops", "Could not delete post. Please try again.")
@@ -226,7 +240,7 @@ export default Service = types.model('Service', {
const status = yield MicroPubApi.publish_draft(self.service_object(), post.content, post.url, post.name)
if(status !== DELETE_ERROR){
App.show_toast("Post was published.")
- self.update_posts_for_active_destination()
+ yield self.refresh_posts_for_current_view(post)
}
else{
Alert.alert("Error Publishing", "Could not publish the draft. Please try again.")
@@ -252,6 +266,19 @@ export default Service = types.model('Service', {
self.check_for_pages_for_destination(active_destination)
}
}),
+
+ refresh_pages_for_current_view: flow(function* () {
+ const active_destination = self.config.pages_destination()
+
+ if(active_destination){
+ if(App.page_search_query?.length > 2){
+ yield App.set_pages_query(App.page_search_query, active_destination)
+ }
+ else {
+ yield self.check_for_pages_for_destination(active_destination)
+ }
+ }
+ }),
update_uploads_for_active_destination: flow(function* () {
const active_destination = self.config.posts_destination()
@@ -260,16 +287,20 @@ export default Service = types.model('Service', {
}
}),
- check_for_uploads_for_destination: flow(function* (destination) {
+ check_for_uploads_for_destination: flow(function* (destination, show_loading = true) {
if(destination){
- self.is_loading_uploads = true
+ if (show_loading) {
+ self.is_loading_uploads = true
+ }
console.log("Endpoint:check_for_uploads_for_destination", destination.uid)
const data = yield MicroPubApi.get_uploads(self.service_object(), destination.uid)
console.log("Endpoint:check_for_uploads_for_destination:posts", data?.items?.length)
if(data?.items != null && data.items?.length > 0){
destination.set_uploads(data.items)
}
- self.is_loading_uploads = false
+ if (show_loading) {
+ self.is_loading_uploads = false
+ }
}
}),
@@ -404,6 +435,8 @@ export default Service = types.model('Service', {
}),
}))
+
+export default Service
.views(self => ({
credentials() {
diff --git a/src/stores/models/posting/TempUpload.js b/src/stores/models/posting/TempUpload.js
index cbe6770d..08850283 100644
--- a/src/stores/models/posting/TempUpload.js
+++ b/src/stores/models/posting/TempUpload.js
@@ -1,10 +1,11 @@
import Clipboard from '@react-native-clipboard/clipboard'
-import { types, flow } from 'mobx-state-tree'
+import { types, flow, isAlive } from 'mobx-state-tree'
import { Image } from 'react-native'
import Toast from 'react-native-simple-toast'
import axios from 'axios'
import MicroPubApi, { POST_ERROR } from '../../../api/MicroPubApi'
import RNFS from 'react-native-fs'
+import { originalFileNameFromMedia } from '../../../utils/file_names'
const get_image_size = uri => {
return new Promise(resolve => {
@@ -15,10 +16,12 @@ const get_image_size = uri => {
})
}
-export default TempUpload = types.model('TempUpload', {
+const TempUpload = types.model('TempUpload', {
uri: types.identifier,
cached_uri: types.maybe(types.string),
name: types.maybe(types.string),
+ fileName: types.maybe(types.string),
+ original_file_name: types.maybe(types.string),
type: types.maybe(types.string),
url: types.maybe(types.string),
published: types.maybe(types.string),
@@ -30,21 +33,40 @@ export default TempUpload = types.model('TempUpload', {
did_upload: types.optional(types.boolean, false),
cancelled: types.optional(types.boolean, false)
})
+ .preProcessSnapshot(snapshot => ({
+ ...snapshot,
+ original_file_name: originalFileNameFromMedia(snapshot) || undefined
+ }))
.volatile(() => ({
cancel_source: null
}))
.actions(self => ({
upload: flow(function* (service_object, destination) {
- console.log("TempUpload:upload", service_object, destination.uri)
+ console.log("TempUpload:upload", service_object, destination.uid)
self.is_uploading = true
self.cancel_source = axios.CancelToken.source()
- const response = yield MicroPubApi.upload_media(service_object, self, destination.uri)
+ const upload_file = {
+ uri: self.uri,
+ type: self.type,
+ original_file_name: self.original_file_name,
+ cancel_source: self.cancel_source,
+ update_progress: progress => {
+ if (isAlive(self)) {
+ self.update_progress(progress)
+ }
+ }
+ }
+ const response = yield MicroPubApi.upload_media(service_object, upload_file, destination.uid)
+ if (!isAlive(self)) {
+ return false
+ }
console.log("TempUpload:upload", response)
if (response !== POST_ERROR && response.success) {
const upload_url = response.headers?.location || response.data?.url
if (upload_url && upload_url.trim() !== "") {
self.url = upload_url
+ self.poster = response.data?.poster || response.poster || ""
self.did_upload = true
console.log("TempUpload:upload:success", self.url)
} else {
@@ -165,3 +187,5 @@ export default TempUpload = types.model('TempUpload', {
return self.uri?.endsWith(".mp3") || self.uri?.endsWith(".wav") || self.uri?.endsWith(".ogg") || self.uri?.endsWith(".flac") || self.uri?.endsWith(".m4a") || self.uri?.endsWith(".aac") || self.uri?.endsWith(".wma")
}
}))
+
+export default TempUpload
diff --git a/src/stores/models/posting/Upload.js b/src/stores/models/posting/Upload.js
index 1d458ec6..76d3ea9a 100644
--- a/src/stores/models/posting/Upload.js
+++ b/src/stores/models/posting/Upload.js
@@ -12,15 +12,23 @@ const get_image_size = uri => {
})
}
-export default Upload = types.model('Upload', {
+const UploadVariants = types.model('UploadVariants', {
+ large: types.maybe(types.string),
+ medium: types.maybe(types.string),
+ small: types.maybe(types.string)
+})
+
+const Upload = types.model('Upload', {
url: types.identifier,
published: types.maybe(types.string),
poster: types.maybe(types.string),
+ preview_uri: types.maybe(types.string),
alt: types.maybe(types.string),
width: types.maybe(types.number),
height: types.maybe(types.number),
is_ai: types.optional(types.boolean, true),
- cdn: types.optional(types.map(types.string), {})
+ sizes: types.optional(UploadVariants, {}),
+ cdn: types.optional(UploadVariants, {})
})
.actions(self => ({
@@ -141,3 +149,5 @@ export default Upload = types.model('Upload', {
}
}
}))
+
+export default Upload
diff --git a/src/stores/models/posting/uploadLargeMediaTask.js b/src/stores/models/posting/uploadLargeMediaTask.js
index 58ba8503..e2a47158 100644
--- a/src/stores/models/posting/uploadLargeMediaTask.js
+++ b/src/stores/models/posting/uploadLargeMediaTask.js
@@ -2,6 +2,7 @@ import { Platform } from "react-native"
import RNFS from "react-native-fs"
import axios from "axios"
import MicroPubApi, { POST_ERROR, FETCH_ERROR } from "../../../api/MicroPubApi"
+import { buildCacheFileName, buildUploadFileName } from "../../../utils/file_names"
export const LARGE_UPLOAD_CHUNK_SIZE = 1000 * 1024
export const LARGE_UPLOAD_MAX_SIZE = 1024 * 1024 * 1024
@@ -10,38 +11,6 @@ export const LARGE_UPLOAD_MAX_ATTEMPTS = 45
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
-export const infer_extension_from_type = (mime = "") => {
- if (mime.includes("quicktime")) {
- return ".mov"
- }
- if (mime.includes("mp4")) {
- return ".mp4"
- }
- if (mime.includes("m4v")) {
- return ".m4v"
- }
- if (mime.includes("webm")) {
- return ".webm"
- }
- if (mime.includes("ogg")) {
- return ".ogv"
- }
- return ".mp4"
-}
-
-export const build_file_name = (media, fallback_id) => {
- if (media?.fileName) {
- return media.fileName
- }
- if (media?.filename) {
- return media.filename
- }
- if (media?.name) {
- return media.name
- }
- return `upload-${fallback_id}${infer_extension_from_type(media?.type || "")}`
-}
-
export const ensure_local_uri_for_upload = async (media, fallback_name) => {
const current_uri = media?.cached_uri || media?.cachedUri || media?.uri
if (!current_uri) {
@@ -50,8 +19,8 @@ export const ensure_local_uri_for_upload = async (media, fallback_name) => {
if (current_uri.startsWith("file://")) {
return current_uri
}
- const target_name = fallback_name || `upload-${Date.now()}${infer_extension_from_type(media?.type || "")}`
- const safe_name = target_name.replace(/[^\w.\-]/g, "_")
+ const target_name = fallback_name || buildUploadFileName(media, Date.now(), "video/mp4")
+ const safe_name = buildCacheFileName(target_name)
const target_path = `${RNFS.CachesDirectoryPath}/${Date.now()}-${safe_name}`
let source_path = current_uri
try {
@@ -98,8 +67,8 @@ export async function upload_large_media_task({
const active_cancel_source = cancel_source || create_cancel_source()
const file_id = Math.floor(Math.random() * 1000000)
- const file_name = build_file_name(media, file_id)
const file_type = media?.type || "video/mp4"
+ const file_name = buildUploadFileName(media, file_id, file_type)
try {
const local_uri = await ensure_local_uri_for_upload(media, file_name)
diff --git a/src/utils/android_toolbar_inset.test.js b/src/utils/android_toolbar_inset.test.js
new file mode 100644
index 00000000..b671dbfa
--- /dev/null
+++ b/src/utils/android_toolbar_inset.test.js
@@ -0,0 +1,42 @@
+// Documents CustomToolbar.kt top-inset resolution (SafeArea may consume statusBars).
+function resolve_android_toolbar_top_inset({
+ should_apply_top_inset = false,
+ unhandled_status_bar_top = 0,
+ decor_view_top = 0,
+} = {}) {
+ if (!should_apply_top_inset) {
+ return 0
+ }
+
+ if (unhandled_status_bar_top > 0) {
+ return unhandled_status_bar_top
+ }
+
+ return Math.max(0, decor_view_top)
+}
+
+describe('resolve_android_toolbar_top_inset', () => {
+ test('returns 0 when top inset should not be applied', () => {
+ expect(resolve_android_toolbar_top_inset({
+ should_apply_top_inset: false,
+ unhandled_status_bar_top: 0,
+ decor_view_top: 48,
+ })).toBe(0)
+ })
+
+ test('prefers unhandled status bar inset when present', () => {
+ expect(resolve_android_toolbar_top_inset({
+ should_apply_top_inset: true,
+ unhandled_status_bar_top: 24,
+ decor_view_top: 48,
+ })).toBe(24)
+ })
+
+ test('falls back to DecorView inset when SafeArea consumed status bars', () => {
+ expect(resolve_android_toolbar_top_inset({
+ should_apply_top_inset: true,
+ unhandled_status_bar_top: 0,
+ decor_view_top: 48,
+ })).toBe(48)
+ })
+})
diff --git a/src/utils/file_names.js b/src/utils/file_names.js
new file mode 100644
index 00000000..ffcca67b
--- /dev/null
+++ b/src/utils/file_names.js
@@ -0,0 +1,119 @@
+const EXTENSIONS_BY_MIME_TYPE = {
+ "application/pdf": ".pdf",
+ "audio/aac": ".aac",
+ "audio/flac": ".flac",
+ "audio/m4a": ".m4a",
+ "audio/mp3": ".mp3",
+ "audio/mp4": ".m4a",
+ "audio/mpeg": ".mp3",
+ "audio/ogg": ".ogg",
+ "audio/wav": ".wav",
+ "audio/x-m4a": ".m4a",
+ "audio/x-wav": ".wav",
+ "audio/x-ms-wma": ".wma",
+ "image/gif": ".gif",
+ "image/heic": ".heic",
+ "image/heif": ".heic",
+ "image/jpeg": ".jpg",
+ "image/jpg": ".jpg",
+ "image/jxl": ".jxl",
+ "image/png": ".png",
+ "image/svg+xml": ".svg",
+ "image/webp": ".webp",
+ "video/avi": ".avi",
+ "video/mp4": ".mp4",
+ "video/mpeg": ".mp4",
+ "video/mov": ".mov",
+ "video/ogg": ".ogv",
+ "video/quicktime": ".mov",
+ "video/webm": ".webm",
+ "video/x-flv": ".flv",
+ "video/x-m4v": ".m4v",
+ "video/x-ms-wmv": ".wmv"
+}
+
+// Leave room for the timestamp prefix and filesystem-specific filename limits.
+const MAX_CACHE_FILE_NAME_LENGTH = 120
+
+export const inferExtensionFromType = (mime = "") => {
+ const normalized_mime = mime.toLowerCase().split(";")[0].trim()
+ return EXTENSIONS_BY_MIME_TYPE[normalized_mime] || ""
+}
+
+const decodedValue = value => {
+ try {
+ return decodeURIComponent(value)
+ }
+ catch {
+ return value
+ }
+}
+
+const lastPathComponent = value => {
+ if (typeof value !== "string") {
+ return ""
+ }
+ const without_query = value.split(/[?#]/)[0]
+ const decoded_value = decodedValue(without_query)
+ return decoded_value.split(/[\\/]/).pop() || ""
+}
+
+const getExtension = name => {
+ const dot_index = name.lastIndexOf(".")
+ if (dot_index < 0 || dot_index === name.length - 1) {
+ return ""
+ }
+ const extension = name.slice(dot_index).toLowerCase()
+ return /^\.[a-z0-9]{1,10}$/.test(extension) ? extension : ""
+}
+
+const fileNameStem = (name, extension = "") => {
+ const name_without_extension = extension ? name.slice(0, -extension.length) : name
+ return name_without_extension.replace(/^[.-]+|[.-]+$/g, "")
+}
+
+export const originalFileNameFromMedia = media => {
+ const explicit_name = media?.original_file_name || media?.fileName || media?.filename || media?.name
+ return lastPathComponent(explicit_name || media?.uri || media?.cached_uri || media?.cachedUri)
+}
+
+export const sanitizeFileName = name => {
+ const last_component = lastPathComponent(name).trim()
+ if (!last_component) {
+ return ""
+ }
+ const normalized_component = typeof last_component.normalize === "function"
+ ? last_component.normalize("NFKD")
+ : last_component
+ return normalized_component
+ .replace(/[\u0300-\u036f]/g, "")
+ .toLowerCase()
+ .replace(/[\s_]+/g, "-")
+ .replace(/-+/g, "-")
+ .replace(/\.{2,}/g, ".")
+ .replace(/[^0-9a-z.-]/g, "")
+}
+
+export const buildUploadFileName = (media, fallback_id, fallback_type = "") => {
+ const fallback_name = `upload-${fallback_id ?? Date.now()}`
+ const candidate_name = originalFileNameFromMedia(media)
+ const safe_candidate = sanitizeFileName(candidate_name)
+ const candidate_extension = getExtension(safe_candidate)
+ const expected_extension = inferExtensionFromType(media?.type || fallback_type)
+ const stem = fileNameStem(safe_candidate, candidate_extension) || fallback_name
+
+ if (expected_extension) {
+ return `${stem}${expected_extension}`
+ }
+ return `${stem}${candidate_extension}`
+}
+
+export const buildCacheFileName = (name, max_length = MAX_CACHE_FILE_NAME_LENGTH) => {
+ const safe_name = sanitizeFileName(name)
+ const extension = getExtension(safe_name)
+ const fallback_stem = "upload"
+ const available_length = Math.max(1, max_length - extension.length)
+ const full_stem = fileNameStem(safe_name, extension) || fallback_stem
+ const truncated_stem = full_stem.slice(0, available_length).replace(/[.-]+$/g, "") || fallback_stem
+ return `${truncated_stem}${extension}`
+}
diff --git a/src/utils/file_names.test.js b/src/utils/file_names.test.js
new file mode 100644
index 00000000..cfa53f5f
--- /dev/null
+++ b/src/utils/file_names.test.js
@@ -0,0 +1,74 @@
+import {
+ buildCacheFileName,
+ buildUploadFileName,
+ inferExtensionFromType,
+ originalFileNameFromMedia,
+ sanitizeFileName
+} from "./file_names"
+
+describe("upload filenames", () => {
+ test("normalizes picker filename shapes", () => {
+ expect(buildUploadFileName({
+ original_file_name: "Final Clip.MOV",
+ fileName: "Ignored Clip.mp4",
+ type: "video/quicktime"
+ }, 1)).toBe("final-clip.mov")
+ expect(buildUploadFileName({
+ name: "My Recording.MP3",
+ type: "audio/mpeg"
+ }, 2)).toBe("my-recording.mp3")
+ })
+
+ test("falls back to a decoded URI basename", () => {
+ const media = {
+ uri: "file:///tmp/My%20Photo.jpeg?edited=true",
+ type: "image/jpeg"
+ }
+ expect(originalFileNameFromMedia(media)).toBe("My Photo.jpeg")
+ expect(buildUploadFileName(media, 3)).toBe("my-photo.jpg")
+ })
+
+ test("uses the MIME type when the filename is missing", () => {
+ expect(buildUploadFileName({ type: "audio/mpeg" }, 42)).toBe("upload-42.mp3")
+ expect(buildUploadFileName({ type: "image/jpeg" }, 42)).toBe("upload-42.jpg")
+ expect(buildUploadFileName({ type: "video/quicktime" }, 42)).toBe("upload-42.mov")
+ expect(buildUploadFileName({ type: "video/mpeg" }, 42)).toBe("upload-42.mp4")
+ expect(buildUploadFileName({}, 42)).toBe("upload-42")
+ })
+
+ test("distinguishes audio and video Ogg files", () => {
+ expect(inferExtensionFromType("audio/ogg")).toBe(".ogg")
+ expect(inferExtensionFromType("video/ogg; codecs=theora")).toBe(".ogv")
+ })
+
+ test("replaces missing or mismatched extensions", () => {
+ expect(buildUploadFileName({ fileName: "clip.", type: "video/mp4" }, 4)).toBe("clip.mp4")
+ expect(buildUploadFileName({ fileName: "draft.final", type: "video/mp4" }, 4)).toBe("draft.mp4")
+ expect(buildUploadFileName({ fileName: "photo.heic", type: "image/jpeg" }, 4)).toBe("photo.jpg")
+ })
+
+ test("uses a fallback stem when normalization removes the original name", () => {
+ expect(buildUploadFileName({ fileName: "旅行.mov", type: "video/quicktime" }, 9)).toBe("upload-9.mov")
+ expect(sanitizeFileName("Résumé 2026.MOV")).toBe("resume-2026.mov")
+ })
+
+ test("removes path components from explicit names", () => {
+ expect(originalFileNameFromMedia({ fileName: "C:\\private\\Clip One.MOV" })).toBe("Clip One.MOV")
+ expect(buildUploadFileName({
+ fileName: "../private/Clip One.MOV",
+ type: "video/quicktime"
+ }, 5)).toBe("clip-one.mov")
+ })
+
+ test("keeps unknown but plausible extensions", () => {
+ expect(buildUploadFileName({ fileName: "archive.custom" }, 6)).toBe("archive.custom")
+ expect(buildUploadFileName({ fileName: "archive" }, 6)).toBe("archive")
+ })
+
+ test("limits cache filenames while preserving the extension", () => {
+ const filename = buildCacheFileName(`${"a".repeat(200)}.mp4`)
+ expect(filename).toHaveLength(120)
+ expect(filename.endsWith(".mp4")).toBe(true)
+ expect(buildCacheFileName("../../Private Clip.MOV")).toBe("private-clip.mov")
+ })
+})
diff --git a/src/utils/navigation.js b/src/utils/navigation.js
new file mode 100644
index 00000000..d2b1dba7
--- /dev/null
+++ b/src/utils/navigation.js
@@ -0,0 +1,83 @@
+import { NativeModules, Platform } from 'react-native'
+import App from '../stores/App'
+import {
+ android_status_bar_style,
+ should_use_android_translucent_status_bar,
+} from './theme'
+import { isLiquidGlass } from './ui'
+
+function android_edge_to_edge_enabled() {
+ try {
+ return NativeModules.NativeDeviceInfo?.getConstants?.()?.isEdgeToEdge === true
+ }
+ catch (error) {
+ return false
+ }
+}
+
+export function android_uses_translucent_status_bar() {
+ return should_use_android_translucent_status_bar({
+ platform_os: Platform.OS,
+ platform_version: Platform.Version,
+ edge_to_edge_enabled: android_edge_to_edge_enabled(),
+ })
+}
+
+export function android_status_bar_options() {
+ if (Platform.OS !== 'android') {
+ return {}
+ }
+
+ const options = {
+ statusBarStyle: android_status_bar_style(App.is_dark_mode()),
+ }
+
+ // Only draw under the status bar when CustomToolbar adds the matching top inset.
+ if (android_uses_translucent_status_bar()) {
+ options.statusBarTranslucent = true
+ }
+
+ return options
+}
+
+function customHeaderItem(element, options = {}) {
+ return {
+ type: 'custom',
+ element,
+ hidesSharedBackground: options.hidesSharedBackground === true
+ }
+}
+
+export function headerLeftElement(renderElement, options = {}) {
+ if (isLiquidGlass()) {
+ return {
+ unstable_headerLeftItems: () => [customHeaderItem(renderElement(), options)]
+ }
+ }
+
+ return {
+ headerLeft: renderElement
+ }
+}
+
+export function headerRightElement(renderElement, options = {}) {
+ if (isLiquidGlass()) {
+ return {
+ unstable_headerRightItems: () => [customHeaderItem(renderElement(), options)]
+ }
+ }
+
+ return {
+ headerRight: renderElement
+ }
+}
+
+export function headerItemGroupStyle(gap = 10) {
+ return {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap,
+ paddingHorizontal: isLiquidGlass() ? 8 : 0
+ }
+}
diff --git a/src/utils/push_notifications.js b/src/utils/push_notifications.js
new file mode 100644
index 00000000..658876f5
--- /dev/null
+++ b/src/utils/push_notifications.js
@@ -0,0 +1,77 @@
+export function parse_notification_user(value) {
+ if (value == null) {
+ return null
+ }
+
+ if (typeof value === 'string') {
+ try {
+ return JSON.parse(value)
+ }
+ catch (_error) {
+ return null
+ }
+ }
+
+ if (typeof value === 'object') {
+ return value
+ }
+
+ return null
+}
+
+export function normalise_notification_payload(notification, platform_os = 'ios') {
+ const data = notification?.data || {}
+ const to_user = parse_notification_user(data.to_user)
+ const from_user = parse_notification_user(data.from_user)
+ const tapped_from_os = notification?.userInteraction === true || notification?.foreground === false
+ const opened_from_foreground_banner = notification?.userInteraction === true && notification?.foreground === true
+ const post_id = data?.post_id != null ? String(data.post_id) : null
+
+ return {
+ id: platform_os === 'ios'
+ ? (data?.notificationId != null ? String(data.notificationId) : (post_id || notification?.id || notification?.message || `${Date.now()}`))
+ : String(notification?.id || data?.id || post_id || notification?.message || `${Date.now()}`),
+ message: notification?.message || null,
+ post_id,
+ to_username: to_user?.username || null,
+ from_username: from_user?.username || null,
+ should_open: tapped_from_os && post_id != null,
+ opened_from_foreground_banner
+ }
+}
+
+export function determine_notification_action({ notification, has_navigation, has_local_user, is_app_active = true }) {
+ if (notification?.should_open) {
+ if (notification?.post_id == null) {
+ return 'show'
+ }
+
+ if (has_navigation && has_local_user && (is_app_active || notification?.opened_from_foreground_banner)) {
+ return 'open'
+ }
+
+ return 'queue'
+ }
+
+ return 'show'
+}
+
+export function determine_pending_notification_action({ notification, has_navigation, has_local_user, is_app_active = true }) {
+ if (!notification?.should_open || notification?.post_id == null) {
+ return 'show'
+ }
+
+ if (!has_navigation) {
+ return 'wait'
+ }
+
+ if (!is_app_active && !notification?.opened_from_foreground_banner) {
+ return 'wait'
+ }
+
+ if (!has_local_user) {
+ return 'show'
+ }
+
+ return 'open'
+}
diff --git a/src/utils/push_notifications.test.js b/src/utils/push_notifications.test.js
new file mode 100644
index 00000000..b33034b7
--- /dev/null
+++ b/src/utils/push_notifications.test.js
@@ -0,0 +1,132 @@
+import {
+ normalise_notification_payload,
+ determine_notification_action,
+ determine_pending_notification_action
+} from './push_notifications'
+
+describe('push notification routing', () => {
+ test('queues a tapped notification when the app is not ready to open it yet', () => {
+ const notification = normalise_notification_payload({
+ message: 'Alice mentioned you',
+ data: {
+ post_id: '123',
+ to_user: { username: 'vincent' },
+ from_user: { username: 'alice' }
+ },
+ foreground: false,
+ userInteraction: true
+ }, 'ios')
+
+ expect(determine_notification_action({
+ notification,
+ has_navigation: false,
+ has_local_user: false,
+ is_app_active: false
+ })).toBe('queue')
+ })
+
+ test('opens a queued notification once auth and navigation are ready', () => {
+ const notification = {
+ post_id: '123',
+ to_username: 'vincent',
+ should_open: true
+ }
+
+ expect(determine_pending_notification_action({
+ notification,
+ has_navigation: true,
+ has_local_user: true,
+ is_app_active: true
+ })).toBe('open')
+ })
+
+ test('keeps a queued notification pending until navigation is ready', () => {
+ const notification = {
+ post_id: '123',
+ to_username: 'vincent',
+ should_open: true
+ }
+
+ expect(determine_pending_notification_action({
+ notification,
+ has_navigation: false,
+ has_local_user: true,
+ is_app_active: true
+ })).toBe('wait')
+ })
+
+ test('keeps a queued notification pending until the app becomes active', () => {
+ const notification = {
+ post_id: '123',
+ to_username: 'vincent',
+ should_open: true
+ }
+
+ expect(determine_pending_notification_action({
+ notification,
+ has_navigation: true,
+ has_local_user: true,
+ is_app_active: false
+ })).toBe('wait')
+ })
+
+ test('shows a foreground notification in the in-app sheet instead of auto-opening', () => {
+ const notification = normalise_notification_payload({
+ id: 'abc',
+ message: 'Alice mentioned you',
+ data: {
+ post_id: '123',
+ to_user: '{"username":"vincent"}',
+ from_user: '{"username":"alice"}'
+ },
+ foreground: true,
+ userInteraction: false
+ }, 'android')
+
+ expect(determine_notification_action({
+ notification,
+ has_navigation: true,
+ has_local_user: true,
+ is_app_active: true
+ })).toBe('show')
+ })
+
+ test('opens a tapped foreground banner once navigation is ready', () => {
+ const notification = normalise_notification_payload({
+ message: 'Alice mentioned you',
+ data: {
+ post_id: '123',
+ to_user: { username: 'vincent' },
+ from_user: { username: 'alice' }
+ },
+ foreground: true,
+ userInteraction: true
+ }, 'ios')
+
+ expect(determine_notification_action({
+ notification,
+ has_navigation: true,
+ has_local_user: true,
+ is_app_active: false
+ })).toBe('open')
+ })
+
+ test('falls back to the in-app sheet when a tapped payload cannot be opened', () => {
+ const notification = normalise_notification_payload({
+ message: 'Alice mentioned you',
+ data: {
+ to_user: { username: 'vincent' },
+ from_user: { username: 'alice' }
+ },
+ foreground: false,
+ userInteraction: true
+ }, 'ios')
+
+ expect(determine_notification_action({
+ notification,
+ has_navigation: true,
+ has_local_user: true,
+ is_app_active: true
+ })).toBe('show')
+ })
+})
diff --git a/src/utils/share_intents.js b/src/utils/share_intents.js
new file mode 100644
index 00000000..c9fc9616
--- /dev/null
+++ b/src/utils/share_intents.js
@@ -0,0 +1,19 @@
+export function share_type_for_mime_type(mime_type) {
+ if (typeof mime_type === "string" && mime_type.startsWith("text/")) {
+ return "text"
+ }
+
+ if (mime_type === "application/json") {
+ return "json"
+ }
+
+ if (typeof mime_type === "string" && mime_type.startsWith("image/")) {
+ return "image"
+ }
+
+ return null
+}
+
+export function is_supported_share_mime_type(mime_type) {
+ return share_type_for_mime_type(mime_type) != null
+}
diff --git a/src/utils/share_intents.test.js b/src/utils/share_intents.test.js
new file mode 100644
index 00000000..2d6efc3d
--- /dev/null
+++ b/src/utils/share_intents.test.js
@@ -0,0 +1,30 @@
+import {
+ is_supported_share_mime_type,
+ share_type_for_mime_type
+} from './share_intents'
+
+describe('share intent MIME support', () => {
+ test('supports text shares for text and URLs', () => {
+ expect(share_type_for_mime_type('text/plain')).toBe('text')
+ expect(share_type_for_mime_type('text/html')).toBe('text')
+ expect(share_type_for_mime_type('text/uri-list')).toBe('text')
+ expect(is_supported_share_mime_type('text/plain')).toBe(true)
+ })
+
+ test('supports photo shares', () => {
+ expect(share_type_for_mime_type('image/jpeg')).toBe('image')
+ expect(share_type_for_mime_type('image/png')).toBe('image')
+ expect(share_type_for_mime_type('image/heic')).toBe('image')
+ })
+
+ test('does not support video shares', () => {
+ expect(share_type_for_mime_type('video/mp4')).toBe(null)
+ expect(is_supported_share_mime_type('video/mp4')).toBe(false)
+ })
+
+ test('does not support arbitrary file shares', () => {
+ expect(share_type_for_mime_type('application/pdf')).toBe(null)
+ expect(share_type_for_mime_type('application/octet-stream')).toBe(null)
+ expect(share_type_for_mime_type('*/*')).toBe(null)
+ })
+})
diff --git a/src/utils/snapshots.js b/src/utils/snapshots.js
index 43319bc8..67e015d9 100644
--- a/src/utils/snapshots.js
+++ b/src/utils/snapshots.js
@@ -1,7 +1,6 @@
import { onSnapshot } from 'mobx-state-tree';
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from 'expo-secure-store';
-import SFInfo from 'react-native-sensitive-info'
import Tokens from './../stores/Tokens';
import Auth from './../stores/Auth';
@@ -20,8 +19,7 @@ function debounce(func, wait) {
const debounce_ms = 1500
onSnapshot(Tokens, snapshot => {
- SFInfo.setItem('Tokens', JSON.stringify(snapshot), {}),
- SecureStore.setItem('Tokens', JSON.stringify(snapshot), {}),
+ SecureStore.setItem('Tokens', JSON.stringify(snapshot))
console.log("SNAPSHOT:::TOKENS")
});
onSnapshot(Auth, debounce(
diff --git a/src/utils/theme.js b/src/utils/theme.js
new file mode 100644
index 00000000..c4bd868a
--- /dev/null
+++ b/src/utils/theme.js
@@ -0,0 +1,61 @@
+export const DEFAULT_THEME = 'light'
+export const DEFAULT_ACCENT_COLOR = '#f80'
+
+// Matches Build.VERSION_CODES.VANILLA_ICE_CREAM in CustomToolbar.kt.
+export const ANDROID_EDGE_TO_EDGE_API = 35
+
+export const normalise_theme = (theme = null) => {
+ return theme === 'dark' ? 'dark' : DEFAULT_THEME
+}
+
+export const normalise_accent_color = (color = null) => {
+ if (typeof color !== 'string') {
+ return DEFAULT_ACCENT_COLOR
+ }
+
+ if (/^#[0-9a-fA-F]{6}$/.test(color)) {
+ return color.toLowerCase()
+ }
+
+ return DEFAULT_ACCENT_COLOR
+}
+
+export const should_follow_system_theme = () => {
+ return true
+}
+
+export const resolve_app_theme = ({
+ system_theme = null,
+}) => {
+ return normalise_theme(system_theme)
+}
+
+export const resolve_app_accent_color = ({
+ platform_os = '',
+ auto_android_theme = true,
+ system_accent_color = null,
+}) => {
+ if (platform_os === 'android' && auto_android_theme) {
+ return normalise_accent_color(system_accent_color)
+ }
+
+ return DEFAULT_ACCENT_COLOR
+}
+
+// react-native-screens statusBarStyle: light icons on dark chrome, dark icons on light.
+export const android_status_bar_style = (is_dark = false) => {
+ return is_dark ? 'light' : 'dark'
+}
+
+// Must stay in sync with CustomToolbar.kt shouldApplyTopInset.
+export const should_use_android_translucent_status_bar = ({
+ platform_os = '',
+ platform_version = 0,
+ edge_to_edge_enabled = false,
+} = {}) => {
+ if (platform_os !== 'android') {
+ return false
+ }
+
+ return platform_version >= ANDROID_EDGE_TO_EDGE_API || edge_to_edge_enabled
+}
diff --git a/src/utils/theme.test.js b/src/utils/theme.test.js
new file mode 100644
index 00000000..03632e8b
--- /dev/null
+++ b/src/utils/theme.test.js
@@ -0,0 +1,97 @@
+import {
+ android_status_bar_style,
+ normalise_accent_color,
+ normalise_theme,
+ resolve_app_accent_color,
+ resolve_app_theme,
+ should_follow_system_theme,
+ should_use_android_translucent_status_bar,
+} from './theme'
+
+describe('theme resolution', () => {
+ test('normalises unsupported system themes to the default light theme', () => {
+ expect(normalise_theme('dark')).toBe('dark')
+ expect(normalise_theme('light')).toBe('light')
+ expect(normalise_theme(null)).toBe('light')
+ expect(normalise_theme('no-preference')).toBe('light')
+ })
+
+ test('follows the Android system theme when Material You accent is enabled', () => {
+ expect(resolve_app_theme({
+ platform_os: 'android',
+ system_theme: 'dark',
+ auto_android_theme: true,
+ })).toBe('dark')
+ })
+
+ test('still follows the Android system theme when Material You accent is disabled', () => {
+ expect(should_follow_system_theme({
+ platform_os: 'android',
+ auto_android_theme: false,
+ })).toBe(true)
+ expect(resolve_app_theme({
+ platform_os: 'android',
+ system_theme: 'dark',
+ auto_android_theme: false,
+ })).toBe('dark')
+ })
+
+ test('keeps non-Android platforms following the system theme', () => {
+ expect(resolve_app_theme({
+ platform_os: 'ios',
+ system_theme: 'dark',
+ auto_android_theme: false,
+ })).toBe('dark')
+ })
+
+ test('normalises accent colours to hex strings', () => {
+ expect(normalise_accent_color('#3366AA')).toBe('#3366aa')
+ expect(normalise_accent_color('orange')).toBe('#f80')
+ expect(normalise_accent_color(null)).toBe('#f80')
+ })
+
+ test('uses Android system accent only when Material You accent is enabled', () => {
+ expect(resolve_app_accent_color({
+ platform_os: 'android',
+ auto_android_theme: true,
+ system_accent_color: '#3366AA',
+ })).toBe('#3366aa')
+
+ expect(resolve_app_accent_color({
+ platform_os: 'android',
+ auto_android_theme: false,
+ system_accent_color: '#3366AA',
+ })).toBe('#f80')
+ })
+
+ test('maps app theme to react-native-screens status bar icon style', () => {
+ expect(android_status_bar_style(true)).toBe('light')
+ expect(android_status_bar_style(false)).toBe('dark')
+ })
+
+ test('enables translucent status bar only when toolbar inset applies', () => {
+ expect(should_use_android_translucent_status_bar({
+ platform_os: 'ios',
+ platform_version: 35,
+ edge_to_edge_enabled: true,
+ })).toBe(false)
+
+ expect(should_use_android_translucent_status_bar({
+ platform_os: 'android',
+ platform_version: 34,
+ edge_to_edge_enabled: false,
+ })).toBe(false)
+
+ expect(should_use_android_translucent_status_bar({
+ platform_os: 'android',
+ platform_version: 35,
+ edge_to_edge_enabled: false,
+ })).toBe(true)
+
+ expect(should_use_android_translucent_status_bar({
+ platform_os: 'android',
+ platform_version: 34,
+ edge_to_edge_enabled: true,
+ })).toBe(true)
+ })
+})
diff --git a/src/utils/ui.js b/src/utils/ui.js
index 1179c6ee..5406f800 100644
--- a/src/utils/ui.js
+++ b/src/utils/ui.js
@@ -1,10 +1,57 @@
-import { Platform } from 'react-native';
+import { DynamicColorIOS, Platform } from 'react-native'
-export const STANDARD_SLOP = 10;
+export const STANDARD_SLOP = 16
+export const HEADER_BUTTON_HIT_SLOP = {
+ top: STANDARD_SLOP,
+ bottom: STANDARD_SLOP,
+ left: STANDARD_SLOP,
+ right: STANDARD_SLOP
+}
+export const IOS_TAB_BAR_PADDING = 49
+export const LIQUID_GLASS_WEBVIEW_TAB_BAR_PADDING = 56
+export const ANDROID_TAB_BAR_PADDING = 96
+
+export function isLiquidGlass(platform = Platform) {
+ return (
+ (platform.OS === 'ios') && (parseInt(platform.Version, 10) >= 26)
+ )
+}
+
+export function liquidGlassWebViewBottomInset(bottom_safe_area_inset = 0, platform = Platform) {
+ if (!isLiquidGlass(platform)) {
+ return 0
+ }
+
+ return bottom_safe_area_inset + LIQUID_GLASS_WEBVIEW_TAB_BAR_PADDING
+}
+
+export function tabBarBottomInset(bottom_safe_area_inset = 0, platform = Platform) {
+ if (platform.OS === 'android') {
+ return bottom_safe_area_inset + ANDROID_TAB_BAR_PADDING
+ }
+
+ if (platform.OS === 'ios') {
+ if (isLiquidGlass(platform)) {
+ return liquidGlassWebViewBottomInset(bottom_safe_area_inset, platform)
+ }
+
+ return bottom_safe_area_inset + IOS_TAB_BAR_PADDING
+ }
+
+ return 0
+}
+
+export function tabBarScrollContentBottomPadding(bottom_safe_area_inset = 0, base_padding = 0, platform = Platform) {
+ return base_padding + tabBarBottomInset(bottom_safe_area_inset, platform)
+}
+
+export function liquidGlassTintColor() {
+ if (Platform.OS === 'ios') {
+ return DynamicColorIOS({
+ dark: 'white',
+ light: 'black'
+ })
+ }
-export function isLiquidGlass() {
- return (
- false
- // (Platform.OS == 'ios') && (parseInt(Platform.Version, 10) >= 26)
- );
+ return undefined
}
diff --git a/src/utils/ui.test.js b/src/utils/ui.test.js
new file mode 100644
index 00000000..83ee8155
--- /dev/null
+++ b/src/utils/ui.test.js
@@ -0,0 +1,37 @@
+import {
+ ANDROID_TAB_BAR_PADDING,
+ IOS_TAB_BAR_PADDING,
+ LIQUID_GLASS_WEBVIEW_TAB_BAR_PADDING,
+ isLiquidGlass,
+ tabBarBottomInset,
+ tabBarScrollContentBottomPadding,
+} from './ui'
+
+describe('UI inset helpers', () => {
+ test('adds the classic iOS tab bar height before Liquid Glass', () => {
+ const platform = { OS: 'ios', Version: '18.5' }
+
+ expect(isLiquidGlass(platform)).toBe(false)
+ expect(tabBarBottomInset(0, platform)).toBe(IOS_TAB_BAR_PADDING)
+ expect(tabBarBottomInset(34, platform)).toBe(34 + IOS_TAB_BAR_PADDING)
+ })
+
+ test('uses the Liquid Glass tab bar height on iOS 26 and newer', () => {
+ const platform = { OS: 'ios', Version: '26.0' }
+
+ expect(isLiquidGlass(platform)).toBe(true)
+ expect(tabBarBottomInset(34, platform)).toBe(34 + LIQUID_GLASS_WEBVIEW_TAB_BAR_PADDING)
+ })
+
+ test('keeps Android tab padding unchanged', () => {
+ const platform = { OS: 'android', Version: 35 }
+
+ expect(tabBarBottomInset(0, platform)).toBe(ANDROID_TAB_BAR_PADDING)
+ })
+
+ test('adds base scroll padding to the tab bar inset', () => {
+ const platform = { OS: 'ios', Version: '18.5' }
+
+ expect(tabBarScrollContentBottomPadding(0, 10, platform)).toBe(10 + IOS_TAB_BAR_PADDING)
+ })
+})
diff --git a/src/utils/webview_recovery.js b/src/utils/webview_recovery.js
new file mode 100644
index 00000000..a51060b3
--- /dev/null
+++ b/src/utils/webview_recovery.js
@@ -0,0 +1,168 @@
+import { DEFAULT_THEME, normalise_theme } from './theme'
+
+export { normalise_theme }
+
+const decode_query_value = (value = '') => {
+ return decodeURIComponent(`${value}`.replace(/\+/g, ' '))
+}
+
+const parse_query_params = (query = '') => {
+ if (!query) {
+ return []
+ }
+
+ return query
+ .split('&')
+ .filter(Boolean)
+ .map(part => {
+ const [raw_key, ...raw_value_parts] = part.split('=')
+
+ return [
+ decode_query_value(raw_key),
+ decode_query_value(raw_value_parts.join('=')),
+ ]
+ })
+}
+
+const has_query_param = (params = [], key = '') => {
+ return params.some(([current_key]) => current_key === key)
+}
+
+const set_query_param = (params = [], key = '', value = '') => {
+ const next_params = []
+ let did_set = false
+
+ params.forEach(([current_key, current_value]) => {
+ if (current_key === key) {
+ if (!did_set) {
+ next_params.push([key, value])
+ did_set = true
+ }
+
+ return
+ }
+
+ next_params.push([current_key, current_value])
+ })
+
+ if (!did_set) {
+ next_params.push([key, value])
+ }
+
+ return next_params
+}
+
+const serialise_query_params = (params = []) => {
+ return params
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(`${value}`)}`)
+ .join('&')
+}
+
+export const get_base_webview_path = (url_or_endpoint = '') => {
+ let path = `${url_or_endpoint}`.replace(/^https?:\/\/[^\/]+/, '')
+ path = path.replace(/^\//, '')
+ path = path.split('?')[0].split('#')[0]
+ return path
+}
+
+export const is_signin_webview_path = (url_or_endpoint = '') => {
+ return get_base_webview_path(url_or_endpoint) === 'hybrid/signin'
+}
+
+export const is_webview_endpoint_url = ({ endpoint = '', url = '' }) => {
+ return get_base_webview_path(endpoint) === get_base_webview_path(url)
+}
+
+export const should_remount_webview_on_android_resume = ({
+ next_app_state = '',
+ platform_os = '',
+ previous_app_state = '',
+ system_theme = null,
+ theme = DEFAULT_THEME,
+}) => {
+ if (platform_os !== 'android') {
+ return false
+ }
+
+ if (next_app_state !== 'active') {
+ return false
+ }
+
+ if (previous_app_state !== 'background' && previous_app_state !== 'inactive') {
+ return false
+ }
+
+ return normalise_theme(system_theme) !== normalise_theme(theme)
+}
+
+export const invalidate_webview_bootstrap_state = (target = null) => {
+ if (target != null) {
+ target.did_load_one_or_more_webviews = false
+ }
+
+ return target
+}
+
+export const build_webview_endpoint = ({
+ endpoint = '',
+ theme = DEFAULT_THEME,
+}) => {
+ const hash_parts = `${endpoint}`.split('#')
+ const path_and_query = hash_parts[0] ?? ''
+ const hash = hash_parts.length > 1 ? `#${hash_parts.slice(1).join('#')}` : ''
+ const query_index = path_and_query.indexOf('?')
+ const path = query_index > -1 ? path_and_query.slice(0, query_index) : path_and_query
+ const query = query_index > -1 ? path_and_query.slice(query_index + 1) : ''
+ let params = parse_query_params(query)
+
+ params = set_query_param(params, 'theme', normalise_theme(theme))
+
+ if (!hash) {
+ if (!has_query_param(params, 'show_actions')) {
+ params.push(['show_actions', 'true'])
+ }
+
+ if (!has_query_param(params, 'fontsize')) {
+ params.push(['fontsize', '17'])
+ }
+ }
+
+ const query_string = serialise_query_params(params)
+
+ return `${path}${query_string ? `?${query_string}` : ''}${hash}`
+}
+
+export const build_webview_source_uri = ({
+ did_load_one_or_more_webviews = false,
+ endpoint = '',
+ theme = DEFAULT_THEME,
+ token = '',
+ web_url = '',
+}) => {
+ const prepared_endpoint = build_webview_endpoint({ endpoint, theme })
+
+ if (did_load_one_or_more_webviews) {
+ return `${web_url}/${prepared_endpoint}`
+ }
+
+ const query_string = serialise_query_params([
+ ['token', token],
+ ['redirect_to', prepared_endpoint],
+ ['theme', normalise_theme(theme)],
+ ['show_actions', 'true'],
+ ])
+
+ return `${web_url}/hybrid/signin?${query_string}`
+}
+
+export const should_attempt_webview_recovery = ({
+ did_load_one_or_more_webviews = false,
+ has_attempted_recovery = false,
+ url = '',
+}) => {
+ if (!did_load_one_or_more_webviews || has_attempted_recovery) {
+ return false
+ }
+
+ return !is_signin_webview_path(url)
+}