Merge branch 'main' into l10n_main

This commit is contained in:
xmflsct 2022-02-06 23:03:59 +01:00 committed by GitHub
commit cfc315ea8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
187 changed files with 6983 additions and 8811 deletions

View File

@ -5,7 +5,7 @@ export SENTRY_PROJECT=""
export SENTRY_AUTH_TOKEN="" export SENTRY_AUTH_TOKEN=""
export SENTRY_DSN="" export SENTRY_DSN=""
export TOOOT_API_KEY="" export TOOOT_PUSH_KEY_PUBLIC=""
# Fastlane start # Fastlane start
export LC_ALL="" export LC_ALL=""

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ['https://www.buymeacoffee.com/xmflsct']

View File

@ -7,7 +7,7 @@ on:
jobs: jobs:
build: build:
runs-on: macos-10.15 runs-on: macos-11
steps: steps:
- name: -- Step 0 -- Extract branch name - name: -- Step 0 -- Extract branch name
shell: bash shell: bash
@ -18,13 +18,13 @@ jobs:
- name: -- Step 2 -- Setup node - name: -- Step 2 -- Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 14.x node-version: 16.x
- name: -- Step 3 -- Use Expo action - name: -- Step 3 -- Use Expo action
uses: expo/expo-github-action@v5 uses: expo/expo-github-action@v6
with: with:
expo-version: 4.x expo-version: 5.x
expo-username: ${{ secrets.EXPO_USERNAME }} username: ${{ secrets.EXPO_USERNAME }}
expo-token: ${{ secrets.EXPO_TOKEN }} token: ${{ secrets.EXPO_TOKEN }}
- name: -- Step 4 -- Install node dependencies - name: -- Step 4 -- Install node dependencies
run: yarn install run: yarn install
- name: -- Step 5 -- Install native dependencies - name: -- Step 5 -- Install native dependencies
@ -33,6 +33,7 @@ jobs:
run: bundle install run: bundle install
- name: -- Step 7 -- Run fastlane - name: -- Step 7 -- Run fastlane
env: env:
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer
ENVIRONMENT: ${{ steps.branch.outputs.branch }} ENVIRONMENT: ${{ steps.branch.outputs.branch }}
LC_ALL: en_US.UTF-8 LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8 LANG: en_US.UTF-8
@ -40,7 +41,7 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
TOOOT_API_KEY: ${{ secrets.TOOOT_API_KEY }} TOOOT_PUSH_KEY_PUBLIC: ${{ secrets.TOOOT_PUSH_KEY_PUBLIC }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }} FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '35 4 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,41 +1,43 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (3.0.3) CFPropertyList (3.0.5)
activesupport (5.2.5) rexml
activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
addressable (2.7.0) zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
algoliasearch (1.27.5) algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3) httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1) json (>= 1.5.1)
artifactory (3.0.15) artifactory (3.0.15)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.1.1) aws-eventstream (1.2.0)
aws-partitions (1.455.0) aws-partitions (1.551.0)
aws-sdk-core (3.114.0) aws-sdk-core (3.125.5)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0) aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.43.0) aws-sdk-kms (1.53.0)
aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.94.1) aws-sdk-s3 (1.111.3)
aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.4)
aws-sigv4 (1.2.3) aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4) babosa (1.0.4)
claide (1.0.3) claide (1.1.0)
cocoapods (1.10.1) cocoapods (1.11.2)
addressable (~> 2.6) addressable (~> 2.8)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.10.1) cocoapods-core (= 1.11.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0)
@ -46,26 +48,26 @@ GEM
escape (~> 0.0.4) escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0) fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0) gh_inspector (~> 1.0)
molinillo (~> 0.6.6) molinillo (~> 0.8.0)
nap (~> 1.0) nap (~> 1.0)
ruby-macho (~> 1.4) ruby-macho (>= 1.0, < 3.0)
xcodeproj (>= 1.19.0, < 2.0) xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.10.1) cocoapods-core (1.11.2)
activesupport (> 5.0, < 6) activesupport (>= 5.0, < 7)
addressable (~> 2.6) addressable (~> 2.8)
algoliasearch (~> 1.0) algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1) concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4) fuzzy_match (~> 2.0.4)
nap (~> 1.0) nap (~> 1.0)
netrc (~> 0.11) netrc (~> 0.11)
public_suffix public_suffix (~> 4.0)
typhoeus (~> 1.0) typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.4) cocoapods-deintegrate (1.0.5)
cocoapods-downloader (1.4.0) cocoapods-downloader (1.5.1)
cocoapods-plugins (1.0.0) cocoapods-plugins (1.0.0)
nap nap
cocoapods-search (1.0.0) cocoapods-search (1.0.1)
cocoapods-trunk (1.5.0) cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0) nap (>= 0.8, < 2.0)
netrc (~> 0.11) netrc (~> 0.11)
cocoapods-try (1.2.0) cocoapods-try (1.2.0)
@ -73,36 +75,50 @@ GEM
colored2 (3.1.2) colored2 (3.1.2)
commander (4.6.0) commander (4.6.0)
highline (~> 2.0.0) highline (~> 2.0.0)
concurrent-ruby (1.1.8) concurrent-ruby (1.1.9)
declarative (0.0.20) declarative (0.0.20)
digest-crc (0.6.3) digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701) domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6) dotenv (2.7.6)
emoji_regex (3.2.2) emoji_regex (3.2.3)
escape (0.0.4) escape (0.0.4)
ethon (0.12.0) ethon (0.15.0)
ffi (>= 1.3.0) ffi (>= 1.15.0)
excon (0.81.0) excon (0.90.0)
faraday (1.4.1) faraday (1.9.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1) faraday-net_http_persistent (~> 1.0)
multipart-post (>= 1.2, < 3) faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7) faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0) faraday (>= 0.8.0)
http-cookie (~> 1.0.0) http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0) faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
faraday-net_http_persistent (1.1.0) faraday-net_http_persistent (1.2.0)
faraday_middleware (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.2.3) fastimage (2.2.6)
fastlane (2.182.0) fastlane (2.203.0)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0) addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0) artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0) aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0) babosa (>= 1.0.3, < 2.0.0)
@ -117,14 +133,16 @@ GEM
faraday_middleware (~> 1.0) faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0) fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0) gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0) google-apis-androidpublisher_v3 (~> 0.3)
google-cloud-storage (>= 1.15.0, < 2.0.0) google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0) highline (~> 2.0)
json (< 3.0.0) json (< 3.0.0)
jwt (>= 2.1.0, < 3) jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0) mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0) multipart-post (~> 2.0.0)
naturally (~> 2.2) naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0) plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0) rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3) security (= 0.1.3)
@ -138,80 +156,76 @@ GEM
xcpretty (~> 0.3.0) xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3) xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-json (1.0.0) fastlane-plugin-json (1.0.0)
fastlane-plugin-sentry (1.8.1) fastlane-plugin-sentry (1.11.0)
fastlane-plugin-versioning_android (0.1.0) fastlane-plugin-versioning_android (0.1.0)
fastlane-plugin-yarn (1.2) fastlane-plugin-yarn (1.2)
ffi (1.15.0) ffi (1.15.4)
fourflusher (2.3.1) fourflusher (2.3.1)
fuzzy_match (2.0.4) fuzzy_match (2.0.4)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-api-client (0.38.0) google-apis-androidpublisher_v3 (0.16.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9) googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.a)
signet (~> 0.12)
google-apis-core (0.3.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
rexml rexml
signet (~> 0.14)
webrick webrick
google-apis-iamcredentials_v1 (0.3.0) google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.3.0) google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0) google-cloud-core (1.6.0)
google-cloud-env (~> 1.0) google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0) google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0) google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0) google-cloud-errors (1.2.0)
google-cloud-storage (1.31.0) google-cloud-storage (1.36.0)
addressable (~> 2.5) addressable (~> 2.8)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1) google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.2) google-cloud-core (~> 1.6)
googleauth (~> 0.9) googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (0.16.2) googleauth (1.1.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
memoist (~> 0.16) memoist (~> 0.16)
multi_json (~> 1.11) multi_json (~> 1.11)
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (~> 0.14) signet (>= 0.16, < 2.a)
highline (2.0.3) highline (2.0.3)
http-cookie (1.0.3) http-cookie (1.0.4)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.9) i18n (1.8.10)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jmespath (1.4.0) jmespath (1.5.0)
json (2.5.1) json (2.6.1)
jwt (2.2.3) jwt (2.3.0)
memoist (0.16.2) memoist (0.16.2)
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.1.0) mini_mime (1.1.2)
minitest (5.14.4) minitest (5.14.4)
molinillo (0.6.6) molinillo (0.8.0)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.0.0) multipart-post (2.0.0)
nanaimo (0.3.0) nanaimo (0.3.0)
nap (1.1.0) nap (1.1.0)
naturally (2.2.1) naturally (2.2.1)
netrc (0.11.0) netrc (0.11.0)
os (1.1.1) optparse (0.1.1)
os (1.1.4)
plist (3.6.0) plist (3.6.0)
public_suffix (4.0.6) public_suffix (4.0.6)
rake (13.0.3) rake (13.0.6)
representable (3.1.1) representable (3.1.1)
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
@ -219,12 +233,12 @@ GEM
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.5) rexml (3.2.5)
rouge (2.0.7) rouge (2.0.7)
ruby-macho (1.4.0) ruby-macho (2.5.1)
ruby2_keywords (0.0.4) ruby2_keywords (0.0.5)
rubyzip (2.3.0) rubyzip (2.3.2)
security (0.1.3) security (0.1.3)
signet (0.15.0) signet (0.16.0)
addressable (~> 2.3) addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)
multi_json (~> 1.10) multi_json (~> 1.10)
@ -234,36 +248,38 @@ GEM
terminal-notifier (2.0.0) terminal-notifier (2.0.0)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6) trailblazer-option (0.1.2)
trailblazer-option (0.1.1)
tty-cursor (0.7.1) tty-cursor (0.7.1)
tty-screen (0.8.1) tty-screen (0.8.1)
tty-spinner (0.9.3) tty-spinner (0.9.3)
tty-cursor (~> 0.7) tty-cursor (~> 0.7)
typhoeus (1.4.0) typhoeus (1.4.0)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.9) tzinfo (2.0.4)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
uber (0.1.0) uber (0.1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.7) unf_ext (0.0.8)
unicode-display_width (1.7.0) unicode-display_width (1.8.0)
webrick (1.7.0) webrick (1.7.0)
word_wrap (1.0.0) word_wrap (1.0.0)
xcodeproj (1.19.0) xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0) CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3) atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
nanaimo (~> 0.3.0) nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0) xcpretty (0.3.0)
rouge (~> 2.0.7) rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7) xcpretty (~> 0.2, >= 0.0.7)
zeitwerk (2.5.1)
PLATFORMS PLATFORMS
universal-darwin-20 universal-darwin-20
universal-darwin-21
DEPENDENCIES DEPENDENCIES
cocoapods cocoapods
@ -274,4 +290,4 @@ DEPENDENCIES
fastlane-plugin-yarn fastlane-plugin-yarn
BUNDLED WITH BUNDLED WITH
2.2.8 2.2.17

View File

@ -5,7 +5,7 @@
* A new app store version has to be submitted. * A new app store version has to be submitted.
* Outdated versions in principle do not receive further OTA updates. * Outdated versions in principle do not receive further OTA updates.
## Minor releases - App Store and OTA ## Minor releases - App Store
"Minor releases" are artifacts published as `?.y.?`: "Minor releases" are artifacts published as `?.y.?`:
* An artifact can be released as `?.y.?` when there is no change nor update made to the native modules. * An artifact can be released as `?.y.?` when there is no change nor update made to the native modules.
@ -21,11 +21,12 @@
## OTA release channels ## OTA release channels
* `MAJOR-environment`. Environments include `release`, `candidate` and `development`. * `MAJOR.MINOR-environment`. Environments include `release`, `candidate` and `development`.
## Major versions mapping to native module versions ## Major versions mapping to native module versions
| Major version | Native module version | Expo version | | Version | Native module version | Expo version |
| :-----------: | :-------------------: | :----------: | | :------:| :-------------------: | :----------: |
| `0` | `210201` | `40.0.0` | | `0-` | `210201` | `40.0.0` |
| `1` | `210317` | `40.0.0` | | `1-` | `210317` | `40.0.0` |
| `2.2` | `210916` | `41.0.0` |

View File

