Merge branch 'main' into l10n_main
|
@ -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=""
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
custom: ['https://www.buymeacoffee.com/xmflsct']
|
|
@ -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 }}
|
||||||
|
|
|
@ -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
|
206
Gemfile.lock
|
@ -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
|
||||||
|
|
|
@ -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` |
|
|
@ -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'
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
@ -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' }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
|
@ -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
|
|
|
@ -1,6 +1,7 @@
|
||||||
tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象(https://joinmastodon.org/)账号。
|
tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象(https://joinmastodon.org/)账号。
|
||||||
|
|
||||||
tooot支持:
|
tooot支持:
|
||||||
|
- iPad
|
||||||
- 多账号登录
|
- 多账号登录
|
||||||
- 黑暗或自适应模式
|
- 黑暗或自适应模式
|
||||||
- 可调整正文字体大小
|
- 可调整正文字体大小
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
加入翻译嘟文支持,翻译服务由多个服务商提供
|
添加支持iPad
|
||||||
修改个人信息里可以上传头像及横幅
|
|
||||||
|
|
After Width: | Height: | Size: 423 KiB |
After Width: | Height: | Size: 423 KiB |
After Width: | Height: | Size: 265 KiB |
After Width: | Height: | Size: 265 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 422 KiB |
After Width: | Height: | Size: 422 KiB |
After Width: | Height: | Size: 268 KiB |
After Width: | Height: | Size: 268 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 291 KiB |
After Width: | Height: | Size: 291 KiB |
|
@ -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
|
||||||
|
|
16
ios/Podfile
|
@ -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.
|
||||||
|
|
1001
ios/Podfile.lock
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"expo.jsEngine": "hermes"
|
||||||
|
}
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 82 B |
Before Width: | Height: | Size: 82 B |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
//
|
||||||
|
// @generated
|
||||||
|
// A blank Swift file must be created for native modules with Swift files to work correctly.
|
||||||
|
//
|
|
@ -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>
|
||||||
|
|
193
package.json
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
|
@ -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) {
|
|
@ -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)
|
|
@ -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'
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
35
src/App.tsx
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
114
src/Screens.tsx
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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
|
|
@ -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') ||
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(() => {})
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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!",
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"message": "居然刷到底了,喝杯 <0 /> 吧"
|
"message": "居然刷到底了,喝杯 <0 /> 吧"
|
||||||
},
|
},
|
||||||
"lookback": {
|
"lookback": {
|
||||||
"message": "最后阅读于"
|
"message": "上次阅读至"
|
||||||
},
|
},
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"fetchPreviousPage": "较新于此的嘟嘟",
|
"fetchPreviousPage": "较新于此的嘟嘟",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|