@ -78,13 +78,11 @@ import com.android.build.OutputFile
*/ */
project.ext.react = [ project.ext.react = [
enableHermes: true enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes",
cliPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute().text.trim(), "../cli.js")
] ]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy' apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute().text.trim(), "../react.gradle")
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
/** /**
* Set this to true to create two separate APKs instead of one: * Set this to true to create two separate APKs instead of one:
@ -190,9 +188,39 @@ android {
} }
dependencies { dependencies {
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.3.0") {
force = true
}
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules implementation "com.facebook.react:react-native:+" // From node_modules
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
// If your app supports Android versions before Ice Cream Sandwich (API level 14)
// All fresco packages should use the same version
if (isGifEnabled || isWebpEnabled) {
implementation 'com.facebook.fresco:fresco:2.0.0'
implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.0.0'
}
if (isGifEnabled) {
// For animated gif support
implementation 'com.facebook.fresco:animated-gif:2.0.0'
}
if (isWebpEnabled) {
// For webp support
implementation 'com.facebook.fresco:webpsupport:2.0.0'
if (isWebpAnimatedEnabled) {
// Animated webp support
implementation 'com.facebook.fresco:animated-webp:2.0.0'
}
}
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni' exclude group:'com.facebook.fbjni'
@ -204,12 +232,10 @@ dependencies {
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper' exclude group:'com.facebook.flipper'
} }
addUnimodulesDependencies()
if (enableHermes) { if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute().text.trim(), "../android/hermes-debug.aar"))
debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute().text.trim(), "../android/hermes-release.aar"))
releaseImplementation files(hermesPath + "hermes-release.aar")
} else { } else {
implementation jscFlavor implementation jscFlavor
} }
@ -222,6 +248,7 @@ task copyDownloadableDepsToLibs(type: Copy) {
into 'libs' into 'libs'
} }
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute().text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -1,7 +1,4 @@
package com.xmflsct.app.tooot; package com.xmflsct.app.tooot;
import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
@ -9,30 +6,14 @@ import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView; import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import expo.modules.splashscreen.singletons.SplashScreen; import expo.modules.ReactActivityDelegateWrapper;
import expo.modules.splashscreen.SplashScreenImageResizeMode;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
// Added automatically by Expo Config
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
sendBroadcast(intent);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null); super.onCreate(null);
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView.class, false);
} }
/** /**
* Returns the name of the main component registered from JavaScript. * Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component. * This is used to schedule rendering of the component.
@ -44,11 +25,14 @@ public class MainActivity extends ReactActivity {
@Override @Override
protected ReactActivityDelegate createReactActivityDelegate() { protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) { return new ReactActivityDelegateWrapper(
@Override this,
protected ReactRootView createRootView() { new ReactActivityDelegate(this, getMainComponentName()) {
return new RNGestureHandlerEnabledRootView(MainActivity.this); @Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
} }
}; );
} }
} }

View File

@ -2,7 +2,8 @@ package com.xmflsct.app.tooot;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.content.res.Configuration;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList; import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication; import com.facebook.react.ReactApplication;
@ -11,32 +12,21 @@ import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage; import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import com.xmflsct.app.tooot.generated.BasePackageList;
import org.unimodules.adapters.react.ReactAdapterPackage; import expo.modules.ApplicationLifecycleDispatcher;
import org.unimodules.adapters.react.ModuleRegistryAdapter; import expo.modules.ReactNativeHostWrapper;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import expo.modules.constants.ConstantsPackage;
import expo.modules.permissions.PermissionsPackage;
import expo.modules.filesystem.FileSystemPackage;
import expo.modules.updates.UpdatesController; import expo.modules.updates.UpdatesController;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import com.facebook.react.bridge.JSIModulePackage; // <- react-native-reanimated-v2 import com.facebook.react.bridge.JSIModulePackage; // <- react-native-reanimated-v2
import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- react-native-reanimated-v2 import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- react-native-reanimated-v2
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider( private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
new BasePackageList().getPackageList() this,
); new ReactNativeHost(this) {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override @Override
public boolean getUseDeveloperSupport() { public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG; return BuildConfig.DEBUG;
@ -44,8 +34,10 @@ public class MainApplication extends Application implements ReactApplication {
@Override @Override
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages(); List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider)); // Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages; return packages;
} }
@ -58,25 +50,7 @@ public class MainApplication extends Application implements ReactApplication {
protected JSIModulePackage getJSIModulePackage() { protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage(); return new ReanimatedJSIModulePackage();
} }
});
@Override
protected @Nullable String getJSBundleFile() {
if (BuildConfig.DEBUG) {
return super.getJSBundleFile();
} else {
return UpdatesController.getInstance().getLaunchAssetFile();
}
}
@Override
protected @Nullable String getBundleAssetName() {
if (BuildConfig.DEBUG) {
return super.getBundleAssetName();
} else {
return UpdatesController.getInstance().getBundleAssetName();
}
}
};
@Override @Override
public ReactNativeHost getReactNativeHost() { public ReactNativeHost getReactNativeHost() {
@ -88,11 +62,14 @@ public class MainApplication extends Application implements ReactApplication {
super.onCreate(); super.onCreate();
SoLoader.init(this, /* native exopackage */ false); SoLoader.init(this, /* native exopackage */ false);
if (!BuildConfig.DEBUG) {
UpdatesController.initialize(this);
}
initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
} }
/** /**

View File

@ -1,37 +0,0 @@
package com.xmflsct.app.tooot.generated;
import java.util.Arrays;
import java.util.List;
import org.unimodules.core.interfaces.Package;
public class BasePackageList {
public List<Package> getPackageList() {
return Arrays.<Package>asList(
new expo.modules.application.ApplicationPackage(),
new expo.modules.av.AVPackage(),
new expo.modules.constants.ConstantsPackage(),
new expo.modules.crypto.CryptoPackage(),
new expo.modules.device.DevicePackage(),
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.firebase.analytics.FirebaseAnalyticsPackage(),
new expo.modules.firebase.core.FirebaseCorePackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.haptics.HapticsPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.imagemanipulator.ImageManipulatorPackage(),
new expo.modules.imagepicker.ImagePickerPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.localization.LocalizationPackage(),
new expo.modules.notifications.NotificationsPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.screencapture.ScreenCapturePackage(),
new expo.modules.securestore.SecureStorePackage(),
new expo.modules.splashscreen.SplashScreenPackage(),
new expo.modules.storereview.StoreReviewPackage(),
new expo.modules.updates.UpdatesPackage(),
new expo.modules.videothumbnails.VideoThumbnailsPackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
}

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources> <resources>
<string name="app_name">tooot</string> <string name="app_name">tooot</string>
<string name="expo_splash_screen_resize_mode">contain</string>
<string name="expo_splash_screen_status_bar_translucent">false</string>
</resources> </resources>

View File

@ -2,19 +2,21 @@
buildscript { buildscript {
ext { ext {
buildToolsVersion = "29.0.3" buildToolsVersion = "30.0.2"
minSdkVersion = 21 minSdkVersion = 21
compileSdkVersion = 30 compileSdkVersion = 30
targetSdkVersion = 30 targetSdkVersion = 30
ndkVersion = "20.1.5948944" ndkVersion = "21.4.7075529"
kotlinVersion = '1.5.32'
} }
repositories { repositories {
google() google()
mavenCentral()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath("com.android.tools.build:gradle:4.1.0") classpath("com.android.tools.build:gradle:4.2.0")
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -26,14 +28,15 @@ allprojects {
mavenLocal() mavenLocal()
maven { maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android") url(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute().text.trim(), "../android"))
} }
maven { maven {
// Android JSC is installed from npm // Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist") url(new File(["node", "--print", "require.resolve('jsc-android/package.json')"].execute().text.trim(), "../dist"))
} }
google() google()
mavenCentral()
jcenter() jcenter()
maven { url 'https://www.jitpack.io' } maven { url 'https://www.jitpack.io' }
} }

View File

@ -31,4 +31,16 @@ FLIPPER_VERSION=0.75.1
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
# The hosted JavaScript engine
# Supported values: expo.jsEngine = "hermes" | "jsc"
expo.jsEngine=hermes
# Enable GIF support in React Native images (~200 B increase)
expo.gif.enabled=true
# Enable webp support in React Native images (~85 KB increase)
expo.webp.enabled=true
# Enable animated webp support (~3.4 MB increase)
# Disabled by default because iOS doesn't support animated webp
expo.webp.animated=false

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,9 +1,9 @@
rootProject.name = 'tooot' rootProject.name = 'tooot'
apply from: '../node_modules/react-native-unimodules/gradle.groovy' apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute().text.trim(), "../scripts/autolinking.gradle");
includeUnimodulesProjects() useExpoModules()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute().text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings) applyNativeModulesSettingsGradle(settings)
include ':app' include ':app'

View File

@ -8,13 +8,13 @@ export default (): ExpoConfig => ({
name: 'tooot', name: 'tooot',
description: 'tooot for Mastodon', description: 'tooot for Mastodon',
slug: 'tooot', slug: 'tooot',
scheme: 'tooot',
version: toootVersion, version: toootVersion,
sdkVersion: versions.expo,
privacy: 'hidden', privacy: 'hidden',
assetBundlePatterns: ['assets/*'], assetBundlePatterns: ['assets/*'],
extra: { extra: {
sentryDSN: process.env.SENTRY_DSN, sentryDSN: process.env.SENTRY_DSN,
toootApiKey: process.env.TOOOT_API_KEY toootPushKeyPublic: process.env.TOOOT_PUSH_KEY_PUBLIC
}, },
hooks: { hooks: {
postPublish: [ postPublish: [
@ -30,11 +30,11 @@ export default (): ExpoConfig => ({
} }
] ]
}, },
jsEngine: 'hermes',
ios: { ios: {
bundleIdentifier: 'com.xmflsct.app.tooot' bundleIdentifier: 'com.xmflsct.app.tooot'
}, },
android: { android: {
versionCode: 4,
package: 'com.xmflsct.app.tooot', package: 'com.xmflsct.app.tooot',
googleServicesFile: './configs/google-services.json', googleServicesFile: './configs/google-services.json',
permissions: ['CAMERA', 'VIBRATE'] permissions: ['CAMERA', 'VIBRATE']

View File

@ -1,4 +1,4 @@
fastlane_version "2.180.1" fastlane_version "2.203.0"
skip_docs skip_docs
ensure_env_vars( ensure_env_vars(
@ -8,7 +8,7 @@ ensure_env_vars(
VERSIONS = read_json( json_path: "./package.json" )[:versions] VERSIONS = read_json( json_path: "./package.json" )[:versions]
ENVIRONMENT = ENV["ENVIRONMENT"] ENVIRONMENT = ENV["ENVIRONMENT"]
VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}" VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}"
RELEASE_CHANNEL = "#{VERSIONS[:major]}-#{ENVIRONMENT}" RELEASE_CHANNEL = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}-#{ENVIRONMENT}"
BUILD_NUMBER = "#{Time.now.strftime("%y%m%d")}#{ENV["GITHUB_RUN_NUMBER"]}" BUILD_NUMBER = "#{Time.now.strftime("%y%m%d")}#{ENV["GITHUB_RUN_NUMBER"]}"
GITHUB_REPO = "tooot-app/app" GITHUB_REPO = "tooot-app/app"
case ENVIRONMENT case ENVIRONMENT
@ -91,6 +91,7 @@ private_lane :build_ios do
dsym_path: DSYM_FILE dsym_path: DSYM_FILE
) )
upload_to_testflight( upload_to_testflight(
skip_submission: true,
ipa: IPA_FILE, ipa: IPA_FILE,
demo_account_required: true, demo_account_required: true,
distribute_external: true, distribute_external: true,
@ -107,6 +108,11 @@ private_lane :build_ios do
silent: true silent: true
) )
upload_to_app_store( ipa: IPA_FILE, app_version: VERSION ) upload_to_app_store( ipa: IPA_FILE, app_version: VERSION )
download_dsyms( version: VERSION, build_number: BUILD_NUMBER, wait_for_dsym_processing: true )
sentry_upload_dsym(
org_slug: ENV["SENTRY_ORGANIZATION"],
project_slug: ENV["SENTRY_PROJECT"],
)
else else
if !is_ci if !is_ci
match( type: "adhoc", readonly: true ) match( type: "adhoc", readonly: true )

View File

@ -1,2 +1 @@
Added translation option, translation service is provided by various providers Support iPad
When updating profile, now avatar and banner can be uploaded

View File

@ -1,6 +1,7 @@
tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象https://joinmastodon.org/)账号。 tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象https://joinmastodon.org/)账号。
tooot支持 tooot支持
- iPad
- 多账号登录 - 多账号登录
- 黑暗或自适应模式 - 黑暗或自适应模式
- 可调整正文字体大小 - 可调整正文字体大小

View File

@ -1,2 +1 @@
加入翻译嘟文支持,翻译服务由多个服务商提供 添加支持iPad
修改个人信息里可以上传头像及横幅

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

View File

@ -2,7 +2,7 @@
// File.swift // File.swift
// tooot // tooot
// //
// Created by Zheng Zhiyuan (SEBD) on 2021-03-15. // Created by Zhiyuan Zheng on 2021-08-22.
// //
import Foundation import Foundation

View File

@ -1,17 +1,19 @@
require_relative '../node_modules/react-native/scripts/react_native_pods' require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require_relative '../node_modules/react-native-unimodules/cocoapods.rb' require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules")
platform :ios, '11.0' platform :ios, '12.0'
require 'json'
podfile_properties = JSON.parse(File.read('./Podfile.properties.json')) rescue {}
target 'tooot' do target 'tooot' do
use_unimodules! use_expo_modules!
config = use_native_modules! config = use_native_modules!
use_react_native!( use_react_native!(
:path => config[:reactNativePath], :path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods :hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes'
:hermes_enabled => true
) )
# Enables Flipper. # Enables Flipper.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
{
"expo.jsEngine": "hermes"
}

View File

@ -1,3 +1,4 @@
#import <Expo/Expo.h>
// //
// Use this file to import your target's public headers that you would like to expose to Swift. // Use this file to import your target's public headers that you would like to expose to Swift.
// //

View File

@ -18,6 +18,7 @@
96905EF65AED1B983A6B3ABC /* libPods-tooot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */; }; 96905EF65AED1B983A6B3ABC /* libPods-tooot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
DA8B5B7F0DED488CAC0FF169 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */; }; DA8B5B7F0DED488CAC0FF169 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */; };
E3BC22F5F8ABE515E14CF199 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D878F932AF7A9974E06E461 /* ExpoModulesProvider.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -37,6 +38,7 @@
5EE44DD52600124E00A9BCED /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; }; 5EE44DD52600124E00A9BCED /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.debug.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.debug.xcconfig"; sourceTree = "<group>"; }; 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.debug.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.debug.xcconfig"; sourceTree = "<group>"; };
7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.release.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.release.xcconfig"; sourceTree = "<group>"; }; 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.release.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.release.xcconfig"; sourceTree = "<group>"; };
9D878F932AF7A9974E06E461 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-tooot/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; };
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = "<group>"; }; B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
@ -75,6 +77,14 @@
name = tooot; name = tooot;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
1568DA5289D5AE7A39201A34 /* tooot */ = {
isa = PBXGroup;
children = (
9D878F932AF7A9974E06E461 /* ExpoModulesProvider.swift */,
);
name = tooot;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = { 2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -85,6 +95,14 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
2FFEB4B0D00502D5425CDDC2 /* ExpoModulesProviders */ = {
isa = PBXGroup;
children = (
1568DA5289D5AE7A39201A34 /* tooot */,
);
name = ExpoModulesProviders;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = { 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -102,6 +120,7 @@
2D16E6871FA4F8E400B85C8A /* Frameworks */, 2D16E6871FA4F8E400B85C8A /* Frameworks */,
D65327D7A22EEC0BE12398D9 /* Pods */, D65327D7A22EEC0BE12398D9 /* Pods */,
5EE44DD42600124E00A9BCED /* tooot-Bridging-Header.h */, 5EE44DD42600124E00A9BCED /* tooot-Bridging-Header.h */,
2FFEB4B0D00502D5425CDDC2 /* ExpoModulesProviders */,
); );
indentWidth = 2; indentWidth = 2;
sourceTree = "<group>"; sourceTree = "<group>";
@ -148,7 +167,7 @@
13B07F8E1A680F5B00A75B9A /* Resources */, 13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
5C5B41FC5F9DBE367CF7EF21 /* [CP] Embed Pods Frameworks */, 49D30A53634620EF2A5C6692 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -165,7 +184,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = { 83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1130; LastUpgradeCheck = 1320;
TargetAttributes = { TargetAttributes = {
13B07F861A680F5B00A75B9A = { 13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8EGBLQ2MA6; DevelopmentTeam = 8EGBLQ2MA6;
@ -247,14 +266,14 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
5C5B41FC5F9DBE367CF7EF21 /* [CP] Embed Pods Frameworks */ = { 49D30A53634620EF2A5C6692 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh",
"${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/iphoneos/hermes.framework", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
@ -272,10 +291,14 @@
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh", "${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
); );
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -312,6 +335,7 @@
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
5EE44DD62600124E00A9BCED /* File.swift in Sources */, 5EE44DD62600124E00A9BCED /* File.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */,
E3BC22F5F8ABE515E14CF199 /* ExpoModulesProvider.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -357,7 +381,7 @@
"FB_SONARKIT_ENABLED=1", "FB_SONARKIT_ENABLED=1",
); );
INFOPLIST_FILE = tooot/Info.plist; INFOPLIST_FILE = tooot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
@ -367,10 +391,12 @@
PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot; PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot;
PRODUCT_NAME = tooot; PRODUCT_NAME = tooot;
PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.xmflsct.app.tooot"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.xmflsct.app.tooot";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "tooot-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "tooot-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Debug; name = Debug;
@ -388,7 +414,7 @@
DEVELOPMENT_TEAM = 8EGBLQ2MA6; DEVELOPMENT_TEAM = 8EGBLQ2MA6;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = tooot/Info.plist; INFOPLIST_FILE = tooot/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
@ -398,9 +424,11 @@
PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot; PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot;
PRODUCT_NAME = tooot; PRODUCT_NAME = tooot;
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.xmflsct.app.tooot"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.xmflsct.app.tooot";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "tooot-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "tooot-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Release; name = Release;
@ -428,6 +456,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -454,7 +483,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
@ -490,6 +519,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -509,7 +539,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
@ -518,6 +548,7 @@
); );
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1210" LastUpgradeVersion = "1320"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -1,10 +1,9 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <EXUpdates/EXUpdatesAppController.h>
#import <React/RCTBridgeDelegate.h> #import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <UMCore/UMAppDelegateWrapper.h> #import <Expo/Expo.h>
@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate> @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate>
@end @end

View File

@ -4,12 +4,7 @@
#import <React/RCTBundleURLProvider.h> #import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h> #import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h> #import <React/RCTLinkingManager.h>
#import <React/RCTConvert.h>
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <EXSplashScreen/EXSplashScreenService.h>
#import <UMCore/UMModuleRegistryProvider.h>
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>) #if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
#import <FlipperKit/FlipperClient.h> #import <FlipperKit/FlipperClient.h>
@ -19,8 +14,6 @@
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h> #import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h> #import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <React/RCTLinkingManager.h>
// iOS 9.x or newer // iOS 9.x or newer
- (BOOL)application:(UIApplication *)application - (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url openURL:(NSURL *)url
@ -50,13 +43,6 @@ static void InitializeFlipper(UIApplication *application) {
} }
#endif #endif
@interface AppDelegate () <RCTBridgeDelegate>
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
@property (nonatomic, strong) NSDictionary *launchOptions;
@end
@implementation AppDelegate @implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
@ -65,57 +51,34 @@ static void InitializeFlipper(UIApplication *application) {
InitializeFlipper(application); InitializeFlipper(application);
#endif #endif
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]]; RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
self.launchOptions = launchOptions; RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
rootView.backgroundColor = [UIColor colorNamed:@"SplashScreenBackgroundColor"];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#ifdef DEBUG UIViewController *rootViewController = [self.reactDelegate createRootViewController];
[self initializeReactNativeApp]; rootViewController.view = rootView;
#else self.window.rootViewController = rootViewController;
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance]; [self.window makeKeyAndVisible];
controller.delegate = self;
[controller startAndShowLaunchScreen:self.window];
#endif
[super application:application didFinishLaunchingWithOptions:launchOptions]; [super application:application didFinishLaunchingWithOptions:launchOptions];
return YES; return YES;
} }
- (RCTBridge *)initializeReactNativeApp
{
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
rootView.backgroundColor = [UIColor colorNamed:@"Background"];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return bridge;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{ {
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here! // If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
return extraModules; return @[];
} }
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG #ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else #else
return [[EXUpdatesAppController sharedInstance] launchAssetUrl]; return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif #endif
} }
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
appController.bridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
// Linking API // Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options]; return [RCTLinkingManager application:application openURL:url options:options];

View File

@ -1,6 +1,6 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

View File

@ -1,52 +0,0 @@
{
"images": [
{
"idiom": "universal",
"filename": "background.png",
"scale": "1x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"filename": "dark_background.png",
"scale": "1x"
},
{
"idiom": "universal",
"scale": "2x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "2x"
},
{
"idiom": "universal",
"scale": "3x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "3x"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "250",
"green" : "250",
"red" : "250"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "18",
"green" : "18",
"red" : "18"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -37,6 +37,8 @@
<string>2102022230</string> <string>2102022230</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
@ -50,12 +52,14 @@
</dict> </dict>
</dict> </dict>
</dict> </dict>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) DOES NOT need microphone permission. Please reject this request.</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to capture photo or video and attach it to your toot</string> <string>Allow $(PRODUCT_NAME) to capture photo or video and attach it to your toot</string>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string/> <string></string>
<key>NSMainNibFile</key>
<string>LaunchScreen</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) DOES NOT need microphone permission. Please reject this request.</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to save an image to your camera roll</string> <string>Allow $(PRODUCT_NAME) to save an image to your camera roll</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>

View File

@ -1,91 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" <device id="retina5_9" orientation="portrait" appearance="light"/>
version="3.0" <dependencies>
toolsVersion="16096" <deployment identifier="iOS"/>
targetRuntime="iOS.CocoaTouch" <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
propertyAccessControl="none" <capability name="Named colors" minToolsVersion="9.0"/>
useAutolayout="YES" <capability name="Safe area layout guides" minToolsVersion="9.0"/>
launchScreen="YES" <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
useTraitCollections="YES" </dependencies>
useSafeAreas="YES" <scenes>
colorMatched="YES" <!--View Controller-->
initialViewController="EXPO-VIEWCONTROLLER-1" <scene sceneID="EXPO-SCENE-1">
> <objects>
<device id="retina5_5" orientation="portrait" appearance="light"/> <viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
<dependencies> <view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
<deployment identifier="iOS"/> <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <subviews>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="SplashScreen" translatesAutoresizingMaskIntoConstraints="NO" id="EXPO-SplashScreen" userLabel="SplashScreen">
</dependencies> <rect key="frame" x="-55" y="-217" width="485" height="1246"/>
<scenes> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<!--View Controller--> <rect key="contentStretch" x="0.0" y="0.0" width="0.0" height="0.0"/>
<scene sceneID="EXPO-SCENE-1"> </imageView>
<objects> </subviews>
<viewController <viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
storyboardIdentifier="SplashScreenViewController" <color key="backgroundColor" name="SplashScreenBackgroundColor"/>
id="EXPO-VIEWCONTROLLER-1" </view>
sceneMemberID="viewController" </viewController>
> <placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
<view </objects>
key="view" <point key="canvasLocation" x="140" y="129.38388625592415"/>
userInteractionEnabled="NO" </scene>
contentMode="scaleToFill" </scenes>
insetsLayoutMarginsFromSafeArea="NO" <resources>
id="EXPO-ContainerView" <image name="SplashScreen" width="1242" height="2436"/>
userLabel="ContainerView" <namedColor name="SplashScreenBackgroundColor">
> <color red="0.98039215686274506" green="0.98039215686274506" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/> </namedColor>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> </resources>
<subviews>
<imageView
userInteractionEnabled="NO"
contentMode="scaleAspectFill"
horizontalHuggingPriority="251"
verticalHuggingPriority="251"
insetsLayoutMarginsFromSafeArea="NO"
image="SplashScreenBackground"
translatesAutoresizingMaskIntoConstraints="NO"
id="EXPO-SplashScreenBackground"
userLabel="SplashScreenBackground"
>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
</imageView>
<imageView
clipsSubviews="YES"
userInteractionEnabled="NO"
contentMode="scaleAspectFit"
horizontalHuggingPriority="251"
verticalHuggingPriority="251"
translatesAutoresizingMaskIntoConstraints="NO"
image="SplashScreen"
id="EXPO-SplashScreen"
userLabel="SplashScreen"
>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="1gX-mQ-vu6"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="6tX-OG-Sck"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="ABX-8g-7v4"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="jkI-2V-eW5"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="2VS-Uz-0LU"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="LhH-Ei-DKo"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="I6l-TP-6fn"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="nbp-HC-eaG"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="140.625" y="129.4921875"/>
</scene>
</scenes>
<resources>
<image name="SplashScreen" width="414" height="736"/>
<image name="SplashScreenBackground" width="1" height="1"/>
</resources>
</document> </document>

View File

@ -11,7 +11,7 @@
<key>EXUpdatesReleaseChannel</key> <key>EXUpdatesReleaseChannel</key>
<string>0-development</string> <string>0-development</string>
<key>EXUpdatesSDKVersion</key> <key>EXUpdatesSDKVersion</key>
<string>40.0.0</string> <string>0</string>
<key>EXUpdatesURL</key> <key>EXUpdatesURL</key>
<string>https://exp.host/@xmflsct/tooot</string> <string>https://exp.host/@xmflsct/tooot</string>
</dict> </dict>

View File

@ -0,0 +1,4 @@
//
// @generated
// A blank Swift file must be created for native modules with Swift files to work correctly.
//

View File

@ -1,8 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>development</string>
</dict> <key>com.apple.security.app-sandbox</key>
</plist> <true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
</plist>

View File

@ -1,11 +1,11 @@
{ {
"name": "tooot", "name": "tooot",
"versions": { "versions": {
"native": "210511", "native": "220204",
"major": 2, "major": 3,
"minor": 1, "minor": 3,
"patch": 2, "patch": 0,
"expo": "41.0.0" "expo": "44.0.0"
}, },
"description": "tooot app for Mastodon", "description": "tooot app for Mastodon",
"author": "xmflsct <me@xmflsct.com>", "author": "xmflsct <me@xmflsct.com>",
@ -17,110 +17,103 @@
"scripts": { "scripts": {
"start": "react-native start", "start": "react-native start",
"android": "react-native run-android", "android": "react-native run-android",
"ios": "react-native run-ios", "iphone": "react-native run-ios",
"ipad": "react-native run-ios --simulator 'iPad mini (6th generation)'",
"app:build": "bundle exec fastlane build", "app:build": "bundle exec fastlane build",
"test": "jest --watchAll",
"release": "scripts/release.sh", "release": "scripts/release.sh",
"clean": "react-native-clean-project" "clean": "react-native-clean-project",
"postinstall": "patch-package"
}, },
"dependencies": { "dependencies": {
"@expo/react-native-action-sheet": "^3.9.0", "@expo/react-native-action-sheet": "3.13.0",
"@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
"@react-native-async-storage/async-storage": "^1.15.4", "@react-native-async-storage/async-storage": "1.15.17",
"@react-native-community/blur": "^3.6.0", "@react-native-community/blur": "3.6.0",
"@react-native-community/cameraroll": "^4.0.4", "@react-native-community/cameraroll": "4.1.2",
"@react-native-community/masked-view": "0.1.11", "@react-native-community/netinfo": "7.1.9",
"@react-native-community/netinfo": "6.0.0",
"@react-native-community/segmented-control": "2.2.2", "@react-native-community/segmented-control": "2.2.2",
"@react-navigation/bottom-tabs": "^5.11.11", "@react-navigation/bottom-tabs": "6.2.0",
"@react-navigation/native": "^5.9.4", "@react-navigation/native": "6.0.8",
"@react-navigation/stack": "^5.14.5", "@react-navigation/native-stack": "6.4.1",
"@reduxjs/toolkit": "^1.5.1", "@react-navigation/stack": "6.1.1",
"@sentry/react-native": "^2.4.3", "@reduxjs/toolkit": "1.7.2",
"@sharcoux/slider": "^5.3.0", "@sentry/react-native": "3.2.13",
"axios": "^0.21.1", "@sharcoux/slider": "5.6.4",
"expo": "^41.0.1", "axios": "0.24.0",
"expo-auth-session": "~3.2.3", "expo": "44.0.6",
"expo-av": "~9.1.2", "expo-auth-session": "3.5.0",
"expo-crypto": "~9.1.0", "expo-av": "10.2.1",
"expo-firebase-analytics": "~4.0.2", "expo-constants": "^13.0.2",
"expo-haptics": "~10.0.0", "expo-crypto": "10.1.2",
"expo-image-manipulator": "~9.1.0", "expo-device": "4.1.1",
"expo-image-picker": "~10.1.4", "expo-file-system": "13.2.0",
"expo-linking": "~2.2.3", "expo-firebase-analytics": "6.0.1",
"expo-localization": "~10.1.0", "expo-haptics": "11.1.1",
"expo-notifications": "~0.11.6", "expo-image-manipulator": "10.2.1",
"expo-random": "~11.1.2", "expo-image-picker": "12.0.2",
"expo-screen-capture": "^3.1.0", "expo-linking": "3.0.0",
"expo-secure-store": "~10.1.0", "expo-localization": "12.0.1",
"expo-splash-screen": "~0.10.2", "expo-notifications": "0.14.1",
"expo-status-bar": "~1.0.4", "expo-random": "12.1.2",
"expo-store-review": "~4.0.2", "expo-screen-capture": "4.1.1",
"expo-video-thumbnails": "~5.1.0", "expo-secure-store": "11.1.1",
"expo-web-browser": "~9.1.0", "expo-splash-screen": "0.14.2",
"i18next": "^20.3.0", "expo-store-review": "5.1.1",
"li": "^1.3.0", "expo-updates": "0.11.3",
"lodash": "^4.17.21", "expo-video-thumbnails": "6.2.0",
"expo-web-browser": "10.1.1",
"i18next": "20.6.1",
"li": "1.3.0",
"lodash": "4.17.21",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-i18next": "^11.9.0", "react-i18next": "11.15.3",
"react-native": "~0.64.1", "react-native": "0.66.4",
"react-native-animated-spinkit": "^1.5.2", "react-native-animated-spinkit": "1.5.2",
"react-native-blurhash": "^1.1.4", "react-native-base64": "^0.2.1",
"react-native-fast-image": "^8.3.4", "react-native-blurhash": "1.1.8",
"react-native-feather": "^1.0.2", "react-native-fast-image": "8.5.11",
"react-native-flash-message": "^0.1.23", "react-native-feather": "1.1.2",
"react-native-gesture-handler": "~1.10.3", "react-native-flash-message": "0.2.1",
"react-native-htmlview": "^0.16.0", "react-native-gesture-handler": "2.2.0",
"react-native-pager-view": "5.1.9", "react-native-htmlview": "0.16.0",
"react-native-reanimated": "~2.1.0", "react-native-pager-view": "5.4.9",
"react-native-safe-area-context": "3.2.0", "react-native-reanimated": "2.3.1",
"react-native-screens": "~3.3.0", "react-native-safe-area-context": "3.3.2",
"react-native-screens": "3.10.2",
"react-native-svg": "12.1.1", "react-native-svg": "12.1.1",
"react-native-swipe-list-view": "^3.2.7", "react-native-swipe-list-view": "3.2.9",
"react-native-tab-view": "^3.0.1", "react-native-tab-view": "3.1.1",
"react-native-unimodules": "~0.13.3", "react-query": "3.34.12",
"react-query": "^3.16.0", "react-redux": "7.2.6",
"react-redux": "^7.2.4", "react-timeago": "6.2.1",
"react-timeago": "^5.2.0", "redux-persist": "6.0.0",
"redux-persist": "^6.0.0", "rn-placeholder": "3.0.3",
"rn-placeholder": "^3.0.3", "sentry-expo": "4.0.3",
"sentry-expo": "^3.1.3", "tslib": "2.3.1",
"tslib": "^2.2.0", "valid-url": "1.0.9"
"valid-url": "^1.0.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "~7.14.3", "@babel/core": "7.17.0",
"@babel/plugin-proposal-optional-chaining": "^7.14.2", "@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/preset-typescript": "^7.13.0", "@babel/preset-typescript": "7.16.7",
"@expo/config": "^3.3.43", "@expo/config": "6.0.16",
"@jest/types": "^26.6.2", "@types/lodash": "4.14.178",
"@testing-library/jest-native": "^4.0.1", "@types/react": "17.0.39",
"@testing-library/react-hooks": "^5.1.2", "@types/react-dom": "17.0.11",
"@testing-library/react-native": "^7.2.0", "@types/react-native": "0.66.15",
"@types/jest": "^26.0.23", "@types/react-native-base64": "^0.2.0",
"@types/lodash": "^4.14.170", "@types/react-redux": "7.1.22",
"@types/react": "~17.0.8", "@types/react-timeago": "4.1.3",
"@types/react-dom": "~17.0.5", "@types/valid-url": "1.0.3",
"@types/react-native": "~0.64.6", "@welldone-software/why-did-you-render": "6.2.3",
"@types/react-navigation": "^3.4.0", "babel-plugin-module-resolver": "4.1.0",
"@types/react-redux": "^7.1.16", "babel-plugin-transform-remove-console": "6.9.4",
"@types/react-test-renderer": "^17.0.1", "chalk": "4.1.2",
"@types/react-timeago": "^4.1.2", "dotenv": "16.0.0",
"@types/valid-url": "^1.0.3", "patch-package": "6.4.7",
"@welldone-software/why-did-you-render": "^6.1.4", "postinstall-postinstall": "2.1.0",
"babel-jest": "~26.6.3", "react-native-clean-project": "4.0.0",
"babel-plugin-module-resolver": "^4.1.0", "typescript": "4.5.5"
"babel-plugin-transform-remove-console": "^6.9.4",
"chalk": "^4.1.1",
"dotenv": "^10.0.0",
"jest": "^26.6.3",
"jest-expo": "^41.0.0",
"nock": "^13.0.11",
"react-native-clean-project": "^3.6.4",
"react-navigation": "^4.4.4",
"react-navigation-stack": "^2.10.4",
"react-test-renderer": "^17.0.2",
"typescript": "~4.2.4"
} }
} }

View File

@ -0,0 +1,81 @@
diff --git a/node_modules/expo-av/ios/EXAV/EXAV.m b/node_modules/expo-av/ios/EXAV/EXAV.m
index d255852..edf934f 100644
--- a/node_modules/expo-av/ios/EXAV/EXAV.m
+++ b/node_modules/expo-av/ios/EXAV/EXAV.m
@@ -63,7 +63,7 @@ NSString *const EXDidUpdateMetadataEventName = @"didUpdateMetadata";
@property (nonatomic, assign) BOOL audioRecorderShouldBeginRecording;
@property (nonatomic, assign) int audioRecorderDurationMillis;
-@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
+@property (nonatomic, weak) EXModuleRegistry *expoModuleRegistry;
@property (nonatomic, weak) id<EXPermissionsInterface> permissionsManager;
@end
@@ -106,7 +106,7 @@ EX_EXPORT_MODULE(ExponentAV);
- (void)installJsiBindings
{
- id<EXJavaScriptContextProvider> jsContextProvider = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXJavaScriptContextProvider)];
+ id<EXJavaScriptContextProvider> jsContextProvider = [_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXJavaScriptContextProvider)];
void *jsRuntimePtr = [jsContextProvider javaScriptRuntimePointer];
if (jsRuntimePtr) {
[self installJSIBindingsForRuntime:jsRuntimePtr withSoundDictionary:_soundDictionary];
@@ -131,16 +131,16 @@ EX_EXPORT_MODULE(ExponentAV);
#pragma mark - Expo experience lifecycle
-- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
+- (void)setModuleRegistry:(EXModuleRegistry *)expoModuleRegistry
{
- [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] unregisterAppLifecycleListener:self];
- _moduleRegistry = moduleRegistry;
- _kernelAudioSessionManagerDelegate = [_moduleRegistry getSingletonModuleForName:@"AudioSessionManager"];
+ [[_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] unregisterAppLifecycleListener:self];
+ _expoModuleRegistry = expoModuleRegistry;
+ _kernelAudioSessionManagerDelegate = [_expoModuleRegistry getSingletonModuleForName:@"AudioSessionManager"];
if (!_isBackgrounded) {
[_kernelAudioSessionManagerDelegate moduleDidForeground:self];
}
- [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] registerAppLifecycleListener:self];
- _permissionsManager = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXPermissionsInterface)];
+ [[_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] registerAppLifecycleListener:self];
+ _permissionsManager = [_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXPermissionsInterface)];
[EXPermissionsMethodsDelegate registerRequesters:@[[EXAudioRecordingPermissionRequester new]] withPermissionsManager:_permissionsManager];
}
@@ -478,7 +478,7 @@ withEXVideoViewForTag:(nonnull NSNumber *)reactTag
{
// TODO check that the bridge is still valid after the dispatch
// TODO check if the queues are ok
- [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXUIManager)] executeUIBlock:^(id view) {
+ [[_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXUIManager)] executeUIBlock:^(id view) {
if ([view isKindOfClass:[EXVideoView class]]) {
block(view);
} else {
@@ -566,7 +566,7 @@ withEXVideoViewForTag:(nonnull NSNumber *)reactTag
return EXErrorWithMessage(@"Recorder is already prepared.");
}
- id<EXFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXFileSystemInterface)];
+ id<EXFileSystemInterface> fileSystem = [_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXFileSystemInterface)];
if (!fileSystem) {
return EXErrorWithMessage(@"No FileSystem module.");
@@ -726,7 +726,7 @@ EX_EXPORT_METHOD_AS(loadForSound,
- (void)sendEventWithName:(NSString *)eventName body:(NSDictionary *)body
{
- [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)] sendEventWithName:eventName body:body];
+ [[_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)] sendEventWithName:eventName body:body];
}
EX_EXPORT_METHOD_AS(unloadForSound,
@@ -984,7 +984,7 @@ EX_EXPORT_METHOD_AS(unloadAudioRecorder,
- (void)dealloc
{
[_kernelAudioSessionManagerDelegate moduleWillDeallocate:self];
- [[_moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] unregisterAppLifecycleListener:self];
+ [[_expoModuleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)] unregisterAppLifecycleListener:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
// This will clear all @properties and deactivate the audio session:

View File

@ -0,0 +1,166 @@
diff --git a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
index 53bf40f..0ba5d89 100644
--- a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
+++ b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
@@ -56,6 +56,7 @@ import okhttp3.Callback
import okhttp3.Headers
import okhttp3.JavaNetCookieJar
import okhttp3.MediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -63,11 +64,7 @@ import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.ResponseBody
-import okio.Buffer
-import okio.BufferedSource
-import okio.ForwardingSource
-import okio.Okio
-import okio.Source
+import okio.*
import org.apache.commons.codec.binary.Hex
import org.apache.commons.codec.digest.DigestUtils
@@ -766,7 +763,7 @@ open class FileSystemModule(
}
val body = createRequestBody(options, decorator, fileUri.toFile())
- return requestBuilder.method(method, body).build()
+ return method?.let { requestBuilder.method(it, body).build() }
} catch (e: Exception) {
e.message?.let { Log.e(TAG, it) }
promise.reject(e)
@@ -791,7 +788,7 @@ open class FileSystemModule(
} ?: URLConnection.guessContentTypeFromName(file.name)
val fieldName = options["fieldName"]?.let { it as String } ?: file.name
- bodyBuilder.addFormDataPart(fieldName, file.name, decorator.decorate(RequestBody.create(MediaType.parse(mimeType), file)))
+ bodyBuilder.addFormDataPart(fieldName, file.name, decorator.decorate(RequestBody.create(mimeType.toMediaTypeOrNull(), file)))
bodyBuilder.build()
}
else -> {
@@ -816,9 +813,9 @@ open class FileSystemModule(
override fun onResponse(call: Call, response: Response) {
val result = Bundle().apply {
- putString("body", response.body()?.string())
- putInt("status", response.code())
- putBundle("headers", translateHeaders(response.headers()))
+ putString("body", response.body?.string())
+ putInt("status", response.code)
+ putBundle("headers", translateHeaders(response.headers))
}
response.close()
promise.resolve(result)
@@ -866,7 +863,7 @@ open class FileSystemModule(
taskHandlers[uuid] = TaskHandler(call)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
- if (call.isCanceled) {
+ if (call.isCanceled()) {
promise.resolve(null)
return
}
@@ -876,11 +873,11 @@ open class FileSystemModule(
override fun onResponse(call: Call, response: Response) {
val result = Bundle()
- val body = response.body()
+ val body = response.body
result.apply {
putString("body", body?.string())
- putInt("status", response.code())
- putBundle("headers", translateHeaders(response.headers()))
+ putInt("status", response.code)
+ putBundle("headers", translateHeaders(response.headers))
}
response.close()
promise.resolve(result)
@@ -900,10 +897,10 @@ open class FileSystemModule(
val resources = context.resources
val packageName = context.packageName
val resourceId = resources.getIdentifier(url, "raw", packageName)
- val bufferedSource = Okio.buffer(Okio.source(context.resources.openRawResource(resourceId)))
+ val bufferedSource = context.resources.openRawResource(resourceId).source().buffer()
val file = uri.toFile()
file.delete()
- val sink = Okio.buffer(Okio.sink(file))
+ val sink = file.sink().buffer()
sink.writeAll(bufferedSource)
sink.close()
val result = Bundle()
@@ -934,13 +931,13 @@ open class FileSystemModule(
override fun onResponse(call: Call, response: Response) {
val file = uri.toFile()
file.delete()
- val sink = Okio.buffer(Okio.sink(file))
- sink.writeAll(response.body()!!.source())
+ val sink = file.sink().buffer()
+ sink.writeAll(response.body!!.source())
sink.close()
val result = Bundle().apply {
putString("uri", Uri.fromFile(file).toString())
- putInt("status", response.code())
- putBundle("headers", translateHeaders(response.headers()))
+ putInt("status", response.code)
+ putBundle("headers", translateHeaders(response.headers))
if (options?.get("md5") == true) {
putString("md5", md5(file))
}
@@ -1003,7 +1000,7 @@ open class FileSystemModule(
?.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
- .body(ProgressResponseBody(originalResponse.body(), progressListener))
+ .body(ProgressResponseBody(originalResponse.body, progressListener))
.build()
}
?.build()
@@ -1098,7 +1095,7 @@ open class FileSystemModule(
val options = params[0]?.options
return try {
val response = call!!.execute()
- val responseBody = response.body()
+ val responseBody = response.body
val input = BufferedInputStream(responseBody!!.byteStream())
val output = FileOutputStream(file, isResume == true)
val data = ByteArray(1024)
@@ -1108,15 +1105,15 @@ open class FileSystemModule(
}
val result = Bundle().apply {
putString("uri", Uri.fromFile(file).toString())
- putInt("status", response.code())
- putBundle("headers", translateHeaders(response.headers()))
+ putInt("status", response.code)
+ putBundle("headers", translateHeaders(response.headers))
options?.get("md5").takeIf { it == true }?.let { putString("md5", file?.let { md5(it) }) }
}
response.close()
promise?.resolve(result)
null
} catch (e: Exception) {
- if (call?.isCanceled == true) {
+ if (call?.isCanceled() == true) {
promise?.resolve(null)
return null
}
@@ -1139,7 +1136,7 @@ open class FileSystemModule(
override fun contentLength(): Long = responseBody?.contentLength() ?: -1
override fun source(): BufferedSource =
- bufferedSource ?: Okio.buffer(source(responseBody!!.source()))
+ bufferedSource ?: source(responseBody!!.source()).buffer()
private fun source(source: Source): Source {
return object : ForwardingSource(source) {
@@ -1304,7 +1301,7 @@ open class FileSystemModule(
// Copied out of React Native's `NetworkingModule.java`
private fun translateHeaders(headers: Headers): Bundle {
val responseHeaders = Bundle()
- for (i in 0 until headers.size()) {
+ for (i in 0 until headers.size) {
val headerName = headers.name(i)
// multiple values for the same header
if (responseHeaders[headerName] != null) {

View File

@ -0,0 +1,106 @@
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Crypto.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Crypto.kt
index 69d5957..ec9858c 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Crypto.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Crypto.kt
@@ -47,7 +47,7 @@ object Crypto {
override fun onResponse(call: Call, response: Response) {
val exception: Exception = try {
val isValid = verifyPublicRSASignature(
- response.body()!!.string(), plainText, cipherText
+ response.body!!.string(), plainText, cipherText
)
listener.onCompleted(isValid)
return
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt
index 530a259..3c70674 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt
@@ -14,6 +14,7 @@ import expo.modules.updates.manifest.ManifestHeaderData
import expo.modules.updates.manifest.UpdateManifest
import expo.modules.updates.selectionpolicy.SelectionPolicies
import okhttp3.*
+import okhttp3.Headers.Companion.toHeaders
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@@ -65,14 +66,14 @@ open class FileDownloader(context: Context) {
if (!response.isSuccessful) {
callback.onFailure(
Exception(
- "Network request failed: " + response.body()!!
+ "Network request failed: " + response.body!!
.string()
)
)
return
}
try {
- response.body()!!.byteStream().use { inputStream ->
+ response.body!!.byteStream().use { inputStream ->
val hash = UpdatesUtils.sha256AndWriteToFile(inputStream, destination)
callback.onSuccess(destination, hash)
}
@@ -100,7 +101,7 @@ open class FileDownloader(context: Context) {
parseMultipartManifestResponse(response, boundaryParameter, configuration, callback)
} else {
- val responseHeaders = response.headers()
+ val responseHeaders = response.headers
val manifestHeaderData = ManifestHeaderData(
protocolVersion = responseHeaders["expo-protocol-version"],
manifestFilters = responseHeaders["expo-manifest-filters"],
@@ -108,7 +109,7 @@ open class FileDownloader(context: Context) {
manifestSignature = responseHeaders["expo-manifest-signature"],
)
- parseManifest(response.body()!!.string(), manifestHeaderData, null, configuration, callback)
+ parseManifest(response.body!!.string(), manifestHeaderData, null, configuration, callback)
}
}
@@ -124,14 +125,14 @@ open class FileDownloader(context: Context) {
val value = line.substring(indexOfSeparator + 1).trim()
headers[key] = value
}
- return Headers.of(headers)
+ return headers.toHeaders()
}
private fun parseMultipartManifestResponse(response: Response, boundary: String, configuration: UpdatesConfiguration, callback: ManifestDownloadCallback) {
var manifestPartBodyAndHeaders: Pair<String, Headers>? = null
var extensionsBody: String? = null
- val multipartStream = MultipartStream(response.body()!!.byteStream(), boundary.toByteArray())
+ val multipartStream = MultipartStream(response.body!!.byteStream(), boundary.toByteArray())
try {
var nextPart = multipartStream.skipPreamble()
@@ -178,7 +179,7 @@ open class FileDownloader(context: Context) {
return
}
- val responseHeaders = response.headers()
+ val responseHeaders = response.headers
val manifestHeaderData = ManifestHeaderData(
protocolVersion = responseHeaders["expo-protocol-version"],
manifestFilters = responseHeaders["expo-manifest-filters"],
@@ -275,7 +276,7 @@ open class FileDownloader(context: Context) {
callback.onFailure(
"Failed to download manifest from URL: " + configuration.updateUrl,
Exception(
- response.body()!!.string()
+ response.body!!.string()
)
)
return
@@ -464,7 +465,9 @@ open class FileDownloader(context: Context) {
if (runtimeVersion != null && runtimeVersion.isNotEmpty()) {
header("Expo-Runtime-Version", runtimeVersion)
} else {
- header("Expo-SDK-Version", sdkVersion)
+ if (sdkVersion != null) {
+ header("Expo-SDK-Version", sdkVersion)
+ }
}
}
.header("Expo-Release-Channel", configuration.releaseChannel)

View File

@ -0,0 +1,10 @@
diff --git a/node_modules/react-native-fast-image/android/build.gradle b/node_modules/react-native-fast-image/android/build.gradle
index 5b21cd5..19d82f8 100644
--- a/node_modules/react-native-fast-image/android/build.gradle
+++ b/node_modules/react-native-fast-image/android/build.gradle
@@ -65,4 +65,5 @@ dependencies {
implementation "com.github.bumptech.glide:glide:${glideVersion}"
implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}"
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
+ implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.12.0'
}

View File

@ -5,4 +5,4 @@ if [ $# -ne 1 ]; then
exit 1 exit 1
fi fi
expo publish --target bare --release-channel=$1 expo publish --quiet --target bare --release-channel=$1

View File

@ -299,8 +299,28 @@ declare namespace Mastodon {
// Others // Others
thumbnail?: string thumbnail?: string
contact_account?: Account contact_account?: Account
configuration?: {
// Custom statuses: {
max_characters: number
max_media_attachments: number
characters_reserved_per_url: number
}
media_attachments: {
supported_mime_types: string[]
image_size_limit: number
image_matrix_limit: number
video_size_limit: number
video_frame_rate_limit: number
video_matrix_limit: number
}
polls: {
max_options: number
max_characters_per_option: number
min_expiration: number
max_expiration: number
}
}
// Custom - to be deprecated in v4
max_toot_chars?: number max_toot_chars?: number
} }

View File

@ -1,152 +0,0 @@
declare namespace Nav {
type RootStackParamList = {
'Screen-Tabs': undefined
'Screen-Actions':
| {
type: 'status'
queryKey: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status
}
| {
type: 'account'
account: Mastodon.Account
}
| {
type: 'notifications_filter'
}
'Screen-Announcements': { showAll: boolean }
'Screen-Compose':
| {
type: 'edit'
incomingStatus: Mastodon.Status
replyToStatus?: Mastodon.Status
queryKey?: [
'Timeline',
{
page: App.Pages
hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id']
toot?: Mastodon.Status['id']
account?: Mastodon.Account['id']
}
]
}
| {
type: 'reply'
incomingStatus: Mastodon.Status
accts: Mastodon.Account['acct'][]
queryKey?: [
'Timeline',
{
page: App.Pages
hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id']
toot?: Mastodon.Status['id']
account?: Mastodon.Account['id']
}
]
}
| {
type: 'conversation'
accts: Mastodon.Account['acct'][]
}
| undefined
'Screen-ImagesViewer': {
imageUrls: {
id: Mastodon.Attachment['id']
preview_url: Mastodon.AttachmentImage['preview_url']
url: Mastodon.AttachmentImage['url']
remote_url?: Mastodon.AttachmentImage['remote_url']
blurhash: Mastodon.AttachmentImage['blurhash']
width?: number
height?: number
}[]
id: Mastodon.Attachment['id']
}
}
type ScreenComposeStackParamList = {
'Screen-Compose-Root': undefined
'Screen-Compose-EditAttachment': { index: number }
'Screen-Compose-DraftsList': { timestamp: number }
}
type ScreenTabsStackParamList = {
'Tab-Local': undefined
'Tab-Public': undefined
'Tab-Compose': undefined
'Tab-Notifications': undefined
'Tab-Me': undefined
}
type TabSharedStackParamList = {
'Tab-Shared-Account': {
account: Mastodon.Account | Mastodon.Mention
}
'Tab-Shared-Attachments': { account: Mastodon.Account }
'Tab-Shared-Hashtag': {
hashtag: Mastodon.Tag['name']
}
'Tab-Shared-Search': { text: string | undefined }
'Tab-Shared-Toot': {
toot: Mastodon.Status
rootQueryKey?: QueryKeyTimeline
}
'Tab-Shared-Users':
| {
reference: 'accounts'
id: Mastodon.Account['id']
type: 'following' | 'followers'
count: number
}
| {
reference: 'statuses'
id: Mastodon.Status['id']
type: 'reblogged_by' | 'favourited_by'
count: number
}
}
type TabLocalStackParamList = {
'Tab-Local-Root': undefined
} & TabSharedStackParamList
type TabPublicStackParamList = {
'Tab-Public-Root': undefined
} & TabSharedStackParamList
type TabNotificationsStackParamList = {
'Tab-Notifications-Root': undefined
} & TabSharedStackParamList
type TabMeStackParamList = {
'Tab-Me-Root': undefined
'Tab-Me-Bookmarks': undefined
'Tab-Me-Conversations': undefined
'Tab-Me-Favourites': undefined
'Tab-Me-Lists': undefined
'Tab-Me-Lists-List': {
list: Mastodon.List['id']
title: Mastodon.List['title']
}
'Tab-Me-Profile': undefined
'Tab-Me-Push': undefined
'Tab-Me-Settings': undefined
'Tab-Me-Settings-Fontsize': undefined
'Tab-Me-Switch': undefined
} & TabSharedStackParamList
type TabMeProfileStackParamList = {
'Tab-Me-Profile-Root': undefined
'Tab-Me-Profile-Name': {
display_name: Mastodon.Account['display_name']
}
'Tab-Me-Profile-Note': {
note: Mastodon.Source['note']
}
'Tab-Me-Profile-Fields': {
fields?: Mastodon.Source['fields']
}
}
}

View File

@ -9,12 +9,17 @@ import netInfo from '@root/startup/netInfo'
import sentry from '@root/startup/sentry' import sentry from '@root/startup/sentry'
import { persistor, store } from '@root/store' import { persistor, store } from '@root/store'
import AccessibilityManager from '@utils/accessibility/AccessibilityManager' import AccessibilityManager from '@utils/accessibility/AccessibilityManager'
import { getSettingsLanguage } from '@utils/slices/settingsSlice' import {
changeLanguage,
getSettingsLanguage
} from '@utils/slices/settingsSlice'
import ThemeManager from '@utils/styles/ThemeManager' import ThemeManager from '@utils/styles/ThemeManager'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import * as SplashScreen from 'expo-splash-screen' import * as SplashScreen from 'expo-splash-screen'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { AppState, LogBox, Platform } from 'react-native' import { AppState, LogBox, Platform } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { enableFreeze } from 'react-native-screens'
import { QueryClientProvider } from 'react-query' import { QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'
@ -28,6 +33,7 @@ dev()
sentry() sentry()
audio() audio()
push() push()
enableFreeze(true)
const App: React.FC = () => { const App: React.FC = () => {
log('log', 'App', 'rendering App') log('log', 'App', 'rendering App')
@ -38,10 +44,10 @@ const App: React.FC = () => {
Notifications.dismissAllNotificationsAsync() Notifications.dismissAllNotificationsAsync()
}, []) }, [])
useEffect(() => { useEffect(() => {
AppState.addEventListener('change', appStateEffect) const appStateListener = AppState.addEventListener('change', appStateEffect)
return () => { return () => {
AppState.removeEventListener('change', appStateEffect) appStateListener.remove()
} }
}, []) }, [])
@ -83,6 +89,9 @@ const App: React.FC = () => {
if (bootstrapped) { if (bootstrapped) {
log('log', 'App', 'loading actual app :)') log('log', 'App', 'loading actual app :)')
const language = getSettingsLanguage(store.getState()) const language = getSettingsLanguage(store.getState())
if (!language) {
store.dispatch(changeLanguage('en'))
}
i18n.changeLanguage(language) i18n.changeLanguage(language)
return ( return (
<ActionSheetProvider> <ActionSheetProvider>
@ -101,15 +110,17 @@ const App: React.FC = () => {
) )
return ( return (
<QueryClientProvider client={queryClient}> <GestureHandlerRootView style={{ flex: 1 }}>
<Provider store={store}> <QueryClientProvider client={queryClient}>
<PersistGate <Provider store={store}>
persistor={persistor} <PersistGate
onBeforeLift={onBeforeLift} persistor={persistor}
children={children} onBeforeLift={onBeforeLift}
/> children={children}
</Provider> />
</QueryClientProvider> </Provider>
</QueryClientProvider>
</GestureHandlerRootView>
) )
} }

View File

@ -1,33 +1,36 @@
import { HeaderCenter, HeaderLeft } from '@components/Header' import { HeaderLeft } from '@components/Header'
import { displayMessage, Message, removeMessage } from '@components/Message' import { displayMessage, Message } from '@components/Message'
import navigationRef from '@helpers/navigationRef' import navigationRef from '@helpers/navigationRef'
import { useNetInfo } from '@react-native-community/netinfo'
import { NavigationContainer } from '@react-navigation/native' import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import ScreenActions from '@screens/Actions' import ScreenActions from '@screens/Actions'
import ScreenAnnouncements from '@screens/Announcements' import ScreenAnnouncements from '@screens/Announcements'
import ScreenCompose from '@screens/Compose' import ScreenCompose from '@screens/Compose'
import ScreenImagesViewer from '@screens/ImagesViewer' import ScreenImagesViewer from '@screens/ImagesViewer'
import ScreenTabs from '@screens/Tabs' import ScreenTabs from '@screens/Tabs'
import initQuery from '@utils/initQuery'
import { RootStackParamList } from '@utils/navigation/navigators'
import pushUseConnect from '@utils/push/useConnect' import pushUseConnect from '@utils/push/useConnect'
import pushUseReceive from '@utils/push/useReceive' import pushUseReceive from '@utils/push/useReceive'
import pushUseRespond from '@utils/push/useRespond' import pushUseRespond from '@utils/push/useRespond'
import { updatePreviousTab } from '@utils/slices/contextsSlice' import { updatePreviousTab } from '@utils/slices/contextsSlice'
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
import { updateFilters } from '@utils/slices/instances/updateFilters' import { updateFilters } from '@utils/slices/instances/updateFilters'
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice' import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes' import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics' import * as Analytics from 'expo-firebase-analytics'
import * as Linking from 'expo-linking'
import { addScreenshotListener } from 'expo-screen-capture' import { addScreenshotListener } from 'expo-screen-capture'
import React, { useCallback, useEffect, useRef } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Alert, Platform, StatusBar } from 'react-native' import { Alert, Platform, StatusBar } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { useQueryClient } from 'react-query'
import { onlineManager, useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
const Stack = createNativeStackNavigator<Nav.RootStackParamList>() const Stack = createNativeStackNavigator<RootStackParamList>()
export interface Props { export interface Props {
localCorrupt?: string localCorrupt?: string
@ -45,35 +48,15 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
const routeRef = useRef<{ name?: string; params?: {} }>() const routeRef = useRef<{ name?: string; params?: {} }>()
const isConnected = useNetInfo().isConnected
useEffect(() => {
switch (isConnected) {
case true:
onlineManager.setOnline(isConnected)
removeMessage()
break
case false:
onlineManager.setOnline(isConnected)
displayMessage({
mode,
type: 'error',
message: t('network.disconnected.message'),
description: t('network.disconnected.description'),
autoHide: false
})
break
}
}, [isConnected])
// Push hooks // Push hooks
const instances = useSelector( const instances = useSelector(
getInstances, getInstances,
(prev, next) => prev.length === next.length (prev, next) => prev.length === next.length
) )
const queryClient = useQueryClient() const queryClient = useQueryClient()
pushUseConnect({ navigationRef, mode, t, instances, dispatch }) pushUseConnect({ t, instances })
pushUseReceive({ navigationRef, queryClient, instances }) pushUseReceive({ instances })
pushUseRespond({ navigationRef, queryClient, instances, dispatch }) pushUseRespond({ instances })
// Prevent screenshot alert // Prevent screenshot alert
useEffect(() => { useEffect(() => {
@ -96,7 +79,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
type: 'error', type: 'error',
mode mode
}) })
navigationRef.current?.navigate('Screen-Tabs', { navigationRef.navigate('Screen-Tabs', {
screen: 'Tab-Me' screen: 'Tab-Me'
}) })
} }
@ -107,6 +90,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
// Lazily update users's preferences, for e.g. composing default visibility // Lazily update users's preferences, for e.g. composing default visibility
useEffect(() => { useEffect(() => {
if (instanceActive !== -1) { if (instanceActive !== -1) {
dispatch(updateConfiguration())
dispatch(updateFilters()) dispatch(updateFilters())
dispatch(updateAccountPreferences()) dispatch(updateAccountPreferences())
} }
@ -114,7 +98,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
// Callbacks // Callbacks
const navigationContainerOnReady = useCallback(() => { const navigationContainerOnReady = useCallback(() => {
const currentRoute = navigationRef.current?.getCurrentRoute() const currentRoute = navigationRef.getCurrentRoute()
routeRef.current = { routeRef.current = {
name: currentRoute?.name, name: currentRoute?.name,
params: currentRoute?.params params: currentRoute?.params
@ -124,7 +108,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
}, []) }, [])
const navigationContainerOnStateChange = useCallback(() => { const navigationContainerOnStateChange = useCallback(() => {
const previousRoute = routeRef.current const previousRoute = routeRef.current
const currentRoute = navigationRef.current?.getCurrentRoute() const currentRoute = navigationRef.getCurrentRoute()
const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/) const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/)
if (matchTabName) { if (matchTabName) {
@ -133,7 +117,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
} }
if (previousRoute?.name !== currentRoute?.name) { if (previousRoute?.name !== currentRoute?.name) {
Analytics.setCurrentScreen(currentRoute?.name) Analytics.logEvent('screen_view', { screen_name: currentRoute?.name })
Sentry.Native.setContext('page', { Sentry.Native.setContext('page', {
previous: previousRoute, previous: previousRoute,
current: currentRoute current: currentRoute
@ -143,6 +127,39 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
routeRef.current = currentRoute routeRef.current = currentRoute
}, []) }, [])
// Deep linking for compose
const [deeplinked, setDeeplinked] = useState(false)
useEffect(() => {
const getUrlAsync = async () => {
setDeeplinked(true)
const initialUrl = await Linking.parseInitialURLAsync()
if (initialUrl.path) {
const paths = initialUrl.path.split('/')
if (paths && paths.length) {
const instanceIndex = instances.findIndex(
instance => paths[0] === `@${instance.account.acct}@${instance.uri}`
)
if (instanceIndex !== -1 && instanceActive !== instanceIndex) {
initQuery({
instance: instances[instanceIndex],
prefetch: { enabled: true }
})
}
}
}
if (initialUrl.hostname === 'compose') {
navigationRef.navigate('Screen-Compose')
}
}
if (!deeplinked) {
getUrlAsync()
}
}, [instanceActive, instances, deeplinked])
return ( return (
<> <>
<StatusBar <StatusBar
@ -166,8 +183,8 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
name='Screen-Actions' name='Screen-Actions'
component={ScreenActions} component={ScreenActions}
options={{ options={{
stackPresentation: 'transparentModal', presentation: 'transparentModal',
stackAnimation: 'fade', animation: 'fade',
headerShown: false headerShown: false
}} }}
/> />
@ -175,38 +192,33 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
name='Screen-Announcements' name='Screen-Announcements'
component={ScreenAnnouncements} component={ScreenAnnouncements}
options={({ navigation }) => ({ options={({ navigation }) => ({
stackPresentation: 'transparentModal', presentation: 'transparentModal',
stackAnimation: 'fade', animation: 'fade',
headerShown: true, headerShown: true,
headerHideShadow: true, headerShadowVisible: false,
headerTopInsetEnabled: false, headerTransparent: true,
headerStyle: { backgroundColor: 'transparent' }, headerStyle: { backgroundColor: 'transparent' },
headerLeft: () => ( headerLeft: () => (
<HeaderLeft content='X' onPress={() => navigation.goBack()} /> <HeaderLeft content='X' onPress={() => navigation.goBack()} />
), ),
headerTitle: t('screenAnnouncements:heading'), title: t('screenAnnouncements:heading')
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('screenAnnouncements:heading')} />
)
})
})} })}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Compose' name='Screen-Compose'
component={ScreenCompose} component={ScreenCompose}
options={{ options={{
stackPresentation: 'fullScreenModal', headerShown: false,
...(Platform.OS === 'android' && { headerShown: false }) presentation: 'fullScreenModal'
}} }}
/> />
<Stack.Screen <Stack.Screen
name='Screen-ImagesViewer' name='Screen-ImagesViewer'
component={ScreenImagesViewer} component={ScreenImagesViewer}
options={{ options={{
stackPresentation: 'fullScreenModal', headerShown: false,
stackAnimation: 'fade', presentation: 'fullScreenModal',
...(Platform.OS === 'android' && { headerShown: false }) animation: 'fade'
}} }}
/> />
</Stack.Navigator> </Stack.Navigator>

View File

@ -1,6 +1,6 @@
import axios from 'axios' import axios from 'axios'
import chalk from 'chalk' import chalk from 'chalk'
import { Constants } from 'react-native-unimodules' import Constants from 'expo-constants'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
const ctx = new chalk.Instance({ level: 3 }) const ctx = new chalk.Instance({ level: 3 })
@ -24,7 +24,7 @@ const apiGeneral = async <T = unknown>({
params, params,
headers, headers,
body, body,
sentry = false sentry = true
}: Params): Promise<{ body: T }> => { }: Params): Promise<{ body: T }> => {
console.log( console.log(
ctx.bgGreen.bold(' API general ') + ctx.bgGreen.bold(' API general ') +
@ -46,7 +46,7 @@ const apiGeneral = async <T = unknown>({
params, params,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': `tooot/${Constants.manifest.version}`, 'User-Agent': `tooot/${Constants.manifest?.version}`,
Accept: '*/*', Accept: '*/*',
...headers ...headers
}, },
@ -58,8 +58,12 @@ const apiGeneral = async <T = unknown>({
}) })
}) })
.catch(error => { .catch(error => {
if (sentry) { if (sentry && Math.random() < 0.01) {
Sentry.Native.setExtras(error.response) Sentry.Native.setExtras({
API: 'general',
...(error.response && { response: error.response }),
...(error.request && { request: error.request })
})
Sentry.Native.captureException(error) Sentry.Native.captureException(error)
} }

View File

@ -1,8 +1,9 @@
import { RootState } from '@root/store' import { RootState } from '@root/store'
import axios, { AxiosRequestConfig } from 'axios' import axios, { AxiosRequestConfig } from 'axios'
import chalk from 'chalk' import chalk from 'chalk'
import Constants from 'expo-constants'
import li from 'li' import li from 'li'
import { Constants } from 'react-native-unimodules' import * as Sentry from 'sentry-expo'
const ctx = new chalk.Instance({ level: 3 }) const ctx = new chalk.Instance({ level: 3 })
@ -21,6 +22,11 @@ export type Params = {
> >
} }
export type InstanceResponse<T = unknown> = {
body: T
links: { prev?: string; next?: string }
}
const apiInstance = async <T = unknown>({ const apiInstance = async <T = unknown>({
method, method,
version = 'v1', version = 'v1',
@ -29,7 +35,7 @@ const apiInstance = async <T = unknown>({
headers, headers,
body, body,
extras extras
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => { }: Params): Promise<InstanceResponse<T>> => {
const { store } = require('@root/store') const { store } = require('@root/store')
const state = store.getState() as RootState const state = store.getState() as RootState
const instanceActive = state.instances.instances.findIndex( const instanceActive = state.instances.instances.findIndex(
@ -68,7 +74,7 @@ const apiInstance = async <T = unknown>({
params, params,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': `tooot/${Constants.manifest.version}`, 'User-Agent': `tooot/${Constants.manifest?.version}`,
Accept: '*/*', Accept: '*/*',
...headers, ...headers,
...(token && { ...(token && {
@ -92,6 +98,15 @@ const apiInstance = async <T = unknown>({
}) })
}) })
.catch(error => { .catch(error => {
if (Math.random() < 0.001) {
Sentry.Native.setExtras({
API: 'instance',
...(error.response && { response: error.response }),
...(error.request && { request: error.request })
})
Sentry.Native.captureException(error)
}
if (error.response) { if (error.response) {
// The request was made and the server responded with a status code // The request was made and the server responded with a status code
// that falls out of the range of 2xx // that falls out of the range of 2xx

View File

@ -1,13 +1,13 @@
import { mapEnvironment } from '@utils/checkEnvironment'
import axios from 'axios' import axios from 'axios'
import chalk from 'chalk' import chalk from 'chalk'
import { Constants } from 'react-native-unimodules' import Constants from 'expo-constants'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
const ctx = new chalk.Instance({ level: 3 }) const ctx = new chalk.Instance({ level: 3 })
export type Params = { export type Params = {
service: 'push' | 'translate' method: 'get' | 'post' | 'put' | 'delete'
method: 'get' | 'post'
url: string url: string
params?: { params?: {
[key: string]: string | number | boolean | string[] | number[] | boolean[] [key: string]: string | number | boolean | string[] | number[] | boolean[]
@ -17,19 +17,20 @@ export type Params = {
sentry?: boolean sentry?: boolean
} }
const DOMAIN = __DEV__ ? 'testapi.tooot.app' : 'api.tooot.app' export const TOOOT_API_DOMAIN = mapEnvironment({
release: 'api.tooot.app',
candidate: 'api-candidate.tooot.app',
development: 'api-development.tooot.app'
})
const apiTooot = async <T = unknown>({ const apiTooot = async <T = unknown>({
service,
method, method,
url, url,
params, params,
headers, headers,
body, body,
sentry = false sentry = true
}: Params): Promise<{ body: T }> => { }: Params): Promise<{ body: T }> => {
const key = Constants.manifest.extra?.toootApiKey
console.log( console.log(
ctx.bgGreen.bold(' API tooot ') + ctx.bgGreen.bold(' API tooot ') +
' ' + ' ' +
@ -43,13 +44,12 @@ const apiTooot = async <T = unknown>({
return axios({ return axios({
timeout: method === 'post' ? 1000 * 60 : 1000 * 15, timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
method, method,
baseURL: `https://${DOMAIN}/`, baseURL: `https://${TOOOT_API_DOMAIN}/`,
url: `${service}/${url}`, url: `${url}`,
params, params,
headers: { headers: {
...(key && { 'x-tooot-key': key }),
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': `tooot/${Constants.manifest.version}`, 'User-Agent': `tooot/${Constants.manifest?.version}`,
Accept: '*/*', Accept: '*/*',
...headers ...headers
}, },
@ -61,8 +61,12 @@ const apiTooot = async <T = unknown>({
}) })
}) })
.catch(error => { .catch(error => {
if (sentry) { if (sentry && Math.random() < 0.01) {
Sentry.Native.setExtras(error.response) Sentry.Native.setExtras({
API: 'tooot',
...(error.response && { response: error.response }),
...(error.request && { request: error.request })
})
Sentry.Native.captureException(error) Sentry.Native.captureException(error)
} }

View File

@ -1,6 +1,7 @@
import { ParseEmojis } from '@components/Parse' import { ParseEmojis } from '@components/Parse'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { TabLocalStackParamList } from '@utils/navigation/navigators'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
@ -21,7 +22,7 @@ const ComponentAccount: React.FC<Props> = ({
}) => { }) => {
const { theme } = useTheme() const { theme } = useTheme()
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList> StackNavigationProp<TabLocalStackParamList>
>() >()
const onPress = useCallback(() => { const onPress = useCallback(() => {

View File

@ -170,7 +170,6 @@ const ComponentInstance: React.FC<Props> = ({
]} ]}
onChangeText={onChangeText} onChangeText={onChangeText}
autoCapitalize='none' autoCapitalize='none'
autoCorrect={false}
clearButtonMode='never' clearButtonMode='never'
keyboardType='url' keyboardType='url'
textContentType='URL' textContentType='URL'
@ -183,6 +182,8 @@ const ComponentInstance: React.FC<Props> = ({
onFocus: () => onFocus: () =>
setTimeout(() => scrollViewRef.current?.scrollToEnd(), 150) setTimeout(() => scrollViewRef.current?.scrollToEnd(), 150)
})} })}
autoCorrect={false}
spellCheck={false}
/> />
<Button <Button
type='text' type='text'

View File

@ -1,4 +1,5 @@
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
import addInstance from '@utils/slices/instances/add' import addInstance from '@utils/slices/instances/add'
import { Instance } from '@utils/slices/instancesSlice' import { Instance } from '@utils/slices/instancesSlice'
import * as AuthSession from 'expo-auth-session' import * as AuthSession from 'expo-auth-session'
@ -21,7 +22,8 @@ const InstanceAuth = React.memo(
useProxy: false useProxy: false
}) })
const navigation = useNavigation() const navigation =
useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const dispatch = useDispatch() const dispatch = useDispatch()
@ -67,7 +69,6 @@ const InstanceAuth = React.memo(
domain: instanceDomain, domain: instanceDomain,
token: accessToken, token: accessToken,
instance, instance,
max_toot_chars: instance.max_toot_chars,
appData appData
}) })
) )

View File

@ -1,7 +1,10 @@
import ComponentSeparator from '@components/Separator' import ComponentSeparator from '@components/Separator'
import { useScrollToTop } from '@react-navigation/native' import { useScrollToTop } from '@react-navigation/native'
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import { getInstanceActive } from '@utils/slices/instancesSlice' import {
getInstanceActive,
updateInstanceTimelineLookback
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useCallback, useRef } from 'react' import React, { RefObject, useCallback, useRef } from 'react'
@ -10,13 +13,14 @@ import {
FlatListProps, FlatListProps,
Platform, Platform,
RefreshControl, RefreshControl,
StyleSheet StyleSheet,
ViewabilityConfigCallbackPairs
} from 'react-native' } from 'react-native'
import Animated, { import Animated, {
useAnimatedScrollHandler, useAnimatedScrollHandler,
useSharedValue useSharedValue
} from 'react-native-reanimated' } from 'react-native-reanimated'
import { useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import TimelineEmpty from './Timeline/Empty' import TimelineEmpty from './Timeline/Empty'
import TimelineFooter from './Timeline/Footer' import TimelineFooter from './Timeline/Footer'
import TimelineRefresh, { import TimelineRefresh, {
@ -31,6 +35,7 @@ export interface Props {
queryKey: QueryKeyTimeline queryKey: QueryKeyTimeline
disableRefresh?: boolean disableRefresh?: boolean
disableInfinity?: boolean disableInfinity?: boolean
lookback?: Extract<App.Pages, 'Following' | 'Local' | 'LocalPublic'>
customProps: Partial<FlatListProps<any>> & customProps: Partial<FlatListProps<any>> &
Pick<FlatListProps<any>, 'renderItem'> Pick<FlatListProps<any>, 'renderItem'>
} }
@ -40,11 +45,9 @@ const Timeline: React.FC<Props> = ({
queryKey, queryKey,
disableRefresh = false, disableRefresh = false,
disableInfinity = false, disableInfinity = false,
lookback,
customProps customProps
}) => { }) => {
// Switching account update timeline
useSelector(getInstanceActive)
const { theme } = useTheme() const { theme } = useTheme()
const { const {
@ -69,7 +72,7 @@ const Timeline: React.FC<Props> = ({
}) })
const flattenData = data?.pages const flattenData = data?.pages
? data.pages.flatMap(page => [...page.body]) ? data.pages?.flatMap(page => [...page.body])
: [] : []
const ItemSeparatorComponent = useCallback( const ItemSeparatorComponent = useCallback(
@ -124,7 +127,35 @@ const Timeline: React.FC<Props> = ({
} }
}) })
const dispatch = useDispatch()
const viewabilityPairs = useRef<ViewabilityConfigCallbackPairs>([
{
viewabilityConfig: {
minimumViewTime: 10,
viewAreaCoveragePercentThreshold: 10
},
onViewableItemsChanged: ({ viewableItems }) => {
lookback &&
dispatch(
updateInstanceTimelineLookback({
[lookback]: {
queryKey,
ids: viewableItems.map(item => item.key).slice(0, 3)
}
})
)
}
}
])
useScrollToTop(flRef) useScrollToTop(flRef)
useSelector(getInstanceActive, (prev, next) => {
if (prev !== next) {
flRef.current?.scrollToOffset({ offset: 0, animated: false })
}
return prev === next
})
return ( return (
<> <>
<TimelineRefresh <TimelineRefresh
@ -135,7 +166,6 @@ const Timeline: React.FC<Props> = ({
disableRefresh={disableRefresh} disableRefresh={disableRefresh}
/> />
<AnimatedFlatList <AnimatedFlatList
// @ts-ignore
ref={customFLRef || flRef} ref={customFLRef || flRef}
scrollEventThrottle={16} scrollEventThrottle={16}
onScroll={onScroll} onScroll={onScroll}
@ -157,6 +187,9 @@ const Timeline: React.FC<Props> = ({
maintainVisibleContentPosition={{ maintainVisibleContentPosition={{
minIndexForVisible: 0 minIndexForVisible: 0
}} }}
{...(lookback && {
viewabilityConfigCallbackPairs: viewabilityPairs.current
})}
{...androidRefreshControl} {...androidRefreshControl}
{...customProps} {...customProps}
/> />

View File

@ -10,10 +10,9 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice' import { getInstanceAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import htmlparser2 from 'htmlparser2-without-node-native'
import { uniqBy } from 'lodash' import { uniqBy } from 'lodash'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { Pressable, StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
@ -51,7 +50,7 @@ const TimelineDefault: React.FC<Props> = ({
const actualStatus = item.reblog ? item.reblog : item const actualStatus = item.reblog ? item.reblog : item
const ownAccount = actualStatus.account.id === instanceAccount?.id const ownAccount = actualStatus.account?.id === instanceAccount?.id
if ( if (
!highlighted && !highlighted &&

View File

@ -0,0 +1,37 @@
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
const TimelineLookback = React.memo(
() => {
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
return (
<View style={[styles.base, { backgroundColor: theme.backgroundDefault }]}>
<Text
style={[StyleConstants.FontStyle.S, { color: theme.primaryDefault }]}
>
{t('lookback.message')}
</Text>
</View>
)
},
() => true
)
const styles = StyleSheet.create({
base: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
padding: StyleConstants.Spacing.S
},
text: {
...StyleConstants.FontStyle.S
}
})
export default TimelineLookback

View File

@ -6,9 +6,11 @@ import AttachmentImage from '@components/Timeline/Shared/Attachment/Image'
import AttachmentUnsupported from '@components/Timeline/Shared/Attachment/Unsupported' import AttachmentUnsupported from '@components/Timeline/Shared/Attachment/Unsupported'
import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video' import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { RootStackParamList } from '@utils/navigation/navigators'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
import React, { useCallback, useMemo, useRef, useState } from 'react' import React, { useCallback, useMemo, useRef, useState } from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Pressable, StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
@ -34,9 +36,25 @@ const TimelineAttachment = React.memo(
}, []) }, [])
const imageUrls = useRef< const imageUrls = useRef<
Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'] RootStackParamList['Screen-ImagesViewer']['imageUrls']
>([]) >([])
const navigation = useNavigation() const navigation = useNavigation()
useEffect(() => {
status.media_attachments.forEach((attachment, index) => {
switch (attachment.type) {
case 'image':
imageUrls.current.push({
id: attachment.id,
preview_url: attachment.preview_url,
url: attachment.url,
remote_url: attachment.remote_url,
blurhash: attachment.blurhash,
width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height
})
}
})
}, [])
const navigateToImagesViewer = (id: string) => const navigateToImagesViewer = (id: string) =>
navigation.navigate('Screen-ImagesViewer', { navigation.navigate('Screen-ImagesViewer', {
imageUrls: imageUrls.current, imageUrls: imageUrls.current,
@ -47,15 +65,6 @@ const TimelineAttachment = React.memo(
status.media_attachments.map((attachment, index) => { status.media_attachments.map((attachment, index) => {
switch (attachment.type) { switch (attachment.type) {
case 'image': case 'image':
imageUrls.current.push({
id: attachment.id,
preview_url: attachment.preview_url,
url: attachment.url,
remote_url: attachment.remote_url,
blurhash: attachment.blurhash,
width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height
})
return ( return (
<AttachmentImage <AttachmentImage
key={index} key={index}
@ -99,10 +108,10 @@ const TimelineAttachment = React.memo(
) )
default: default:
if ( if (
attachment.preview_url.endsWith('.jpg') || attachment.preview_url?.endsWith('.jpg') ||
attachment.preview_url.endsWith('.jpeg') || attachment.preview_url?.endsWith('.jpeg') ||
attachment.preview_url.endsWith('.png') || attachment.preview_url?.endsWith('.png') ||
attachment.preview_url.endsWith('.gif') || attachment.preview_url?.endsWith('.gif') ||
attachment.remote_url?.endsWith('.jpg') || attachment.remote_url?.endsWith('.jpg') ||
attachment.remote_url?.endsWith('.jpeg') || attachment.remote_url?.endsWith('.jpeg') ||
attachment.remote_url?.endsWith('.png') || attachment.remote_url?.endsWith('.png') ||

View File

@ -96,7 +96,7 @@ const AttachmentAudio: React.FC<Props> = ({
</> </>
)} )}
</View> </View>
{audio.meta.original.duration ? ( {audio.meta?.original?.duration ? (
<View <View
style={{ style={{
alignSelf: 'flex-end', alignSelf: 'flex-end',

View File

@ -1,8 +1,14 @@
import Button from '@components/Button' import Button from '@components/Button'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { Video } from 'expo-av' import { Video } from 'expo-av'
import React, { useCallback, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Pressable, StyleSheet, View } from 'react-native' import {
AppState,
AppStateStatus,
Pressable,
StyleSheet,
View
} from 'react-native'
import { Blurhash } from 'react-native-blurhash' import { Blurhash } from 'react-native-blurhash'
import attachmentAspectRatio from './aspectRatio' import attachmentAspectRatio from './aspectRatio'
import analytics from '@components/analytics' import analytics from '@components/analytics'
@ -45,15 +51,41 @@ const AttachmentVideo: React.FC<Props> = ({
videoPlayer.current?.setOnPlaybackStatusUpdate(props => { videoPlayer.current?.setOnPlaybackStatusUpdate(props => {
if (props.isLoaded) { if (props.isLoaded) {
setVideoLoaded(true) setVideoLoaded(true)
} if (props.positionMillis) {
// @ts-ignore setVideoPosition(props.positionMillis)
if (props.positionMillis) { }
// @ts-ignore
setVideoPosition(props.positionMillis)
} }
}) })
}, [videoLoaded, videoPosition]) }, [videoLoaded, videoPosition])
const appState = useRef(AppState.currentState)
useEffect(() => {
const appState = AppState.addEventListener('change', _handleAppStateChange)
return () => appState.remove()
}, [])
const _handleAppStateChange = async (nextAppState: AppStateStatus) => {
if (appState.current.match(/active/) && nextAppState.match(/inactive/)) {
await videoPlayer.current?.pauseAsync()
} else if (
gifv &&
appState.current.match(/background/) &&
nextAppState.match(/active/)
) {
await videoPlayer.current?.setIsMutedAsync(true)
await videoPlayer.current?.playAsync()
}
appState.current = nextAppState
}
const playerStatus = useRef<any>(null)
useEffect(() => {
videoPlayer.current?.setOnPlaybackStatusUpdate(playbackStatus => {
playerStatus.current = playbackStatus
})
}, [])
return ( return (
<View <View
style={[ style={[
@ -83,16 +115,15 @@ const AttachmentVideo: React.FC<Props> = ({
posterStyle: { resizeMode: 'cover' } posterStyle: { resizeMode: 'cover' }
})} })}
useNativeControls={false} useNativeControls={false}
onFullscreenUpdate={event => { onFullscreenUpdate={async event => {
if ( if (
event.fullscreenUpdate === event.fullscreenUpdate ===
Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS
) { ) {
if (gifv) { if (gifv) {
videoPlayer.current?.setIsLoopingAsync(true) await videoPlayer.current?.pauseAsync()
videoPlayer.current?.playAsync()
} else { } else {
videoPlayer.current?.pauseAsync() await videoPlayer.current?.pauseAsync()
} }
} }
}} }}
@ -108,7 +139,7 @@ const AttachmentVideo: React.FC<Props> = ({
}} }}
/> />
) : null ) : null
) : !gifv ? ( ) : !gifv || (gifv && playerStatus.current === false) ? (
<Button <Button
round round
overlay overlay

View File

@ -1,6 +1,8 @@
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import { getInstanceAccount } from '@utils/slices/instancesSlice'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
export interface Props { export interface Props {
status: Mastodon.Status status: Mastodon.Status
@ -17,6 +19,7 @@ const TimelineContent = React.memo(
disableDetails = false disableDetails = false
}: Props) => { }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const instanceAccount = useSelector(getInstanceAccount, () => true)
return ( return (
<> <>
@ -41,7 +44,9 @@ const TimelineContent = React.memo(
emojis={status.emojis} emojis={status.emojis}
mentions={status.mentions} mentions={status.mentions}
tags={status.tags} tags={status.tags}
numberOfLines={1} numberOfLines={
instanceAccount.preferences['reading:expand:spoilers'] ? 999 : 1
}
expandHint={t('shared.content.expandHint')} expandHint={t('shared.content.expandHint')}
highlighted={highlighted} highlighted={highlighted}
disableDetails={disableDetails} disableDetails={disableDetails}

View File

@ -4,6 +4,7 @@ import { useTranslateQuery } from '@utils/queryHooks/translate'
import { getSettingsLanguage } from '@utils/slices/settingsSlice' import { getSettingsLanguage } from '@utils/slices/settingsSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import * as Localization from 'expo-localization'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Pressable, StyleSheet, Text } from 'react-native' import { Pressable, StyleSheet, Text } from 'react-native'
@ -47,9 +48,8 @@ const TimelineTranslate = React.memo(
const [enabled, setEnabled] = useState(false) const [enabled, setEnabled] = useState(false)
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({ const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
uri: status.uri,
source: status.language, source: status.language,
target: settingsLanguage, target: Localization.locale || settingsLanguage || 'en',
text, text,
options: { enabled } options: { enabled }
}) })
@ -93,7 +93,9 @@ const TimelineTranslate = React.memo(
source: data?.sourceLanguage source: data?.sourceLanguage
}) })
: t('shared.translate.default')} : t('shared.translate.default')}
{__DEV__ ? ` Source: ${status.language}` : undefined} {__DEV__
? ` Source: ${status.language}; Target: ${settingsLanguage}`
: undefined}
</Text> </Text>
{isLoading ? ( {isLoading ? (
<Circle <Circle

View File

@ -4,6 +4,9 @@ import { Platform } from 'react-native'
const haptics = ( const haptics = (
type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy' type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy'
) => { ) => {
if (Platform.OS === 'ios' && parseInt(Platform.Version, 10) <= 12) {
return
}
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
if (type === 'Error') { if (type === 'Error') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle['Light']).catch(() => {}) Haptics.impactAsync(Haptics.ImpactFeedbackStyle['Light']).catch(() => {})

View File

@ -11,7 +11,7 @@ export interface Props {
resize?: { width?: number; height?: number } // Resize mode contain resize?: { width?: number; height?: number } // Resize mode contain
showActionSheetWithOptions: ( showActionSheetWithOptions: (
options: ActionSheetOptions, options: ActionSheetOptions,
callback: (i: number) => void callback: (i?: number | undefined) => void | Promise<void>
) => void ) => void
} }
@ -57,9 +57,8 @@ const mediaSelector = async ({
}, },
async buttonIndex => { async buttonIndex => {
if (buttonIndex === 0) { if (buttonIndex === 0) {
const { const { status } =
status await ImagePicker.requestMediaLibraryPermissionsAsync()
} = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') { if (status !== 'granted') {
Alert.alert( Alert.alert(
i18next.t('componentMediaSelector:library.alert.title'), i18next.t('componentMediaSelector:library.alert.title'),

View File

@ -1,6 +1,5 @@
import apiInstance from '@api/instance' import apiInstance from '@api/instance'
import navigationRef from '@helpers/navigationRef' import navigationRef from '@helpers/navigationRef'
import { NavigationProp, ParamListBase } from '@react-navigation/native'
import { store } from '@root/store' import { store } from '@root/store'
import { SearchResult } from '@utils/queryHooks/search' import { SearchResult } from '@utils/queryHooks/search'
import { getInstanceUrl } from '@utils/slices/instancesSlice' import { getInstanceUrl } from '@utils/slices/instancesSlice'
@ -22,24 +21,7 @@ const matcherAccount = new RegExp(
export let loadingLink = false export let loadingLink = false
const openLink = async ( const openLink = async (url: string, navigation?: any) => {
url: string,
navigation?: NavigationProp<
ParamListBase,
string,
Readonly<{
key: string
index: number
routeNames: string[]
history?: unknown[] | undefined
routes: any[]
type: string
stale: false
}>,
{},
{}
>
) => {
if (loadingLink) { if (loadingLink) {
return return
} }
@ -52,7 +34,7 @@ const openLink = async (
// @ts-ignore // @ts-ignore
navigation.push(page, options) navigation.push(page, options)
} else { } else {
navigationRef.current?.navigate(page, options) navigationRef.navigate(page, options)
} }
} }

View File

@ -1,6 +1,6 @@
import { NavigationContainerRef } from '@react-navigation/native' import { createNavigationContainerRef } from '@react-navigation/native'
import { createRef } from 'react' import { RootStackParamList } from '@utils/navigation/navigators'
const navigationRef = createRef<NavigationContainerRef>() const navigationRef = createNavigationContainerRef<RootStackParamList>()
export default navigationRef export default navigationRef

View File

@ -11,6 +11,9 @@
"end": { "end": {
"message": "The end, what about a cup of <0 />" "message": "The end, what about a cup of <0 />"
}, },
"lookback": {
"message": "Last read at"
},
"refresh": { "refresh": {
"fetchPreviousPage": "Newer from here", "fetchPreviousPage": "Newer from here",
"refetch": "To latest" "refetch": "To latest"

View File

@ -1,10 +1,4 @@
{ {
"network": {
"disconnected": {
"message": "Lost network connection",
"description": "Please check your phone's network setting"
}
},
"screenshot": { "screenshot": {
"title": "Privacy Protection", "title": "Privacy Protection",
"message": "Please do not disclose other user's identity, such as username, avatar, etc. Thank you!", "message": "Please do not disclose other user's identity, such as username, avatar, etc. Thank you!",

View File

@ -144,6 +144,7 @@
} }
}, },
"push": { "push": {
"notAvailable": "Your phone does not support tooot's push notification",
"enable": { "enable": {
"direct": "Enable push notification", "direct": "Enable push notification",
"settings": "Enable in settings" "settings": "Enable in settings"
@ -184,6 +185,12 @@
"empty": "None" "empty": "None"
} }
}, },
"push": {
"content": {
"enabled": "Enabled",
"disabled": "Disabled"
}
},
"update": { "update": {
"title": "Update to latest version" "title": "Update to latest version"
}, },

View File

@ -12,7 +12,7 @@
"message": "居然刷到底了,喝杯 <0 /> 吧" "message": "居然刷到底了,喝杯 <0 /> 吧"
}, },
"lookback": { "lookback": {
"message": "最后阅读于" "message": "上次阅读至"
}, },
"refresh": { "refresh": {
"fetchPreviousPage": "较新于此的嘟嘟", "fetchPreviousPage": "较新于此的嘟嘟",

View File

@ -1,22 +1,244 @@
import { StackScreenProps } from '@react-navigation/stack' import analytics from '@components/analytics'
import React from 'react' import Button from '@components/Button'
import { SafeAreaProvider } from 'react-native-safe-area-context' import { RootStackScreenProps } from '@utils/navigation/navigators'
import ScreenActionsRoot from './Actions/Root' import {
getInstanceAccount,
export type ScreenAccountProp = StackScreenProps< getInstanceUrl
Nav.RootStackParamList, } from '@utils/slices/instancesSlice'
'Screen-Actions' import { StyleConstants } from '@utils/styles/constants'
> import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import {
SafeAreaProvider,
useSafeAreaInsets
} from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
import ActionsAccount from './Actions/Account'
import ActionsDomain from './Actions/Domain'
import ActionsNotificationsFilter from './Actions/NotificationsFilter'
import ActionsShare from './Actions/Share'
import ActionsStatus from './Actions/Status'
const ScreenActions = React.memo( const ScreenActions = React.memo(
(props: ScreenAccountProp) => { ({
route: { params },
navigation
}: RootStackScreenProps<'Screen-Actions'>) => {
const { t } = useTranslation()
const instanceAccount = useSelector(
getInstanceAccount,
(prev, next) => prev?.id === next?.id
)
let sameAccount = false
switch (params.type) {
case 'status':
sameAccount = instanceAccount?.id === params.status.account.id
break
case 'account':
sameAccount = instanceAccount?.id === params.account.id
break
}
const instanceDomain = useSelector(getInstanceUrl)
let sameDomain = true
let statusDomain: string
switch (params.type) {
case 'status':
statusDomain = params.status.uri
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
sameDomain = instanceDomain === statusDomain
break
}
const { theme } = useTheme()
const insets = useSafeAreaInsets()
const DEFAULT_VALUE = 350
const screenHeight = Dimensions.get('screen').height
const panY = useSharedValue(DEFAULT_VALUE)
useEffect(() => {
panY.value = withTiming(0)
}, [])
const styleTop = useAnimatedStyle(() => {
return {
bottom: interpolate(
panY.value,
[0, screenHeight],
[0, -screenHeight],
Extrapolate.CLAMP
)
}
})
const dismiss = useCallback(() => {
navigation.goBack()
}, [])
const onGestureEvent = useAnimatedGestureHandler({
onActive: ({ translationY }) => {
panY.value = translationY
},
onEnd: ({ velocityY }) => {
if (velocityY > 500) {
runOnJS(dismiss)()
} else {
panY.value = withTiming(0)
}
}
})
const actions = useMemo(() => {
switch (params.type) {
case 'status':
return (
<>
{!sameAccount ? (
<ActionsAccount
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
account={params.status.account}
dismiss={dismiss}
/>
) : null}
{sameAccount && params.status ? (
<ActionsStatus
navigation={navigation}
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
status={params.status}
dismiss={dismiss}
/>
) : null}
{!sameDomain && statusDomain ? (
<ActionsDomain
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
domain={statusDomain}
dismiss={dismiss}
/>
) : null}
{params.status.visibility !== 'direct' ? (
<ActionsShare
url={params.status.url || params.status.uri}
type={params.type}
dismiss={dismiss}
/>
) : null}
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'account':
return (
<>
{!sameAccount ? (
<ActionsAccount account={params.account} dismiss={dismiss} />
) : null}
<ActionsShare
url={params.account.url}
type={params.type}
dismiss={dismiss}
/>
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'notifications_filter':
return <ActionsNotificationsFilter />
}
}, [])
return ( return (
<SafeAreaProvider> <SafeAreaProvider>
<ScreenActionsRoot {...props} /> <Animated.View style={{ flex: 1 }}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
dismiss()
}
}}
>
<Animated.View
style={[
styles.overlay,
{ backgroundColor: theme.backgroundOverlayInvert }
]}
>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.backgroundDefault,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: theme.primaryOverlay }
]}
/>
{actions}
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Animated.View>
</SafeAreaProvider> </SafeAreaProvider>
) )
}, },
() => true () => true
) )
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
container: {
paddingTop: StyleConstants.Spacing.M
},
handle: {
alignSelf: 'center',
width: StyleConstants.Spacing.S * 8,
height: StyleConstants.Spacing.S / 2,
borderRadius: 100,
top: -StyleConstants.Spacing.M * 2
},
button: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
}
})
export default ScreenActions export default ScreenActions

View File

@ -1,241 +0,0 @@
import analytics from '@components/analytics'
import Button from '@components/Button'
import { StackScreenProps } from '@react-navigation/stack'
import {
getInstanceAccount,
getInstanceUrl
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
import ActionsAccount from './Account'
import ActionsDomain from './Domain'
import ActionsNotificationsFilter from './NotificationsFilter'
import ActionsShare from './Share'
import ActionsStatus from './Status'
export type ScreenAccountProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Actions'
>
const ScreenActionsRoot = React.memo(
({ route: { params }, navigation }: ScreenAccountProp) => {
const { t } = useTranslation()
const instanceAccount = useSelector(
getInstanceAccount,
(prev, next) => prev?.id === next?.id
)
let sameAccount = false
switch (params.type) {
case 'status':
sameAccount = instanceAccount?.id === params.status.account.id
break
case 'account':
sameAccount = instanceAccount?.id === params.account.id
break
}
const instanceDomain = useSelector(getInstanceUrl)
let sameDomain = true
let statusDomain: string
switch (params.type) {
case 'status':
statusDomain = params.status.uri
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
sameDomain = instanceDomain === statusDomain
break
}
const { theme } = useTheme()
const insets = useSafeAreaInsets()
const DEFAULT_VALUE = 350
const screenHeight = Dimensions.get('screen').height
const panY = useSharedValue(DEFAULT_VALUE)
useEffect(() => {
panY.value = withTiming(0)
}, [])
const styleTop = useAnimatedStyle(() => {
return {
bottom: interpolate(
panY.value,
[0, screenHeight],
[0, -screenHeight],
Extrapolate.CLAMP
)
}
})
const dismiss = useCallback(() => {
navigation.goBack()
}, [])
const onGestureEvent = useAnimatedGestureHandler({
onActive: ({ translationY }) => {
panY.value = translationY
},
onEnd: ({ velocityY }) => {
if (velocityY > 500) {
runOnJS(dismiss)()
} else {
panY.value = withTiming(0)
}
}
})
const actions = useMemo(() => {
switch (params.type) {
case 'status':
return (
<>
{!sameAccount ? (
<ActionsAccount
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
account={params.status.account}
dismiss={dismiss}
/>
) : null}
{sameAccount && params.status ? (
<ActionsStatus
navigation={navigation}
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
status={params.status}
dismiss={dismiss}
/>
) : null}
{!sameDomain && statusDomain ? (
<ActionsDomain
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
domain={statusDomain}
dismiss={dismiss}
/>
) : null}
{params.status.visibility !== 'direct' ? (
<ActionsShare
url={params.status.url || params.status.uri}
type={params.type}
dismiss={dismiss}
/>
) : null}
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'account':
return (
<>
{!sameAccount ? (
<ActionsAccount account={params.account} dismiss={dismiss} />
) : null}
<ActionsShare
url={params.account.url}
type={params.type}
dismiss={dismiss}
/>
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'notifications_filter':
return <ActionsNotificationsFilter />
}
}, [])
return (
<Animated.View style={{ flex: 1 }}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
dismiss()
}
}}
>
<Animated.View
style={[
styles.overlay,
{ backgroundColor: theme.backgroundOverlayInvert }
]}
>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: theme.backgroundDefault,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: theme.primaryOverlay }
]}
/>
{actions}
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Animated.View>
)
},
() => true
)
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
container: {
paddingTop: StyleConstants.Spacing.M
},
handle: {
alignSelf: 'center',
width: StyleConstants.Spacing.S * 8,
height: StyleConstants.Spacing.S / 2,
borderRadius: 100,
top: -StyleConstants.Spacing.M * 2
},
button: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
}
})
export default ScreenActionsRoot

View File

@ -9,13 +9,14 @@ import {
useTimelineMutation useTimelineMutation
} from '@utils/queryHooks/timeline' } from '@utils/queryHooks/timeline'
import analytics from '@components/analytics' import analytics from '@components/analytics'
import { StackNavigationProp } from '@react-navigation/stack'
import { displayMessage } from '@components/Message' import { displayMessage } from '@components/Message'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import apiInstance from '@api/instance' import apiInstance from '@api/instance'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { RootStackParamList } from '@utils/navigation/navigators'
export interface Props { export interface Props {
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Actions'> navigation: NativeStackNavigationProp<RootStackParamList, 'Screen-Actions'>
queryKey: QueryKeyTimeline queryKey: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status status: Mastodon.Status

View File

@ -4,8 +4,8 @@ import haptics from '@components/haptics'
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import RelativeTime from '@components/RelativeTime' import RelativeTime from '@components/RelativeTime'
import { BlurView } from '@react-native-community/blur' import { BlurView } from '@react-native-community/blur'
import { StackScreenProps } from '@react-navigation/stack'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { RootStackScreenProps } from '@utils/navigation/navigators'
import { import {
useAnnouncementMutation, useAnnouncementMutation,
useAnnouncementQuery useAnnouncementQuery
@ -14,18 +14,22 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native' import {
Dimensions,
Platform,
Pressable,
StyleSheet,
Text,
View
} from 'react-native'
import { Circle } from 'react-native-animated-spinkit' import { Circle } from 'react-native-animated-spinkit'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { FlatList, ScrollView } from 'react-native-gesture-handler' import { FlatList, ScrollView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
export type ScreenAnnouncementsProp = StackScreenProps< const ScreenAnnouncements: React.FC<
Nav.RootStackParamList, RootStackScreenProps<'Screen-Announcements'>
'Screen-Announcements' > = ({
>
const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
route: { route: {
params: { showAll = false } params: { showAll = false }
}, },
@ -202,7 +206,7 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
) )
}, []) }, [])
return ( return Platform.OS === 'ios' ? (
<BlurView <BlurView
blurType={mode} blurType={mode}
blurAmount={20} blurAmount={20}
@ -242,6 +246,41 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
</View> </View>
</SafeAreaView> </SafeAreaView>
</BlurView> </BlurView>
) : (
<SafeAreaView
style={[styles.base, { backgroundColor: theme.backgroundDefault }]}
>
<FlatList
horizontal
data={query.data}
pagingEnabled
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
onMomentumScrollEnd={onMomentumScrollEnd}
ListEmptyComponent={ListEmptyComponent}
/>
<View style={styles.indicators}>
{query.data && query.data.length > 1 ? (
<>
{query.data.map((d, i) => (
<View
key={i}
style={[
styles.indicator,
{
borderColor: theme.primaryDefault,
backgroundColor:
i === index ? theme.primaryDefault : undefined,
marginLeft:
i === query.data.length ? 0 : StyleConstants.Spacing.S
}
]}
/>
))}
</>
) : null}
</View>
</SafeAreaView>
) )
} }
@ -253,6 +292,7 @@ const styles = StyleSheet.create({
announcementContainer: { announcementContainer: {
width: Dimensions.get('screen').width, width: Dimensions.get('screen').width,
padding: StyleConstants.Spacing.Global.PagePadding, padding: StyleConstants.Spacing.Global.PagePadding,
marginVertical: StyleConstants.Spacing.Global.PagePadding,
justifyContent: 'center' justifyContent: 'center'
}, },
published: { published: {

View File

@ -1,14 +1,15 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header' import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import haptics from '@root/components/haptics' import haptics from '@root/components/haptics'
import formatText from '@screens/Compose/formatText' import formatText from '@screens/Compose/formatText'
import ComposeRoot from '@screens/Compose/Root' import ComposeRoot from '@screens/Compose/Root'
import { RootStackScreenProps } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { updateStoreReview } from '@utils/slices/contextsSlice' import { updateStoreReview } from '@utils/slices/contextsSlice'
import { import {
getInstanceAccount, getInstanceAccount,
getInstanceMaxTootChar, getInstanceConfigurationStatusMaxChars,
removeInstanceDraft, removeInstanceDraft,
updateInstanceDraft updateInstanceDraft
} from '@utils/slices/instancesSlice' } from '@utils/slices/instancesSlice'
@ -31,7 +32,6 @@ import {
StyleSheet StyleSheet
} from 'react-native' } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useQueryClient } from 'react-query' import { useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
@ -43,14 +43,9 @@ import composeParseState from './Compose/utils/parseState'
import composePost from './Compose/utils/post' import composePost from './Compose/utils/post'
import composeReducer from './Compose/utils/reducer' import composeReducer from './Compose/utils/reducer'
export type ScreenComposeProp = StackScreenProps<
Nav.RootStackParamList,
'Screen-Compose'
>
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
const ScreenCompose: React.FC<ScreenComposeProp> = ({ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
route: { params }, route: { params },
navigation navigation
}) => { }) => {
@ -60,20 +55,18 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
const [hasKeyboard, setHasKeyboard] = useState(false) const [hasKeyboard, setHasKeyboard] = useState(false)
useEffect(() => { useEffect(() => {
Keyboard.addListener('keyboardWillShow', _keyboardDidShow) const keyboardShown = Keyboard.addListener('keyboardWillShow', () =>
Keyboard.addListener('keyboardWillHide', _keyboardDidHide) setHasKeyboard(true)
)
const keyboardHidden = Keyboard.addListener('keyboardWillHide', () =>
setHasKeyboard(false)
)
return () => { return () => {
Keyboard.removeListener('keyboardWillShow', _keyboardDidShow) keyboardShown.remove()
Keyboard.removeListener('keyboardWillHide', _keyboardDidHide) keyboardHidden.remove()
} }
}, []) }, [])
const _keyboardDidShow = () => {
setHasKeyboard(true)
}
const _keyboardDidHide = () => {
setHasKeyboard(false)
}
const localAccount = useSelector(getInstanceAccount, (prev, next) => const localAccount = useSelector(getInstanceAccount, (prev, next) =>
prev?.preferences && next?.preferences prev?.preferences && next?.preferences
@ -110,7 +103,10 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
initialReducerState initialReducerState
) )
const maxTootChars = useSelector(getInstanceMaxTootChar, () => true) const maxTootChars = useSelector(
getInstanceConfigurationStatusMaxChars,
() => true
)
const totalTextCount = const totalTextCount =
(composeState.spoiler.active ? composeState.spoiler.count : 0) + (composeState.spoiler.active ? composeState.spoiler.count : 0) +
composeState.text.count composeState.text.count
@ -337,7 +333,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
] ]
) )
} else { } else {
Sentry.Native.captureException(error) Sentry.Native.captureMessage('Compose posting', error)
haptics('Error') haptics('Error')
composeDispatch({ type: 'posting', payload: false }) composeDispatch({ type: 'posting', payload: false })
Alert.alert(t('heading.right.alert.default.title'), undefined, [ Alert.alert(t('heading.right.alert.default.title'), undefined, [
@ -371,33 +367,21 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
edges={hasKeyboard ? ['top'] : ['top', 'bottom']} edges={hasKeyboard ? ['top'] : ['top', 'bottom']}
> >
<ComposeContext.Provider value={{ composeState, composeDispatch }}> <ComposeContext.Provider value={{ composeState, composeDispatch }}>
<Stack.Navigator <Stack.Navigator initialRouteName='Screen-Compose-Root'>
screenOptions={{ headerTopInsetEnabled: false }}
initialRouteName='Screen-Compose-Root'
>
<Stack.Screen <Stack.Screen
name='Screen-Compose-Root' name='Screen-Compose-Root'
component={ComposeRoot} component={ComposeRoot}
options={{ options={{
...Platform.select({ title: headerContent,
ios: { titleStyle: {
headerTitle: headerContent, fontWeight:
headerTitleStyle: { totalTextCount > maxTootChars
fontWeight: ? StyleConstants.Font.Weight.Bold
totalTextCount > maxTootChars : StyleConstants.Font.Weight.Normal,
? StyleConstants.Font.Weight.Bold fontSize: StyleConstants.Font.Size.M
: StyleConstants.Font.Weight.Normal, },
fontSize: StyleConstants.Font.Size.M headerTintColor:
}, totalTextCount > maxTootChars ? theme.red : theme.secondary,
headerTintColor:
totalTextCount > maxTootChars
? theme.red
: theme.secondary
},
android: {
headerCenter: () => <HeaderCenter content={headerContent} />
}
}),
headerLeft, headerLeft,
headerRight headerRight
}} }}
@ -405,18 +389,12 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
<Stack.Screen <Stack.Screen
name='Screen-Compose-DraftsList' name='Screen-Compose-DraftsList'
component={ComposeDraftsList} component={ComposeDraftsList}
options={{ options={{ headerShown: false, presentation: 'modal' }}
stackPresentation: 'modal',
...(Platform.OS === 'android' && { headerShown: false })
}}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Compose-EditAttachment' name='Screen-Compose-EditAttachment'
component={ComposeEditAttachment} component={ComposeEditAttachment}
options={{ options={{ headerShown: false, presentation: 'modal' }}
stackPresentation: 'modal',
...(Platform.OS === 'android' && { headerShown: false })
}}
/> />
</Stack.Navigator> </Stack.Navigator>
</ComposeContext.Provider> </ComposeContext.Provider>

View File

@ -1,19 +1,15 @@
import { HeaderCenter, HeaderLeft } from '@components/Header' import { HeaderLeft } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ComposeDraftsListRoot from './DraftsList/Root' import ComposeDraftsListRoot from './DraftsList/Root'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
export type ScreenComposeEditAttachmentProp = StackScreenProps< const ComposeDraftsList: React.FC<
Nav.ScreenComposeStackParamList, ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>
'Screen-Compose-DraftsList' > = ({
>
const ComposeDraftsList: React.FC<ScreenComposeEditAttachmentProp> = ({
route: { route: {
params: { timestamp } params: { timestamp }
}, },
@ -37,19 +33,14 @@ const ComposeDraftsList: React.FC<ScreenComposeEditAttachmentProp> = ({
) )
return ( return (
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}> <Stack.Navigator>
<Stack.Screen <Stack.Screen
name='Screen-Compose-EditAttachment-Root' name='Screen-Compose-EditAttachment-Root'
children={children} children={children}
options={{ options={{
headerLeft, headerLeft,
headerTitle: t('content.draftsList.header.title'), title: t('content.draftsList.header.title'),
...(Platform.OS === 'android' && { headerShadowVisible: false
headerCenter: () => (
<HeaderCenter content={t('content.draftsList.header.title')} />
)
}),
headerHideShadow: true
}} }}
/> />
</Stack.Navigator> </Stack.Navigator>

View File

@ -1,21 +1,18 @@
import { HeaderCenter, HeaderLeft } from '@components/Header' import { HeaderLeft } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { KeyboardAvoidingView, Platform } from 'react-native' import { KeyboardAvoidingView, Platform } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ComposeEditAttachmentRoot from './EditAttachment/Root' import ComposeEditAttachmentRoot from './EditAttachment/Root'
import ComposeEditAttachmentSubmit from './EditAttachment/Submit' import ComposeEditAttachmentSubmit from './EditAttachment/Submit'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
export type ScreenComposeEditAttachmentProp = StackScreenProps< const ComposeEditAttachment: React.FC<ScreenComposeStackScreenProps<
Nav.ScreenComposeStackParamList,
'Screen-Compose-EditAttachment' 'Screen-Compose-EditAttachment'
> >> = ({
const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
route: { route: {
params: { index } params: { index }
}, },
@ -45,21 +42,14 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
style={{ flex: 1 }} style={{ flex: 1 }}
> >
<SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}> <SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}>
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}> <Stack.Navigator>
<Stack.Screen <Stack.Screen
name='Screen-Compose-EditAttachment-Root' name='Screen-Compose-EditAttachment-Root'
children={children} children={children}
options={{ options={{
headerLeft, headerLeft,
headerRight: () => <ComposeEditAttachmentSubmit index={index} />, headerRight: () => <ComposeEditAttachmentSubmit index={index} />,
headerTitle: t('content.editAttachment.header.title'), title: t('content.editAttachment.header.title')
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter
content={t('content.editAttachment.header.title')}
/>
)
})
}} }}
/> />
</Stack.Navigator> </Stack.Navigator>

View File

@ -42,34 +42,38 @@ const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => {
) { ) {
formData.append( formData.append(
'focus', 'focus',
`${theAttachment.meta?.focus?.x || 0},${-theAttachment.meta?.focus `${theAttachment.meta?.focus?.x || 0},${
?.y || 0}` -theAttachment.meta?.focus?.y || 0
}`
) )
} }
apiInstance<Mastodon.Attachment>({ theAttachment?.id &&
method: 'put', apiInstance<Mastodon.Attachment>({
url: `media/${theAttachment.id}`, method: 'put',
body: formData url: `media/${theAttachment.id}`,
}) body: formData
.then(() => {
haptics('Success')
navigation.goBack()
})
.catch(() => {
setIsSubmitting(false)
haptics('Error')
Alert.alert(
t('content.editAttachment.header.right.failed.title'),
undefined,
[
{
text: t('content.editAttachment.header.right.failed.button'),
style: 'cancel'
}
]
)
}) })
.then(() => {
haptics('Success')
navigation.goBack()
})
.catch(() => {
setIsSubmitting(false)
haptics('Error')
Alert.alert(
t('content.editAttachment.header.right.failed.title'),
undefined,
[
{
text: t(
'content.editAttachment.header.right.failed.button'
),
style: 'cancel'
}
]
)
})
}} }}
/> />
) )

View File

@ -29,6 +29,8 @@ import ComposeDrafts from './Root/Drafts'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { ComposeState } from './utils/types' import { ComposeState } from './utils/types'
import { useSelector } from 'react-redux'
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>, sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>,
@ -54,11 +56,18 @@ const prefetchEmojis = (
} catch {} } catch {}
} }
export let instanceConfigurationStatusCharsURL = 23
const ComposeRoot = React.memo( const ComposeRoot = React.memo(
() => { () => {
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme() const { theme } = useTheme()
instanceConfigurationStatusCharsURL = useSelector(
getInstanceConfigurationStatusCharsURL,
() => true
)
const accessibleRefDrafts = useRef(null) const accessibleRefDrafts = useRef(null)
const accessibleRefAttachments = useRef(null) const accessibleRefAttachments = useRef(null)
const accessibleRefEmojis = useRef(null) const accessibleRefEmojis = useRef(null)

View File

@ -1,12 +1,14 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import Icon from '@components/Icon' import Icon from '@components/Icon'
import { useActionSheet } from '@expo/react-native-action-sheet' import { useActionSheet } from '@expo/react-native-action-sheet'
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useContext, useMemo } from 'react' import React, { useCallback, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Pressable, StyleSheet, View } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import ComposeContext from '../utils/createContext' import ComposeContext from '../utils/createContext'
import addAttachment from './Footer/addAttachment' import addAttachment from './Footer/addAttachment'
@ -15,6 +17,10 @@ const ComposeActions: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { theme } = useTheme()
const instanceConfigurationStatusMaxAttachments = useSelector(
getInstanceConfigurationStatusMaxAttachments,
() => true
)
const attachmentColor = useMemo(() => { const attachmentColor = useMemo(() => {
if (composeState.poll.active) return theme.disabled if (composeState.poll.active) return theme.disabled
@ -28,7 +34,10 @@ const ComposeActions: React.FC = () => {
const attachmentOnPress = useCallback(async () => { const attachmentOnPress = useCallback(async () => {
if (composeState.poll.active) return if (composeState.poll.active) return
if (composeState.attachments.uploads.length < 4) { if (
composeState.attachments.uploads.length <
instanceConfigurationStatusMaxAttachments
) {
analytics('compose_actions_attachment_press', { analytics('compose_actions_attachment_press', {
count: composeState.attachments.uploads.length count: composeState.attachments.uploads.length
}) })

View File

@ -15,7 +15,7 @@ export interface Props {
const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => { const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const navigation = useNavigation() const navigation = useNavigation<any>()
const { composeState } = useContext(ComposeContext) const { composeState } = useContext(ComposeContext)
const instanceDrafts = useSelector(getInstanceDrafts)?.filter( const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
draft => draft.timestamp !== composeState.timestamp draft => draft.timestamp !== composeState.timestamp

View File

@ -40,7 +40,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { theme } = useTheme()
const navigation = useNavigation() const navigation = useNavigation<any>()
const flatListRef = useRef<FlatList>(null) const flatListRef = useRef<FlatList>(null)

View File

@ -3,11 +3,13 @@ import Button from '@components/Button'
import Icon from '@components/Icon' import Icon from '@components/Icon'
import { MenuRow } from '@components/Menu' import { MenuRow } from '@components/Menu'
import { useActionSheet } from '@expo/react-native-action-sheet' import { useActionSheet } from '@expo/react-native-action-sheet'
import { getInstanceConfigurationPoll } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleSheet, TextInput, View } from 'react-native' import { StyleSheet, Text, TextInput, View } from 'react-native'
import { useSelector } from 'react-redux'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
const ComposePoll: React.FC = () => { const ComposePoll: React.FC = () => {
@ -21,6 +23,16 @@ const ComposePoll: React.FC = () => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { mode, theme } = useTheme() const { mode, theme } = useTheme()
const instanceConfigurationPoll = useSelector(
getInstanceConfigurationPoll,
() => true
)
const MAX_OPTIONS = instanceConfigurationPoll.max_options
const MAX_CHARS_PER_OPTION =
instanceConfigurationPoll.max_characters_per_option
const MIN_EXPIRATION = instanceConfigurationPoll.min_expiration
const MAX_EXPIRATION = instanceConfigurationPoll.max_expiration
const [firstRender, setFirstRender] = useState(true) const [firstRender, setFirstRender] = useState(true)
useEffect(() => { useEffect(() => {
setFirstRender(false) setFirstRender(false)
@ -67,7 +79,7 @@ const ComposePoll: React.FC = () => {
: t('content.root.footer.poll.option.placeholder.single') : t('content.root.footer.poll.option.placeholder.single')
} }
placeholderTextColor={theme.disabled} placeholderTextColor={theme.disabled}
maxLength={50} maxLength={MAX_CHARS_PER_OPTION}
// @ts-ignore // @ts-ignore
value={options[i]} value={options[i]}
onChangeText={e => onChangeText={e =>
@ -82,37 +94,38 @@ const ComposePoll: React.FC = () => {
})} })}
</View> </View>
<View style={styles.controlAmount}> <View style={styles.controlAmount}>
<View style={styles.firstButton}>
<Button
{...(total > 2
? {
accessibilityLabel: t(
'content.root.footer.poll.quantity.reduce.accessibilityLabel',
{ amount: total - 1 }
)
}
: {
accessibilityHint: t(
'content.root.footer.poll.quantity.reduce.accessibilityHint',
{ amount: total }
)
})}
onPress={() => {
analytics('compose_poll_reduce_press')
total > 2 &&
composeDispatch({
type: 'poll',
payload: { total: total - 1 }
})
}}
type='icon'
content='Minus'
round
disabled={!(total > 2)}
/>
</View>
<Button <Button
{...(total < 4 {...(total > 2
? {
accessibilityLabel: t(
'content.root.footer.poll.quantity.reduce.accessibilityLabel',
{ amount: total - 1 }
)
}
: {
accessibilityHint: t(
'content.root.footer.poll.quantity.reduce.accessibilityHint',
{ amount: total }
)
})}
onPress={() => {
analytics('compose_poll_reduce_press')
total > 2 &&
composeDispatch({
type: 'poll',
payload: { total: total - 1 }
})
}}
type='icon'
content='Minus'
round
disabled={!(total > 2)}
/>
<Text style={styles.controlCount}>
{total} / {MAX_OPTIONS}
</Text>
<Button
{...(total < MAX_OPTIONS
? { ? {
accessibilityLabel: t( accessibilityLabel: t(
'content.root.footer.poll.quantity.increase.accessibilityLabel', 'content.root.footer.poll.quantity.increase.accessibilityLabel',
@ -127,7 +140,7 @@ const ComposePoll: React.FC = () => {
})} })}
onPress={() => { onPress={() => {
analytics('compose_poll_increase_press') analytics('compose_poll_increase_press')
total < 4 && total < MAX_OPTIONS &&
composeDispatch({ composeDispatch({
type: 'poll', type: 'poll',
payload: { total: total + 1 } payload: { total: total + 1 }
@ -136,7 +149,7 @@ const ComposePoll: React.FC = () => {
type='icon' type='icon'
content='Plus' content='Plus'
round round
disabled={!(total < 4)} disabled={!(total < MAX_OPTIONS)}
/> />
</View> </View>
<View style={styles.controlOptions}> <View style={styles.controlOptions}>
@ -158,7 +171,7 @@ const ComposePoll: React.FC = () => {
cancelButtonIndex: 2 cancelButtonIndex: 2
}, },
index => { index => {
if (index < 2) { if (index && index < 2) {
analytics('compose_poll_expiration_press', { analytics('compose_poll_expiration_press', {
current: multiple, current: multiple,
new: index === 1 new: index === 1
@ -177,7 +190,7 @@ const ComposePoll: React.FC = () => {
title={t('content.root.footer.poll.expiration.heading')} title={t('content.root.footer.poll.expiration.heading')}
content={t(`content.root.footer.poll.expiration.options.${expire}`)} content={t(`content.root.footer.poll.expiration.options.${expire}`)}
onPress={() => { onPress={() => {
const expirations: [ const expirations = [
'300', '300',
'1800', '1800',
'3600', '3600',
@ -185,7 +198,11 @@ const ComposePoll: React.FC = () => {
'86400', '86400',
'259200', '259200',
'604800' '604800'
] = ['300', '1800', '3600', '21600', '86400', '259200', '604800'] ].filter(
expiration =>
parseInt(expiration) >= MIN_EXPIRATION &&
parseInt(expiration) <= MAX_EXPIRATION
)
showActionSheetWithOptions( showActionSheetWithOptions(
{ {
options: [ options: [
@ -194,16 +211,17 @@ const ComposePoll: React.FC = () => {
), ),
t('content.root.footer.poll.expiration.options.cancel') t('content.root.footer.poll.expiration.options.cancel')
], ],
cancelButtonIndex: 7 cancelButtonIndex: expirations.length
}, },
index => { index => {
if (index < 7) { if (index && index < expirations.length) {
analytics('compose_poll_expiration_press', { analytics('compose_poll_expiration_press', {
current: expire, current: expire,
new: expirations[index] new: expirations[index]
}) })
composeDispatch({ composeDispatch({
type: 'poll', type: 'poll',
// @ts-ignore
payload: { expire: expirations[index] } payload: { expire: expirations[index] }
}) })
} }
@ -246,14 +264,15 @@ const styles = StyleSheet.create({
controlAmount: { controlAmount: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end', justifyContent: 'flex-end',
marginRight: StyleConstants.Spacing.M marginRight: StyleConstants.Spacing.M
}, },
controlOptions: { controlOptions: {
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
}, },
firstButton: { controlCount: {
marginRight: StyleConstants.Spacing.S marginHorizontal: StyleConstants.Spacing.S
} }
}) })

Some files were not shown because too many files have changed in this diff Show More