mirror of https://github.com/tooot-app/app
commit
d238b829d6
|
@ -5,7 +5,7 @@ export SENTRY_PROJECT=""
|
|||
export SENTRY_AUTH_TOKEN=""
|
||||
export SENTRY_DSN=""
|
||||
|
||||
export TOOOT_API_KEY=""
|
||||
export TOOOT_PUSH_KEY_PUBLIC=""
|
||||
|
||||
# Fastlane start
|
||||
export LC_ALL=""
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
run: bundle install
|
||||
- name: -- Step 7 -- Run fastlane
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
|
||||
ENVIRONMENT: ${{ steps.branch.outputs.branch }}
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
|
@ -41,7 +41,7 @@ jobs:
|
|||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
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 }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
|
||||
|
|
194
Gemfile.lock
194
Gemfile.lock
|
@ -1,41 +1,43 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.3)
|
||||
activesupport (5.2.5)
|
||||
CFPropertyList (3.0.5)
|
||||
rexml
|
||||
activesupport (6.1.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.1)
|
||||
aws-partitions (1.455.0)
|
||||
aws-sdk-core (3.114.0)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.541.0)
|
||||
aws-sdk-core (3.124.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.43.0)
|
||||
aws-sdk-core (~> 3, >= 3.112.0)
|
||||
aws-sdk-kms (1.52.0)
|
||||
aws-sdk-core (~> 3, >= 3.122.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.94.1)
|
||||
aws-sdk-core (~> 3, >= 3.112.0)
|
||||
aws-sdk-s3 (1.109.0)
|
||||
aws-sdk-core (~> 3, >= 3.122.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.3)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.10.1)
|
||||
addressable (~> 2.6)
|
||||
cocoapods (1.11.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.10.1)
|
||||
cocoapods-core (= 1.11.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.4.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
|
@ -46,26 +48,26 @@ GEM
|
|||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.6)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.19.0, < 2.0)
|
||||
cocoapods-core (1.10.1)
|
||||
activesupport (> 5.0, < 6)
|
||||
addressable (~> 2.6)
|
||||
ruby-macho (>= 1.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.11.2)
|
||||
activesupport (>= 5.0, < 7)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.4.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.5.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-trunk (1.5.0)
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
|
@ -73,36 +75,46 @@ GEM
|
|||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.1.8)
|
||||
concurrent-ruby (1.1.9)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.3)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.2)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.12.0)
|
||||
ffi (>= 1.3.0)
|
||||
excon (0.81.0)
|
||||
faraday (1.4.1)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.89.0)
|
||||
faraday (1.8.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.1.0)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.3)
|
||||
fastlane (2.182.0)
|
||||
fastimage (2.2.5)
|
||||
fastlane (2.199.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
|
@ -117,14 +129,16 @@ GEM
|
|||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.37.0, < 0.39.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
|
@ -138,80 +152,76 @@ GEM
|
|||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
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-yarn (1.2)
|
||||
ffi (1.15.0)
|
||||
ffi (1.15.4)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.38.0)
|
||||
google-apis-androidpublisher_v3 (0.14.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
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)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
signet (~> 0.14)
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.3.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-storage_v1 (0.3.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-iamcredentials_v1 (0.9.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.6.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.1.0)
|
||||
google-cloud-storage (1.31.0)
|
||||
addressable (~> 2.5)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.35.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.16.2)
|
||||
googleauth (1.1.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.3)
|
||||
http-cookie (1.0.4)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.9)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.4.0)
|
||||
json (2.5.1)
|
||||
jwt (2.2.3)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.14.4)
|
||||
molinillo (0.6.6)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
os (1.1.1)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.3)
|
||||
rake (13.0.6)
|
||||
representable (3.1.1)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
|
@ -219,12 +229,12 @@ GEM
|
|||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.4.0)
|
||||
ruby2_keywords (0.0.4)
|
||||
rubyzip (2.3.0)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.15.0)
|
||||
addressable (~> 2.3)
|
||||
signet (0.16.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
|
@ -234,36 +244,38 @@ GEM
|
|||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
trailblazer-option (0.1.1)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
unf_ext (0.0.8)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.19.0)
|
||||
xcodeproj (1.21.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
universal-darwin-20
|
||||
universal-darwin-21
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods
|
||||
|
@ -274,4 +286,4 @@ DEPENDENCIES
|
|||
fastlane-plugin-yarn
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.8
|
||||
2.2.17
|
||||
|
|
|
@ -78,13 +78,11 @@ import com.android.build.OutputFile
|
|||
*/
|
||||
|
||||
project.ext.react = [
|
||||
enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes"
|
||||
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: "../../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"
|
||||
apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute().text.trim(), "../react.gradle")
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
|
@ -190,9 +188,39 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.3.0") {
|
||||
force = true
|
||||
}
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
//noinspection GradleDynamicVersion
|
||||
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"
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
|
@ -204,12 +232,10 @@ dependencies {
|
|||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
}
|
||||
addUnimodulesDependencies()
|
||||
|
||||
if (enableHermes) {
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/";
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
debugImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute().text.trim(), "../android/hermes-debug.aar"))
|
||||
releaseImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute().text.trim(), "../android/hermes-release.aar"))
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
@ -222,6 +248,7 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
|||
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'
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
package com.xmflsct.app.tooot;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.Intent;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
|
@ -9,30 +6,14 @@ import com.facebook.react.ReactActivityDelegate;
|
|||
import com.facebook.react.ReactRootView;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||
|
||||
import expo.modules.splashscreen.singletons.SplashScreen;
|
||||
import expo.modules.splashscreen.SplashScreenImageResizeMode;
|
||||
|
||||
import expo.modules.ReactActivityDelegateWrapper;
|
||||
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
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.
|
||||
* This is used to schedule rendering of the component.
|
||||
|
@ -44,11 +25,14 @@ public class MainActivity extends ReactActivity {
|
|||
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
||||
return new ReactActivityDelegateWrapper(
|
||||
this,
|
||||
new ReactActivityDelegate(this, getMainComponentName()) {
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ package com.xmflsct.app.tooot;
|
|||
|
||||
import android.app.Application;
|
||||
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.ReactApplication;
|
||||
|
@ -11,32 +12,23 @@ import com.facebook.react.ReactNativeHost;
|
|||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.xmflsct.app.tooot.generated.BasePackageList;
|
||||
|
||||
import org.unimodules.adapters.react.ReactAdapterPackage;
|
||||
import org.unimodules.adapters.react.ModuleRegistryAdapter;
|
||||
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.ApplicationLifecycleDispatcher;
|
||||
import expo.modules.ReactNativeHostWrapper;
|
||||
import expo.modules.permissions.PermissionsPackage;
|
||||
import expo.modules.filesystem.FileSystemPackage;
|
||||
import expo.modules.updates.UpdatesController;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.JSIModulePackage; // <- react-native-reanimated-v2
|
||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- react-native-reanimated-v2
|
||||
import com.facebook.react.bridge.JSIModulePackage; // <- react-native-reanimated-v2
|
||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- react-native-reanimated-v2
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
|
||||
new BasePackageList().getPackageList()
|
||||
);
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
|
||||
this,
|
||||
new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
|
@ -44,8 +36,10 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -58,25 +52,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
protected JSIModulePackage getJSIModulePackage() {
|
||||
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
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
|
@ -88,11 +64,14 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
UpdatesController.initialize(this);
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,19 +2,21 @@
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "29.0.3"
|
||||
buildToolsVersion = "30.0.2"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 30
|
||||
targetSdkVersion = 30
|
||||
ndkVersion = "20.1.5948944"
|
||||
ndkVersion = "21.4.7075529"
|
||||
kotlinVersion = '1.5.32'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
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
|
||||
// in the individual module build.gradle files
|
||||
|
@ -26,14 +28,15 @@ allprojects {
|
|||
mavenLocal()
|
||||
maven {
|
||||
// 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 {
|
||||
// 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()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
|
|
|
@ -35,4 +35,12 @@ org.gradle.configureondemand=true
|
|||
|
||||
# The hosted JavaScript engine
|
||||
# Supported values: expo.jsEngine = "hermes" | "jsc"
|
||||
expo.jsEngine=hermes
|
||||
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
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
rootProject.name = 'tooot'
|
||||
|
||||
apply from: '../node_modules/react-native-unimodules/gradle.groovy'
|
||||
includeUnimodulesProjects()
|
||||
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute().text.trim(), "../scripts/autolinking.gradle");
|
||||
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)
|
||||
|
||||
include ':app'
|
||||
|
|
|
@ -9,12 +9,11 @@ export default (): ExpoConfig => ({
|
|||
description: 'tooot for Mastodon',
|
||||
slug: 'tooot',
|
||||
version: toootVersion,
|
||||
sdkVersion: versions.expo,
|
||||
privacy: 'hidden',
|
||||
assetBundlePatterns: ['assets/*'],
|
||||
extra: {
|
||||
sentryDSN: process.env.SENTRY_DSN,
|
||||
toootApiKey: process.env.TOOOT_API_KEY
|
||||
toootPushKeyPublic: process.env.TOOOT_PUSH_KEY_PUBLIC
|
||||
},
|
||||
hooks: {
|
||||
postPublish: [
|
||||
|
@ -30,11 +29,11 @@ export default (): ExpoConfig => ({
|
|||
}
|
||||
]
|
||||
},
|
||||
jsEngine: 'hermes',
|
||||
ios: {
|
||||
bundleIdentifier: 'com.xmflsct.app.tooot'
|
||||
},
|
||||
android: {
|
||||
jsEngine: 'hermes',
|
||||
package: 'com.xmflsct.app.tooot',
|
||||
googleServicesFile: './configs/google-services.json',
|
||||
permissions: ['CAMERA', 'VIBRATE']
|
||||
|
|
|
@ -91,6 +91,7 @@ private_lane :build_ios do
|
|||
dsym_path: DSYM_FILE
|
||||
)
|
||||
upload_to_testflight(
|
||||
skip_submission: true,
|
||||
ipa: IPA_FILE,
|
||||
demo_account_required: true,
|
||||
distribute_external: true,
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
Added translation option, translation service is provided by various providers
|
||||
When updating profile, now avatar and banner can be uploaded
|
||||
🍎
|
||||
Support version from iOS 12
|
||||
🤖️
|
||||
Support APNG animation
|
|
@ -1,2 +1,4 @@
|
|||
加入翻译嘟文支持,翻译服务由多个服务商提供
|
||||
修改个人信息里可以上传头像及横幅
|
||||
🍎
|
||||
最低版本支持升级至iOS 12
|
||||
🤖️
|
||||
支持APNG动画
|
16
ios/Podfile
16
ios/Podfile
|
@ -1,17 +1,19 @@
|
|||
require_relative '../node_modules/react-native/scripts/react_native_pods'
|
||||
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
|
||||
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
||||
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
|
||||
use_unimodules!
|
||||
use_expo_modules!
|
||||
config = use_native_modules!
|
||||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
# to enable hermes on iOS, change `false` to `true` and then install pods
|
||||
:hermes_enabled => false
|
||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes'
|
||||
)
|
||||
|
||||
# Enables Flipper.
|
||||
|
|
928
ios/Podfile.lock
928
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
96905EF65AED1B983A6B3ABC /* libPods-tooot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.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 */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -37,6 +38,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -75,6 +77,14 @@
|
|||
name = tooot;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1568DA5289D5AE7A39201A34 /* tooot */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9D878F932AF7A9974E06E461 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = tooot;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -85,6 +95,14 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2FFEB4B0D00502D5425CDDC2 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1568DA5289D5AE7A39201A34 /* tooot */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -102,6 +120,7 @@
|
|||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
D65327D7A22EEC0BE12398D9 /* Pods */,
|
||||
5EE44DD42600124E00A9BCED /* tooot-Bridging-Header.h */,
|
||||
2FFEB4B0D00502D5425CDDC2 /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
|
@ -148,6 +167,7 @@
|
|||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
49D30A53634620EF2A5C6692 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -246,6 +266,24 @@
|
|||
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;
|
||||
};
|
||||
49D30A53634620EF2A5C6692 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -253,10 +291,14 @@
|
|||
);
|
||||
inputPaths = (
|
||||
"${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",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
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",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -293,6 +335,7 @@
|
|||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
|
||||
5EE44DD62600124E00A9BCED /* File.swift in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
E3BC22F5F8ABE515E14CF199 /* ExpoModulesProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -338,7 +381,7 @@
|
|||
"FB_SONARKIT_ENABLED=1",
|
||||
);
|
||||
INFOPLIST_FILE = tooot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
@ -369,7 +412,7 @@
|
|||
DEVELOPMENT_TEAM = 8EGBLQ2MA6;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
INFOPLIST_FILE = tooot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
@ -419,7 +462,7 @@
|
|||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
|
@ -435,7 +478,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
|
@ -481,7 +524,7 @@
|
|||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
|
@ -490,7 +533,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <EXUpdates/EXUpdatesAppController.h>
|
||||
#import <React/RCTBridgeDelegate.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <UMCore/UMAppDelegateWrapper.h>
|
||||
#import <Expo/Expo.h>
|
||||
|
||||
@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
|
||||
@interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate>
|
||||
|
||||
@end
|
||||
|
|
|
@ -4,12 +4,7 @@
|
|||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
#import <React/RCTLinkingManager.h>
|
||||
|
||||
#import <UMCore/UMModuleRegistry.h>
|
||||
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
|
||||
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
|
||||
#import <EXSplashScreen/EXSplashScreenService.h>
|
||||
#import <UMCore/UMModuleRegistryProvider.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#if defined(FB_SONARKIT_ENABLED) && __has_include(<FlipperKit/FlipperClient.h>)
|
||||
#import <FlipperKit/FlipperClient.h>
|
||||
|
@ -19,8 +14,6 @@
|
|||
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
|
||||
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
|
||||
|
||||
#import <React/RCTLinkingManager.h>
|
||||
|
||||
// iOS 9.x or newer
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
|
@ -50,13 +43,6 @@ static void InitializeFlipper(UIApplication *application) {
|
|||
}
|
||||
#endif
|
||||
|
||||
@interface AppDelegate () <RCTBridgeDelegate>
|
||||
|
||||
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
|
||||
@property (nonatomic, strong) NSDictionary *launchOptions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
|
@ -65,57 +51,34 @@ static void InitializeFlipper(UIApplication *application) {
|
|||
InitializeFlipper(application);
|
||||
#endif
|
||||
|
||||
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
|
||||
self.launchOptions = launchOptions;
|
||||
RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
|
||||
RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
|
||||
rootView.backgroundColor = [UIColor whiteColor];
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
#ifdef DEBUG
|
||||
[self initializeReactNativeApp];
|
||||
#else
|
||||
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
|
||||
controller.delegate = self;
|
||||
[controller startAndShowLaunchScreen:self.window];
|
||||
#endif
|
||||
UIViewController *rootViewController = [self.reactDelegate createRootViewController];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
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>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
|
||||
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
|
||||
return extraModules;
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
|
||||
#ifdef DEBUG
|
||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
|
||||
#else
|
||||
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
|
||||
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
#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
|
||||
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [RCTLinkingManager application:application openURL:url options:options];
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
//
|
||||
// @generated
|
||||
// A blank Swift file must be created for native modules with Swift files to work correctly.
|
||||
//
|
132
package.json
132
package.json
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "tooot",
|
||||
"versions": {
|
||||
"native": "210916",
|
||||
"major": 2,
|
||||
"minor": 2,
|
||||
"native": "211218",
|
||||
"major": 3,
|
||||
"minor": 0,
|
||||
"patch": 0,
|
||||
"expo": "42.0.0"
|
||||
"expo": "44.0.0"
|
||||
},
|
||||
"description": "tooot app for Mastodon",
|
||||
"author": "xmflsct <me@xmflsct.com>",
|
||||
|
@ -20,96 +20,102 @@
|
|||
"ios": "react-native run-ios",
|
||||
"app:build": "bundle exec fastlane build",
|
||||
"release": "scripts/release.sh",
|
||||
"clean": "react-native-clean-project"
|
||||
"clean": "react-native-clean-project",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/react-native-action-sheet": "3.11.0",
|
||||
"@expo/react-native-action-sheet": "3.12.0",
|
||||
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
|
||||
"@react-native-async-storage/async-storage": "1.15.8",
|
||||
"@react-native-async-storage/async-storage": "1.15.14",
|
||||
"@react-native-community/blur": "3.6.0",
|
||||
"@react-native-community/cameraroll": "4.0.4",
|
||||
"@react-native-community/netinfo": "6.0.2",
|
||||
"@react-native-community/cameraroll": "4.1.2",
|
||||
"@react-native-community/netinfo": "7.1.6",
|
||||
"@react-native-community/segmented-control": "2.2.2",
|
||||
"@react-navigation/bottom-tabs": "6.0.7",
|
||||
"@react-navigation/native": "6.0.4",
|
||||
"@react-navigation/native-stack": "6.2.2",
|
||||
"@react-navigation/stack": "6.0.9",
|
||||
"@reduxjs/toolkit": "1.6.1",
|
||||
"@sentry/react-native": "2.6.2",
|
||||
"@sharcoux/slider": "5.5.2",
|
||||
"axios": "0.22.0",
|
||||
"expo": "42.0.4",
|
||||
"expo-auth-session": "3.3.1",
|
||||
"expo-av": "9.2.3",
|
||||
"expo-crypto": "9.2.0",
|
||||
"expo-device": "^3.3.0",
|
||||
"expo-firebase-analytics": "4.1.0",
|
||||
"expo-haptics": "10.1.0",
|
||||
"expo-image-manipulator": "9.2.2",
|
||||
"expo-image-picker": "10.2.3",
|
||||
"expo-linking": "2.3.1",
|
||||
"expo-localization": "10.2.0",
|
||||
"expo-notifications": "0.12.3",
|
||||
"expo-permissions": "^12.1.1",
|
||||
"expo-random": "11.2.0",
|
||||
"expo-screen-capture": "3.2.0",
|
||||
"expo-secure-store": "10.2.0",
|
||||
"expo-splash-screen": "0.11.4",
|
||||
"expo-status-bar": "1.0.4",
|
||||
"expo-store-review": "4.1.0",
|
||||
"expo-updates": "^0.8.5",
|
||||
"expo-video-thumbnails": "^5.2.1",
|
||||
"expo-web-browser": "9.2.0",
|
||||
"@react-navigation/bottom-tabs": "6.0.9",
|
||||
"@react-navigation/native": "6.0.6",
|
||||
"@react-navigation/native-stack": "6.2.5",
|
||||
"@react-navigation/stack": "6.0.11",
|
||||
"@reduxjs/toolkit": "1.7.1",
|
||||
"@sentry/react-native": "3.2.3",
|
||||
"@sharcoux/slider": "5.6.1",
|
||||
"axios": "0.24.0",
|
||||
"expo": "44.0.0",
|
||||
"expo-auth-session": "3.5.0",
|
||||
"expo-av": "10.1.3",
|
||||
"expo-constants": "^13.0.0",
|
||||
"expo-crypto": "10.1.1",
|
||||
"expo-device": "4.1.0",
|
||||
"expo-file-system": "13.1.0",
|
||||
"expo-firebase-analytics": "6.0.0",
|
||||
"expo-haptics": "11.1.0",
|
||||
"expo-image-manipulator": "10.2.0",
|
||||
"expo-image-picker": "12.0.1",
|
||||
"expo-linking": "3.0.0",
|
||||
"expo-localization": "12.0.0",
|
||||
"expo-media-library": "^14.0.0",
|
||||
"expo-notifications": "0.14.0",
|
||||
"expo-permissions": "13.1.0",
|
||||
"expo-random": "12.1.1",
|
||||
"expo-screen-capture": "4.1.0",
|
||||
"expo-secure-store": "11.1.0",
|
||||
"expo-splash-screen": "0.14.0",
|
||||
"expo-status-bar": "1.2.0",
|
||||
"expo-store-review": "5.1.0",
|
||||
"expo-updates": "0.11.1",
|
||||
"expo-video-thumbnails": "6.1.0",
|
||||
"expo-web-browser": "10.1.0",
|
||||
"i18next": "20.6.1",
|
||||
"li": "1.3.0",
|
||||
"lodash": "4.17.21",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-i18next": "11.12.0",
|
||||
"react-native": "0.64.2",
|
||||
"react-i18next": "11.15.1",
|
||||
"react-native": "0.66.4",
|
||||
"react-native-animated-spinkit": "1.5.2",
|
||||
"react-native-blurhash": "1.1.5",
|
||||
"react-native-base64": "^0.2.1",
|
||||
"react-native-blurhash": "1.1.7",
|
||||
"react-native-fast-image": "8.5.11",
|
||||
"react-native-feather": "1.1.2",
|
||||
"react-native-flash-message": "0.2.0",
|
||||
"react-native-gesture-handler": "1.10.3",
|
||||
"react-native-gesture-handler": "2.1.0",
|
||||
"react-native-htmlview": "0.16.0",
|
||||
"react-native-pager-view": "5.4.6",
|
||||
"react-native-reanimated": "2.2.2",
|
||||
"react-native-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "3.8.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-reanimated": "2.3.1",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "3.10.1",
|
||||
"react-native-svg": "12.1.1",
|
||||
"react-native-swipe-list-view": "3.2.9",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-unimodules": "0.14.8",
|
||||
"react-query": "3.25.1",
|
||||
"react-redux": "7.2.5",
|
||||
"react-query": "3.34.5",
|
||||
"react-redux": "7.2.6",
|
||||
"react-timeago": "6.2.1",
|
||||
"redux-persist": "6.0.0",
|
||||
"rn-placeholder": "3.0.3",
|
||||
"sentry-expo": "4.0.1",
|
||||
"sentry-expo": "4.0.3",
|
||||
"tslib": "2.3.1",
|
||||
"valid-url": "1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.15.5",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.14.5",
|
||||
"@babel/preset-typescript": "7.15.0",
|
||||
"@expo/config": "6.0.0",
|
||||
"@types/lodash": "4.14.175",
|
||||
"@types/react": "17.0.27",
|
||||
"@types/react-dom": "17.0.9",
|
||||
"@types/react-native": "0.64.13",
|
||||
"@types/react-navigation": "3.4.0",
|
||||
"@types/react-redux": "7.1.18",
|
||||
"@babel/core": "7.16.5",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.16.5",
|
||||
"@babel/preset-typescript": "7.16.5",
|
||||
"@expo/config": "6.0.14",
|
||||
"@types/lodash": "4.14.178",
|
||||
"@types/react": "17.0.37",
|
||||
"@types/react-dom": "17.0.11",
|
||||
"@types/react-native": "0.66.9",
|
||||
"@types/react-native-base64": "^0.2.0",
|
||||
"@types/react-redux": "7.1.20",
|
||||
"@types/react-timeago": "4.1.3",
|
||||
"@types/valid-url": "1.0.3",
|
||||
"@welldone-software/why-did-you-render": "6.2.1",
|
||||
"@welldone-software/why-did-you-render": "6.2.3",
|
||||
"babel-plugin-module-resolver": "4.1.0",
|
||||
"babel-plugin-transform-remove-console": "6.9.4",
|
||||
"chalk": "4.1.2",
|
||||
"dotenv": "10.0.0",
|
||||
"patch-package": "6.4.7",
|
||||
"postinstall-postinstall": "2.1.0",
|
||||
"react-native-clean-project": "3.6.7",
|
||||
"typescript": "4.4.3"
|
||||
"typescript": "4.5.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
File diff suppressed because one or more lines are too long
|
@ -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'
|
||||
}
|
|
@ -299,8 +299,28 @@ declare namespace Mastodon {
|
|||
// Others
|
||||
thumbnail?: string
|
||||
contact_account?: Account
|
||||
|
||||
// Custom
|
||||
configuration?: {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
23
src/App.tsx
23
src/App.tsx
|
@ -15,6 +15,8 @@ import * as Notifications from 'expo-notifications'
|
|||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
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 { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
@ -28,6 +30,7 @@ dev()
|
|||
sentry()
|
||||
audio()
|
||||
push()
|
||||
enableFreeze()
|
||||
|
||||
const App: React.FC = () => {
|
||||
log('log', 'App', 'rendering App')
|
||||
|
@ -101,15 +104,17 @@ const App: React.FC = () => {
|
|||
)
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
children={children}
|
||||
/>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
children={children}
|
||||
/>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</GestureHandlerRootView>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import pushUseReceive from '@utils/push/useReceive'
|
|||
import pushUseRespond from '@utils/push/useRespond'
|
||||
import { updatePreviousTab } from '@utils/slices/contextsSlice'
|
||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
|
||||
import { updateFilters } from '@utils/slices/instances/updateFilters'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
@ -108,6 +109,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
// Lazily update users's preferences, for e.g. composing default visibility
|
||||
useEffect(() => {
|
||||
if (instanceActive !== -1) {
|
||||
dispatch(updateConfiguration())
|
||||
dispatch(updateFilters())
|
||||
dispatch(updateAccountPreferences())
|
||||
}
|
||||
|
@ -134,7 +136,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
}
|
||||
|
||||
if (previousRoute?.name !== currentRoute?.name) {
|
||||
Analytics.setCurrentScreen(currentRoute?.name)
|
||||
Analytics.logEvent('screen_view', { screen_name: currentRoute?.name })
|
||||
Sentry.Native.setContext('page', {
|
||||
previous: previousRoute,
|
||||
current: currentRoute
|
||||
|
@ -185,7 +187,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||
headerLeft: () => (
|
||||
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
||||
),
|
||||
headerTitle: t('screenAnnouncements:heading')
|
||||
title: t('screenAnnouncements:heading')
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios from 'axios'
|
||||
import chalk from 'chalk'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
|
||||
const ctx = new chalk.Instance({ level: 3 })
|
||||
|
@ -59,7 +59,7 @@ const apiGeneral = async <T = unknown>({
|
|||
})
|
||||
.catch(error => {
|
||||
if (sentry) {
|
||||
Sentry.Native.setExtras(error.response)
|
||||
Sentry.Native.setExtras(error.response || error.request)
|
||||
Sentry.Native.captureException(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { RootState } from '@root/store'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import chalk from 'chalk'
|
||||
import Constants from 'expo-constants'
|
||||
import li from 'li'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
|
||||
const ctx = new chalk.Instance({ level: 3 })
|
||||
|
||||
|
@ -21,6 +21,11 @@ export type Params = {
|
|||
>
|
||||
}
|
||||
|
||||
export type InstanceResponse<T = unknown> = {
|
||||
body: T
|
||||
links: { prev?: string; next?: string }
|
||||
}
|
||||
|
||||
const apiInstance = async <T = unknown>({
|
||||
method,
|
||||
version = 'v1',
|
||||
|
@ -29,7 +34,7 @@ const apiInstance = async <T = unknown>({
|
|||
headers,
|
||||
body,
|
||||
extras
|
||||
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
|
||||
}: Params): Promise<InstanceResponse<T>> => {
|
||||
const { store } = require('@root/store')
|
||||
const state = store.getState() as RootState
|
||||
const instanceActive = state.instances.instances.findIndex(
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import axios from 'axios'
|
||||
import chalk from 'chalk'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
|
||||
const ctx = new chalk.Instance({ level: 3 })
|
||||
|
||||
export type Params = {
|
||||
service: 'push' | 'translate'
|
||||
method: 'get' | 'post'
|
||||
method: 'get' | 'post' | 'put' | 'delete'
|
||||
url: string
|
||||
params?: {
|
||||
[key: string]: string | number | boolean | string[] | number[] | boolean[]
|
||||
|
@ -17,10 +16,9 @@ export type Params = {
|
|||
sentry?: boolean
|
||||
}
|
||||
|
||||
const DOMAIN = __DEV__ ? 'testapi.tooot.app' : 'api.tooot.app'
|
||||
export const TOOOT_API_DOMAIN = __DEV__ ? 'testapi.tooot.app' : 'api.tooot.app'
|
||||
|
||||
const apiTooot = async <T = unknown>({
|
||||
service,
|
||||
method,
|
||||
url,
|
||||
params,
|
||||
|
@ -28,8 +26,6 @@ const apiTooot = async <T = unknown>({
|
|||
body,
|
||||
sentry = false
|
||||
}: Params): Promise<{ body: T }> => {
|
||||
const key = Constants.manifest?.extra?.toootApiKey
|
||||
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API tooot ') +
|
||||
' ' +
|
||||
|
@ -43,11 +39,10 @@ const apiTooot = async <T = unknown>({
|
|||
return axios({
|
||||
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
|
||||
method,
|
||||
baseURL: `https://${DOMAIN}/`,
|
||||
url: `${service}/${url}`,
|
||||
baseURL: `https://${TOOOT_API_DOMAIN}/`,
|
||||
url: `${url}`,
|
||||
params,
|
||||
headers: {
|
||||
...(key && { 'x-tooot-key': key }),
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': `tooot/${Constants.manifest?.version}`,
|
||||
Accept: '*/*',
|
||||
|
@ -62,7 +57,7 @@ const apiTooot = async <T = unknown>({
|
|||
})
|
||||
.catch(error => {
|
||||
if (sentry) {
|
||||
Sentry.Native.setExtras(error.response)
|
||||
Sentry.Native.setExtras(error.response || error.request)
|
||||
Sentry.Native.captureException(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -170,7 +170,6 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
]}
|
||||
onChangeText={onChangeText}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
clearButtonMode='never'
|
||||
keyboardType='url'
|
||||
textContentType='URL'
|
||||
|
@ -183,6 +182,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||
onFocus: () =>
|
||||
setTimeout(() => scrollViewRef.current?.scrollToEnd(), 150)
|
||||
})}
|
||||
autoCorrect={false}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<Button
|
||||
type='text'
|
||||
|
|
|
@ -22,9 +22,8 @@ const InstanceAuth = React.memo(
|
|||
useProxy: false
|
||||
})
|
||||
|
||||
const navigation = useNavigation<
|
||||
TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>
|
||||
>()
|
||||
const navigation =
|
||||
useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
|
||||
const queryClient = useQueryClient()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
@ -70,7 +69,6 @@ const InstanceAuth = React.memo(
|
|||
domain: instanceDomain,
|
||||
token: accessToken,
|
||||
instance,
|
||||
max_toot_chars: instance.max_toot_chars,
|
||||
appData
|
||||
})
|
||||
)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { ParseHTML } from '@components/Parse'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
status: Mastodon.Status
|
||||
|
@ -17,6 +19,7 @@ const TimelineContent = React.memo(
|
|||
disableDetails = false
|
||||
}: Props) => {
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -41,7 +44,9 @@ const TimelineContent = React.memo(
|
|||
emojis={status.emojis}
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={1}
|
||||
numberOfLines={
|
||||
instanceAccount.preferences['reading:expand:spoilers'] ? 999 : 1
|
||||
}
|
||||
expandHint={t('shared.content.expandHint')}
|
||||
highlighted={highlighted}
|
||||
disableDetails={disableDetails}
|
||||
|
|
|
@ -47,7 +47,6 @@ const TimelineTranslate = React.memo(
|
|||
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
||||
uri: status.uri,
|
||||
source: status.language,
|
||||
target: settingsLanguage,
|
||||
text,
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface Props {
|
|||
resize?: { width?: number; height?: number } // Resize mode contain
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (i: number) => void
|
||||
callback: (i?: number | undefined) => void | Promise<void>
|
||||
) => void
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,8 @@ const mediaSelector = async ({
|
|||
},
|
||||
async buttonIndex => {
|
||||
if (buttonIndex === 0) {
|
||||
const {
|
||||
status
|
||||
} = await ImagePicker.requestMediaLibraryPermissionsAsync()
|
||||
const { status } =
|
||||
await ImagePicker.requestMediaLibraryPermissionsAsync()
|
||||
if (status !== 'granted') {
|
||||
Alert.alert(
|
||||
i18next.t('componentMediaSelector:library.alert.title'),
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
}
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Your phone does not support tooot's push notification",
|
||||
"enable": {
|
||||
"direct": "Enable push notification",
|
||||
"settings": "Enable in settings"
|
||||
|
@ -184,6 +185,12 @@
|
|||
"empty": "None"
|
||||
}
|
||||
},
|
||||
"push": {
|
||||
"content": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "Update to latest version"
|
||||
},
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
}
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "您的机型暂不支持 tooot 推送通知",
|
||||
"enable": {
|
||||
"direct": "启用推送通知",
|
||||
"settings": "在系统设置中启用"
|
||||
|
@ -184,6 +185,12 @@
|
|||
"empty": "无公告"
|
||||
}
|
||||
},
|
||||
"push": {
|
||||
"content": {
|
||||
"enabled": "已启用",
|
||||
"disabled": "未启用"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "更新至最新版本"
|
||||
},
|
||||
|
|
|
@ -20,9 +20,9 @@ import FastImage from 'react-native-fast-image'
|
|||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
const ScreenAnnouncements: React.FC<RootStackScreenProps<
|
||||
'Screen-Announcements'
|
||||
>> = ({
|
||||
const ScreenAnnouncements: React.FC<
|
||||
RootStackScreenProps<'Screen-Announcements'>
|
||||
> = ({
|
||||
route: {
|
||||
params: { showAll = false }
|
||||
},
|
||||
|
@ -250,6 +250,7 @@ const styles = StyleSheet.create({
|
|||
announcementContainer: {
|
||||
width: Dimensions.get('screen').width,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
published: {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|||
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceMaxTootChar,
|
||||
getInstanceConfigurationStatusMaxChars,
|
||||
removeInstanceDraft,
|
||||
updateInstanceDraft
|
||||
} from '@utils/slices/instancesSlice'
|
||||
|
@ -55,20 +55,18 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||
|
||||
const [hasKeyboard, setHasKeyboard] = useState(false)
|
||||
useEffect(() => {
|
||||
Keyboard.addListener('keyboardWillShow', _keyboardDidShow)
|
||||
Keyboard.addListener('keyboardWillHide', _keyboardDidHide)
|
||||
const keyboardShown = Keyboard.addListener('keyboardWillShow', () =>
|
||||
setHasKeyboard(true)
|
||||
)
|
||||
const keyboardHidden = Keyboard.addListener('keyboardWillHide', () =>
|
||||
setHasKeyboard(false)
|
||||
)
|
||||
|
||||
return () => {
|
||||
Keyboard.removeListener('keyboardWillShow', _keyboardDidShow)
|
||||
Keyboard.removeListener('keyboardWillHide', _keyboardDidHide)
|
||||
keyboardShown.remove()
|
||||
keyboardHidden.remove()
|
||||
}
|
||||
}, [])
|
||||
const _keyboardDidShow = () => {
|
||||
setHasKeyboard(true)
|
||||
}
|
||||
const _keyboardDidHide = () => {
|
||||
setHasKeyboard(false)
|
||||
}
|
||||
|
||||
const localAccount = useSelector(getInstanceAccount, (prev, next) =>
|
||||
prev?.preferences && next?.preferences
|
||||
|
@ -105,7 +103,10 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||
initialReducerState
|
||||
)
|
||||
|
||||
const maxTootChars = useSelector(getInstanceMaxTootChar, () => true)
|
||||
const maxTootChars = useSelector(
|
||||
getInstanceConfigurationStatusMaxChars,
|
||||
() => true
|
||||
)
|
||||
const totalTextCount =
|
||||
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
|
||||
composeState.text.count
|
||||
|
@ -371,25 +372,16 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||
name='Screen-Compose-Root'
|
||||
component={ComposeRoot}
|
||||
options={{
|
||||
...Platform.select({
|
||||
ios: {
|
||||
headerTitle: headerContent,
|
||||
headerTitleStyle: {
|
||||
fontWeight:
|
||||
totalTextCount > maxTootChars
|
||||
? StyleConstants.Font.Weight.Bold
|
||||
: StyleConstants.Font.Weight.Normal,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
},
|
||||
headerTintColor:
|
||||
totalTextCount > maxTootChars
|
||||
? theme.red
|
||||
: theme.secondary
|
||||
},
|
||||
android: {
|
||||
headerCenter: () => <HeaderCenter content={headerContent} />
|
||||
}
|
||||
}),
|
||||
title: headerContent,
|
||||
titleStyle: {
|
||||
fontWeight:
|
||||
totalTextCount > maxTootChars
|
||||
? StyleConstants.Font.Weight.Bold
|
||||
: StyleConstants.Font.Weight.Normal,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
},
|
||||
headerTintColor:
|
||||
totalTextCount > maxTootChars ? theme.red : theme.secondary,
|
||||
headerLeft,
|
||||
headerRight
|
||||
}}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import ComposeDraftsListRoot from './DraftsList/Root'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<
|
||||
'Screen-Compose-DraftsList'
|
||||
>> = ({
|
||||
const ComposeDraftsList: React.FC<
|
||||
ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>
|
||||
> = ({
|
||||
route: {
|
||||
params: { timestamp }
|
||||
},
|
||||
|
@ -40,12 +39,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<
|
|||
children={children}
|
||||
options={{
|
||||
headerLeft,
|
||||
headerTitle: t('content.draftsList.header.title'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('content.draftsList.header.title')} />
|
||||
)
|
||||
}),
|
||||
title: t('content.draftsList.header.title'),
|
||||
headerShadowVisible: false
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -49,7 +49,7 @@ const ComposeEditAttachment: React.FC<ScreenComposeStackScreenProps<
|
|||
options={{
|
||||
headerLeft,
|
||||
headerRight: () => <ComposeEditAttachmentSubmit index={index} />,
|
||||
headerTitle: t('content.editAttachment.header.title')
|
||||
title: t('content.editAttachment.header.title')
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
|
|
|
@ -29,6 +29,8 @@ import ComposeDrafts from './Root/Drafts'
|
|||
import FastImage from 'react-native-fast-image'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { ComposeState } from './utils/types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
|
||||
|
||||
const prefetchEmojis = (
|
||||
sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>,
|
||||
|
@ -54,11 +56,18 @@ const prefetchEmojis = (
|
|||
} catch {}
|
||||
}
|
||||
|
||||
export let instanceConfigurationStatusCharsURL = 23
|
||||
|
||||
const ComposeRoot = React.memo(
|
||||
() => {
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { theme } = useTheme()
|
||||
|
||||
instanceConfigurationStatusCharsURL = useSelector(
|
||||
getInstanceConfigurationStatusCharsURL,
|
||||
() => true
|
||||
)
|
||||
|
||||
const accessibleRefDrafts = useRef(null)
|
||||
const accessibleRefAttachments = useRef(null)
|
||||
const accessibleRefEmojis = useRef(null)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import analytics from '@components/analytics'
|
||||
import Icon from '@components/Icon'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import addAttachment from './Footer/addAttachment'
|
||||
|
||||
|
@ -15,6 +17,10 @@ const ComposeActions: React.FC = () => {
|
|||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { theme } = useTheme()
|
||||
const instanceConfigurationStatusMaxAttachments = useSelector(
|
||||
getInstanceConfigurationStatusMaxAttachments,
|
||||
() => true
|
||||
)
|
||||
|
||||
const attachmentColor = useMemo(() => {
|
||||
if (composeState.poll.active) return theme.disabled
|
||||
|
@ -28,7 +34,10 @@ const ComposeActions: React.FC = () => {
|
|||
const attachmentOnPress = useCallback(async () => {
|
||||
if (composeState.poll.active) return
|
||||
|
||||
if (composeState.attachments.uploads.length < 4) {
|
||||
if (
|
||||
composeState.attachments.uploads.length <
|
||||
instanceConfigurationStatusMaxAttachments
|
||||
) {
|
||||
analytics('compose_actions_attachment_press', {
|
||||
count: composeState.attachments.uploads.length
|
||||
})
|
||||
|
|
|
@ -3,11 +3,13 @@ import Button from '@components/Button'
|
|||
import Icon from '@components/Icon'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { getInstanceConfigurationPoll } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
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'
|
||||
|
||||
const ComposePoll: React.FC = () => {
|
||||
|
@ -21,6 +23,16 @@ const ComposePoll: React.FC = () => {
|
|||
const { t } = useTranslation('screenCompose')
|
||||
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)
|
||||
useEffect(() => {
|
||||
setFirstRender(false)
|
||||
|
@ -67,7 +79,7 @@ const ComposePoll: React.FC = () => {
|
|||
: t('content.root.footer.poll.option.placeholder.single')
|
||||
}
|
||||
placeholderTextColor={theme.disabled}
|
||||
maxLength={50}
|
||||
maxLength={MAX_CHARS_PER_OPTION}
|
||||
// @ts-ignore
|
||||
value={options[i]}
|
||||
onChangeText={e =>
|
||||
|
@ -82,37 +94,38 @@ const ComposePoll: React.FC = () => {
|
|||
})}
|
||||
</View>
|
||||
<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
|
||||
{...(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(
|
||||
'content.root.footer.poll.quantity.increase.accessibilityLabel',
|
||||
|
@ -127,7 +140,7 @@ const ComposePoll: React.FC = () => {
|
|||
})}
|
||||
onPress={() => {
|
||||
analytics('compose_poll_increase_press')
|
||||
total < 4 &&
|
||||
total < MAX_OPTIONS &&
|
||||
composeDispatch({
|
||||
type: 'poll',
|
||||
payload: { total: total + 1 }
|
||||
|
@ -136,7 +149,7 @@ const ComposePoll: React.FC = () => {
|
|||
type='icon'
|
||||
content='Plus'
|
||||
round
|
||||
disabled={!(total < 4)}
|
||||
disabled={!(total < MAX_OPTIONS)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.controlOptions}>
|
||||
|
@ -158,7 +171,7 @@ const ComposePoll: React.FC = () => {
|
|||
cancelButtonIndex: 2
|
||||
},
|
||||
index => {
|
||||
if (index < 2) {
|
||||
if (index && index < 2) {
|
||||
analytics('compose_poll_expiration_press', {
|
||||
current: multiple,
|
||||
new: index === 1
|
||||
|
@ -177,6 +190,7 @@ const ComposePoll: React.FC = () => {
|
|||
title={t('content.root.footer.poll.expiration.heading')}
|
||||
content={t(`content.root.footer.poll.expiration.options.${expire}`)}
|
||||
onPress={() => {
|
||||
// @ts-ignore
|
||||
const expirations: [
|
||||
'300',
|
||||
'1800',
|
||||
|
@ -185,7 +199,19 @@ const ComposePoll: React.FC = () => {
|
|||
'86400',
|
||||
'259200',
|
||||
'604800'
|
||||
] = ['300', '1800', '3600', '21600', '86400', '259200', '604800']
|
||||
] = [
|
||||
'300',
|
||||
'1800',
|
||||
'3600',
|
||||
'21600',
|
||||
'86400',
|
||||
'259200',
|
||||
'604800'
|
||||
].filter(
|
||||
expiration =>
|
||||
parseInt(expiration) >= MIN_EXPIRATION &&
|
||||
parseInt(expiration) <= MAX_EXPIRATION
|
||||
)
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
|
@ -197,7 +223,7 @@ const ComposePoll: React.FC = () => {
|
|||
cancelButtonIndex: 7
|
||||
},
|
||||
index => {
|
||||
if (index < 7) {
|
||||
if (index && expirations.length < 7) {
|
||||
analytics('compose_poll_expiration_press', {
|
||||
current: expire,
|
||||
new: expirations[index]
|
||||
|
@ -246,14 +272,15 @@ const styles = StyleSheet.create({
|
|||
controlAmount: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
},
|
||||
controlOptions: {
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
|
||||
},
|
||||
firstButton: {
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
controlCount: {
|
||||
marginHorizontal: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ export interface Props {
|
|||
composeDispatch: Dispatch<ComposeAction>
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (i: number) => void
|
||||
callback: (i?: number | undefined) => void | Promise<void>
|
||||
) => void
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { FetchOptions } from 'react-query/types/core/query'
|
|||
import Autolinker from '@root/modules/autolinker'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { ComposeAction, ComposeState } from './utils/types'
|
||||
import { instanceConfigurationStatusCharsURL } from './Root'
|
||||
|
||||
export interface Params {
|
||||
textInput: ComposeState['textInputFocus']['current']
|
||||
|
@ -92,7 +93,7 @@ const formatText = ({
|
|||
children.push(<TagText key={index} text={main} />)
|
||||
switch (tag.type) {
|
||||
case 'url':
|
||||
contentLength = contentLength + 23
|
||||
contentLength = contentLength + instanceConfigurationStatusCharsURL
|
||||
break
|
||||
case 'accounts':
|
||||
const theMatch = main.match(/@/g)
|
||||
|
|
|
@ -2,11 +2,12 @@ import haptics from '@components/haptics'
|
|||
import { displayMessage } from '@components/Message'
|
||||
import CameraRoll from '@react-native-community/cameraroll'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import i18next from 'i18next'
|
||||
import { RefObject } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import { FileSystem, Permissions } from 'react-native-unimodules'
|
||||
|
||||
type CommonProps = {
|
||||
messageRef: RefObject<FlashMessage>
|
||||
|
@ -60,17 +61,15 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
|
|||
|
||||
const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
|
||||
const fileUri: string = `${FileSystem.documentDirectory}${image.id}.jpg`
|
||||
const downloadedFile: FileSystem.FileSystemDownloadResult = await FileSystem.downloadAsync(
|
||||
image.url,
|
||||
fileUri
|
||||
)
|
||||
const downloadedFile: FileSystem.FileSystemDownloadResult =
|
||||
await FileSystem.downloadAsync(image.url, fileUri)
|
||||
|
||||
if (downloadedFile.status != 200) {
|
||||
console.warn('error!')
|
||||
}
|
||||
|
||||
const perm = await Permissions.askAsync(Permissions.MEDIA_LIBRARY)
|
||||
if (perm.status != 'granted') {
|
||||
const perm = await MediaLibrary.requestPermissionsAsync()
|
||||
if (!perm.granted) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -61,9 +61,11 @@ const HeaderComponent = React.memo(
|
|||
analytics('imageviewer_more_share_press')
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
Share.share({ url: imageUrls[currentIndex].url })
|
||||
await Share.share({ url: imageUrls[currentIndex].url })
|
||||
break
|
||||
case 'android':
|
||||
Share.share({ message: imageUrls[currentIndex].url })
|
||||
await Share.share({ message: imageUrls[currentIndex].url })
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
|
|||
|
||||
const ScreenTabs = React.memo(
|
||||
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
const { mode, theme } = useTheme()
|
||||
const { theme } = useTheme()
|
||||
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instanceAccount = useSelector(
|
||||
|
|
|
@ -21,7 +21,7 @@ const TabLocal = React.memo(
|
|||
|
||||
const screenOptionsRoot = useMemo(
|
||||
() => ({
|
||||
headerTitle: t('tabs.local.name'),
|
||||
title: t('tabs.local.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content={t('tabs.local.name')} />
|
||||
}),
|
||||
|
|
|
@ -38,7 +38,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Bookmarks'
|
||||
component={TabMeBookmarks}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.bookmarks.name'),
|
||||
title: t('me.stacks.bookmarks.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.bookmarks.name')} />
|
||||
|
@ -51,7 +51,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Conversations'
|
||||
component={TabMeConversations}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.conversations.name'),
|
||||
title: t('me.stacks.conversations.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.conversations.name')} />
|
||||
|
@ -64,7 +64,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Favourites'
|
||||
component={TabMeFavourites}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.favourites.name'),
|
||||
title: t('me.stacks.favourites.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.favourites.name')} />
|
||||
|
@ -77,7 +77,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Lists'
|
||||
component={TabMeLists}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.lists.name'),
|
||||
title: t('me.stacks.lists.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.lists.name')} />
|
||||
|
@ -90,7 +90,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Lists-List'
|
||||
component={TabMeListsList}
|
||||
options={({ route, navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.list.name', { list: route.params.title }),
|
||||
title: t('me.stacks.list.name', { list: route.params.title }),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter
|
||||
|
@ -117,7 +117,7 @@ const TabMe = React.memo(
|
|||
options={({ navigation }) => ({
|
||||
presentation: 'modal',
|
||||
headerShown: true,
|
||||
headerTitle: t('me.stacks.push.name'),
|
||||
title: t('me.stacks.push.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.push.name')} />
|
||||
|
@ -135,12 +135,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Settings'
|
||||
component={TabMeSettings}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.settings.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.settings.name')} />
|
||||
)
|
||||
}),
|
||||
title: t('me.stacks.settings.name'),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
|
@ -148,7 +143,7 @@ const TabMe = React.memo(
|
|||
name='Tab-Me-Settings-Fontsize'
|
||||
component={TabMeSettingsFontsize}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('me.stacks.fontSize.name'),
|
||||
title: t('me.stacks.fontSize.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.fontSize.name')} />
|
||||
|
@ -163,7 +158,7 @@ const TabMe = React.memo(
|
|||
options={({ navigation }) => ({
|
||||
presentation: 'modal',
|
||||
headerShown: true,
|
||||
headerTitle: t('me.stacks.switch.name'),
|
||||
title: t('me.stacks.switch.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.switch.name')} />
|
||||
|
|
|
@ -31,7 +31,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({
|
|||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Root'
|
||||
options={{
|
||||
headerTitle: t('me.stacks.profile.name'),
|
||||
title: t('me.stacks.profile.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.profile.name')} />
|
||||
|
@ -56,7 +56,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({
|
|||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Name'
|
||||
options={{
|
||||
headerTitle: t('me.stacks.profileName.name'),
|
||||
title: t('me.stacks.profileName.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.profileName.name')} />
|
||||
|
@ -75,7 +75,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({
|
|||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Note'
|
||||
options={{
|
||||
headerTitle: t('me.stacks.profileNote.name'),
|
||||
title: t('me.stacks.profileNote.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.profileNote.name')} />
|
||||
|
@ -94,7 +94,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({
|
|||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Fields'
|
||||
options={{
|
||||
headerTitle: t('me.stacks.profileFields.name'),
|
||||
title: t('me.stacks.profileFields.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('me.stacks.profileFields.name')} />
|
||||
|
|
|
@ -10,9 +10,11 @@ import { Alert, StyleSheet } from 'react-native'
|
|||
import FlashMessage from 'react-native-flash-message'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
|
||||
const TabMeProfileName: React.FC<TabMeProfileStackScreenProps<
|
||||
'Tab-Me-Profile-Name'
|
||||
> & { messageRef: RefObject<FlashMessage> }> = ({
|
||||
const TabMeProfileName: React.FC<
|
||||
TabMeProfileStackScreenProps<'Tab-Me-Profile-Name'> & {
|
||||
messageRef: RefObject<FlashMessage>
|
||||
}
|
||||
> = ({
|
||||
messageRef,
|
||||
route: {
|
||||
params: { display_name }
|
||||
|
@ -91,7 +93,8 @@ const TabMeProfileName: React.FC<TabMeProfileStackScreenProps<
|
|||
options={{
|
||||
maxLength: 30,
|
||||
autoCapitalize: 'none',
|
||||
autoCompleteType: 'username',
|
||||
autoComplete: 'username',
|
||||
textContentType: 'username',
|
||||
autoCorrect: false
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
||||
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
||||
|
@ -12,14 +13,16 @@ import {
|
|||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { useState, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppState, Linking, ScrollView } from 'react-native'
|
||||
import { AppState, Linking, ScrollView, Text, View } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const TabMePush: React.FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
|
@ -30,6 +33,9 @@ const TabMePush: React.FC = () => {
|
|||
const dispatch = useDispatch()
|
||||
const instancePush = useSelector(getInstancePush)
|
||||
|
||||
const [pushAvailable, setPushAvailable] = useState<boolean | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
||||
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
||||
const checkPush = async () => {
|
||||
|
@ -39,10 +45,15 @@ const TabMePush: React.FC = () => {
|
|||
setPushCanAskAgain(settings.canAskAgain)
|
||||
}
|
||||
useEffect(() => {
|
||||
Notifications.getExpoPushTokenAsync({
|
||||
experienceId: '@xmflsct/tooot'
|
||||
})
|
||||
.then(data => setPushAvailable(!!data))
|
||||
.catch(() => setPushAvailable(false))
|
||||
checkPush()
|
||||
AppState.addEventListener('change', checkPush)
|
||||
const subscription = AppState.addEventListener('change', checkPush)
|
||||
return () => {
|
||||
AppState.removeEventListener('change', checkPush)
|
||||
subscription.remove()
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -54,13 +65,15 @@ const TabMePush: React.FC = () => {
|
|||
|
||||
const alerts = useMemo(() => {
|
||||
return instancePush?.alerts
|
||||
? (['follow', 'favourite', 'reblog', 'mention', 'poll'] as [
|
||||
'follow',
|
||||
'favourite',
|
||||
'reblog',
|
||||
'mention',
|
||||
'poll'
|
||||
]).map(alert => (
|
||||
? (
|
||||
['follow', 'favourite', 'reblog', 'mention', 'poll'] as [
|
||||
'follow',
|
||||
'favourite',
|
||||
'reblog',
|
||||
'mention',
|
||||
'poll'
|
||||
]
|
||||
).map(alert => (
|
||||
<MenuRow
|
||||
key={alert}
|
||||
title={t(`me.push.${alert}.heading`)}
|
||||
|
@ -93,80 +106,108 @@ const TabMePush: React.FC = () => {
|
|||
|
||||
return (
|
||||
<ScrollView>
|
||||
{pushEnabled === false ? (
|
||||
<MenuContainer>
|
||||
<Button
|
||||
type='text'
|
||||
content={
|
||||
pushCanAskAgain
|
||||
? t('me.push.enable.direct')
|
||||
: t('me.push.enable.settings')
|
||||
}
|
||||
style={{
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (pushCanAskAgain) {
|
||||
analytics('me_push_enabled_dialogue')
|
||||
const result = await Notifications.requestPermissionsAsync()
|
||||
setPushEnabled(result.granted)
|
||||
setPushCanAskAgain(result.canAskAgain)
|
||||
} else {
|
||||
analytics('me_push_enabled_setting')
|
||||
Linking.openSettings()
|
||||
{pushAvailable !== false ? (
|
||||
<>
|
||||
{pushEnabled === false ? (
|
||||
<MenuContainer>
|
||||
<Button
|
||||
type='text'
|
||||
content={
|
||||
pushCanAskAgain
|
||||
? t('me.push.enable.direct')
|
||||
: t('me.push.enable.settings')
|
||||
}
|
||||
style={{
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginHorizontal:
|
||||
StyleConstants.Spacing.Global.PagePadding * 2
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (pushCanAskAgain) {
|
||||
analytics('me_push_enabled_dialogue')
|
||||
const result = await Notifications.requestPermissionsAsync()
|
||||
setPushEnabled(result.granted)
|
||||
setPushCanAskAgain(result.canAskAgain)
|
||||
} else {
|
||||
analytics('me_push_enabled_setting')
|
||||
Linking.openSettings()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
) : null}
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.global.heading', {
|
||||
acct: `@${instanceAccount?.acct}@${instanceUri}`
|
||||
})}
|
||||
description={t('me.push.global.description')}
|
||||
loading={instancePush?.global.loading}
|
||||
switchDisabled={!pushEnabled || isLoading}
|
||||
switchValue={
|
||||
pushEnabled === false ? false : instancePush?.global.value
|
||||
}
|
||||
}}
|
||||
switchOnValueChange={() => {
|
||||
analytics('me_push_global', {
|
||||
current: instancePush?.global.value,
|
||||
new: !instancePush?.global.value
|
||||
})
|
||||
dispatch(updateInstancePush(!instancePush?.global.value))
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.decode.heading')}
|
||||
description={t('me.push.decode.description')}
|
||||
loading={instancePush?.decode.loading}
|
||||
switchDisabled={
|
||||
!pushEnabled || !instancePush?.global.value || isLoading
|
||||
}
|
||||
switchValue={instancePush?.decode.value}
|
||||
switchOnValueChange={() => {
|
||||
analytics('me_push_decode', {
|
||||
current: instancePush?.decode.value,
|
||||
new: !instancePush?.decode.value
|
||||
})
|
||||
dispatch(updateInstancePushDecode(!instancePush?.decode.value))
|
||||
}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.push.howitworks')}
|
||||
iconBack='ExternalLink'
|
||||
onPress={() => {
|
||||
analytics('me_push_howitworks')
|
||||
WebBrowser.openBrowserAsync('https://tooot.app/how-push-works')
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>{alerts}</MenuContainer>
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name='Frown'
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme.primaryDefault}
|
||||
/>
|
||||
</MenuContainer>
|
||||
) : null}
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.global.heading', {
|
||||
acct: `@${instanceAccount?.acct}@${instanceUri}`
|
||||
})}
|
||||
description={t('me.push.global.description')}
|
||||
loading={instancePush?.global.loading}
|
||||
switchDisabled={!pushEnabled || isLoading}
|
||||
switchValue={
|
||||
pushEnabled === false ? false : instancePush?.global.value
|
||||
}
|
||||
switchOnValueChange={() => {
|
||||
analytics('me_push_global', {
|
||||
current: instancePush?.global.value,
|
||||
new: !instancePush?.global.value
|
||||
})
|
||||
dispatch(updateInstancePush(!instancePush?.global.value))
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.decode.heading')}
|
||||
description={t('me.push.decode.description')}
|
||||
loading={instancePush?.decode.loading}
|
||||
switchDisabled={
|
||||
!pushEnabled || !instancePush?.global.value || isLoading
|
||||
}
|
||||
switchValue={instancePush?.decode.value}
|
||||
switchOnValueChange={() => {
|
||||
analytics('me_push_decode', {
|
||||
current: instancePush?.decode.value,
|
||||
new: !instancePush?.decode.value
|
||||
})
|
||||
dispatch(updateInstancePushDecode(!instancePush?.decode.value))
|
||||
}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.push.howitworks')}
|
||||
iconBack='ExternalLink'
|
||||
onPress={() => {
|
||||
analytics('me_push_howitworks')
|
||||
WebBrowser.openBrowserAsync('https://tooot.app/how-push-works')
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>{alerts}</MenuContainer>
|
||||
<Text
|
||||
style={{
|
||||
...StyleConstants.FontStyle.M,
|
||||
color: theme.primaryDefault
|
||||
}}
|
||||
>
|
||||
{t('me.push.notAvailable')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,18 +2,36 @@ import { MenuContainer, MenuRow } from '@components/Menu'
|
|||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import React from 'react'
|
||||
import { getMePage, updateContextMePage } from '@utils/slices/contextsSlice'
|
||||
import { getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const Collections: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const mePage = useSelector(
|
||||
getMePage,
|
||||
(a, b) => a.announcements.unread === b.announcements.unread
|
||||
)
|
||||
|
||||
const listsQuery = useListsQuery({
|
||||
options: {
|
||||
notifyOnChangeProps: ['data']
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (listsQuery.isSuccess) {
|
||||
dispatch(
|
||||
updateContextMePage({
|
||||
lists: { shown: listsQuery.data?.length ? true : false }
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [listsQuery.isSuccess, listsQuery.data?.length])
|
||||
|
||||
const announcementsQuery = useAnnouncementQuery({
|
||||
showAll: true,
|
||||
|
@ -21,6 +39,25 @@ const Collections: React.FC = () => {
|
|||
notifyOnChangeProps: ['data']
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (announcementsQuery.isSuccess) {
|
||||
dispatch(
|
||||
updateContextMePage({
|
||||
announcements: {
|
||||
shown: announcementsQuery.data?.length ? true : false,
|
||||
unread: announcementsQuery.data.filter(
|
||||
announcement => !announcement.read
|
||||
).length
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [announcementsQuery.isSuccess, announcementsQuery.data?.length])
|
||||
|
||||
const instancePush = useSelector(
|
||||
getInstancePush,
|
||||
(prev, next) => prev?.global.value === next?.global.value
|
||||
)
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
|
@ -42,7 +79,7 @@ const Collections: React.FC = () => {
|
|||
title={t('me.stacks.favourites.name')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||
/>
|
||||
{listsQuery.data?.length ? (
|
||||
{mePage.lists.shown ? (
|
||||
<MenuRow
|
||||
iconFront='List'
|
||||
iconBack='ChevronRight'
|
||||
|
@ -50,18 +87,15 @@ const Collections: React.FC = () => {
|
|||
onPress={() => navigation.navigate('Tab-Me-Lists')}
|
||||
/>
|
||||
) : null}
|
||||
{announcementsQuery.data?.length ? (
|
||||
{mePage.announcements.shown ? (
|
||||
<MenuRow
|
||||
iconFront='Clipboard'
|
||||
iconBack='ChevronRight'
|
||||
title={t('screenAnnouncements:heading')}
|
||||
content={
|
||||
announcementsQuery.data.filter(announcement => !announcement.read)
|
||||
.length
|
||||
mePage.announcements.unread
|
||||
? t('me.root.announcements.content.unread', {
|
||||
amount: announcementsQuery.data.filter(
|
||||
announcement => !announcement.read
|
||||
).length
|
||||
amount: mePage.announcements.unread
|
||||
})
|
||||
: t('me.root.announcements.content.read')
|
||||
}
|
||||
|
@ -70,6 +104,17 @@ const Collections: React.FC = () => {
|
|||
}
|
||||
/>
|
||||
) : null}
|
||||
<MenuRow
|
||||
iconFront={instancePush ? 'Bell' : 'BellOff'}
|
||||
iconBack='ChevronRight'
|
||||
title={t('me.stacks.push.name')}
|
||||
content={
|
||||
instancePush.global.value
|
||||
? t('me.root.push.content.enabled')
|
||||
: t('me.root.push.content.disabled')
|
||||
}
|
||||
onPress={() => navigation.navigate('Tab-Me-Push')}
|
||||
/>
|
||||
</MenuContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import {
|
|||
} from '@utils/slices/settingsSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Constants from 'expo-constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const SettingsAnalytics: React.FC = () => {
|
||||
|
|
|
@ -64,6 +64,7 @@ const SettingsApp: React.FC = () => {
|
|||
cancelButtonIndex: options.length - 1
|
||||
},
|
||||
buttonIndex => {
|
||||
if (!buttonIndex) return
|
||||
if (buttonIndex < options.length - 1) {
|
||||
analytics('settings_language_press', {
|
||||
current: i18n.language,
|
||||
|
|
|
@ -32,9 +32,9 @@ export const mapFontsizeToName = (size: SettingsState['fontsize']) => {
|
|||
}
|
||||
}
|
||||
|
||||
const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<
|
||||
'Tab-Me-Settings-Fontsize'
|
||||
>> = () => {
|
||||
const TabMeSettingsFontsize: React.FC<
|
||||
TabMeStackScreenProps<'Tab-Me-Settings-Fontsize'>
|
||||
> = () => {
|
||||
const { mode, theme } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const initialSize = useSelector(getSettingsFontsize)
|
||||
|
@ -113,7 +113,12 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<
|
|||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
/>
|
||||
<TimelineDefault item={item} disableDetails disableOnPress />
|
||||
<TimelineDefault
|
||||
// @ts-ignore
|
||||
item={item}
|
||||
disableDetails
|
||||
disableOnPress
|
||||
/>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
|
|
|
@ -19,7 +19,7 @@ const TabNotifications = React.memo(
|
|||
|
||||
const screenOptionsRoot = useMemo(
|
||||
() => ({
|
||||
headerTitle: t('tabs.notifications.name'),
|
||||
title: t('tabs.notifications.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('tabs.notifications.name')} />
|
||||
|
|
|
@ -3,10 +3,7 @@ import Button from '@components/Button'
|
|||
import { RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstancePush
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -56,7 +53,6 @@ const AccountInformationActions: React.FC<Props> = ({ account, myInfo }) => {
|
|||
content={t('shared.account.moved')}
|
||||
onPress={() => {
|
||||
analytics('account_gotomoved_press')
|
||||
// @ts-ignore
|
||||
navigation.push('Tab-Shared-Account', { account: accountMoved })
|
||||
}}
|
||||
/>
|
||||
|
@ -64,21 +60,9 @@ const AccountInformationActions: React.FC<Props> = ({ account, myInfo }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const instancePush = useSelector(
|
||||
getInstancePush,
|
||||
(prev, next) => prev?.global.value === next?.global.value
|
||||
)
|
||||
|
||||
if (myInfo) {
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<Button
|
||||
round
|
||||
type='icon'
|
||||
content={instancePush?.global.value ? 'Bell' : 'BellOff'}
|
||||
style={styles.actionLeft}
|
||||
onPress={() => navigation.navigate('Tab-Me-Push')}
|
||||
/>
|
||||
<Button
|
||||
type='text'
|
||||
disabled={account === undefined}
|
||||
|
|
|
@ -41,7 +41,7 @@ const TabSharedRoot = ({
|
|||
headerStyle: {
|
||||
backgroundColor: `rgba(255, 255, 255, 0)`
|
||||
},
|
||||
headerTitle: '',
|
||||
title: '',
|
||||
headerLeft: () => (
|
||||
<HeaderLeft onPress={() => navigation.goBack()} background />
|
||||
)
|
||||
|
@ -91,14 +91,7 @@ const TabSharedRoot = ({
|
|||
options={({
|
||||
route
|
||||
}: TabSharedStackScreenProps<'Tab-Shared-Hashtag'>) => ({
|
||||
headerTitle: `#${decodeURIComponent(route.params.hashtag)}`,
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter
|
||||
content={`#${decodeURIComponent(route.params.hashtag)}`}
|
||||
/>
|
||||
)
|
||||
})
|
||||
title: `#${decodeURIComponent(route.params.hashtag)}`
|
||||
})}
|
||||
/>
|
||||
|
||||
|
@ -109,6 +102,13 @@ const TabSharedRoot = ({
|
|||
options={({
|
||||
navigation
|
||||
}: TabSharedStackScreenProps<'Tab-Shared-Search'>) => ({
|
||||
...(Platform.OS === 'ios'
|
||||
? {
|
||||
headerLeft: () => (
|
||||
<HeaderLeft onPress={() => navigation.goBack()} />
|
||||
)
|
||||
}
|
||||
: { headerLeft: () => null }),
|
||||
headerTitle: () => {
|
||||
const onChangeText = debounce(
|
||||
(text: string) => navigation.setParams({ text }),
|
||||
|
@ -164,10 +164,7 @@ const TabSharedRoot = ({
|
|||
name='Tab-Shared-Toot'
|
||||
component={TabSharedToot}
|
||||
options={{
|
||||
headerTitle: t('shared.toot.name'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content={t('shared.toot.name')} />
|
||||
})
|
||||
title: t('shared.toot.name')
|
||||
}}
|
||||
/>
|
||||
|
||||
|
@ -180,7 +177,7 @@ const TabSharedRoot = ({
|
|||
params: { reference, type, count }
|
||||
}
|
||||
}: TabSharedStackScreenProps<'Tab-Shared-Users'>) => ({
|
||||
headerTitle: t(`shared.users.${reference}.${type}`, { count }),
|
||||
title: t(`shared.users.${reference}.${type}`, { count }),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter
|
||||
|
|
|
@ -18,9 +18,9 @@ import {
|
|||
} from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
|
||||
const TabSharedSearch: React.FC<TabSharedStackScreenProps<
|
||||
'Tab-Shared-Search'
|
||||
>> = ({
|
||||
const TabSharedSearch: React.FC<
|
||||
TabSharedStackScreenProps<'Tab-Shared-Search'>
|
||||
> = ({
|
||||
route: {
|
||||
params: { text }
|
||||
}
|
||||
|
@ -33,7 +33,13 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<
|
|||
hashtags: t('shared.search.sections.hashtags'),
|
||||
statuses: t('shared.search.sections.statuses')
|
||||
}
|
||||
const { status, data } = useSearchQuery({
|
||||
const { status, data } = useSearchQuery<
|
||||
{
|
||||
title: string
|
||||
translation: string
|
||||
data: any[]
|
||||
}[]
|
||||
>({
|
||||
term: text,
|
||||
options: {
|
||||
enabled: text !== undefined,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Constants from 'expo-constants'
|
||||
import * as Updates from 'expo-updates'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
import log from './log'
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import createSecureStore from '@neverdull-agency/expo-unlimited-secure-store'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { AnyAction, configureStore, Reducer } from '@reduxjs/toolkit'
|
||||
import contextsMigration from '@utils/migrations/contexts/migration'
|
||||
import instancesMigration from '@utils/migrations/instances/migration'
|
||||
import contextsSlice, { ContextsState } from '@utils/slices/contextsSlice'
|
||||
import instancesSlice, { InstancesState } from '@utils/slices/instancesSlice'
|
||||
|
@ -15,14 +16,17 @@ const prefix = 'tooot'
|
|||
const contextsPersistConfig = {
|
||||
key: 'contexts',
|
||||
prefix,
|
||||
storage: AsyncStorage
|
||||
storage: AsyncStorage,
|
||||
version: 1,
|
||||
// @ts-ignore
|
||||
migrate: createMigrate(contextsMigration)
|
||||
}
|
||||
|
||||
const instancesPersistConfig = {
|
||||
key: 'instances',
|
||||
prefix,
|
||||
storage: secureStorage,
|
||||
version: 5,
|
||||
version: 6,
|
||||
// @ts-ignore
|
||||
migrate: createMigrate(instancesMigration)
|
||||
}
|
||||
|
|
|
@ -33,24 +33,18 @@ const AccessibilityManager: React.FC = ({ children }) => {
|
|||
useEffect(() => {
|
||||
loadAccessibilityInfo()
|
||||
|
||||
AccessibilityInfo.addEventListener(
|
||||
const reduceMotionSubscription = AccessibilityInfo.addEventListener(
|
||||
'reduceMotionChanged',
|
||||
handleReduceMotionChanged
|
||||
)
|
||||
AccessibilityInfo.addEventListener(
|
||||
const screenReaderSubscription = AccessibilityInfo.addEventListener(
|
||||
'screenReaderChanged',
|
||||
handleScreenReaderEnabled
|
||||
)
|
||||
|
||||
return () => {
|
||||
AccessibilityInfo.removeEventListener(
|
||||
'reduceMotionChanged',
|
||||
handleReduceMotionChanged
|
||||
)
|
||||
AccessibilityInfo.removeEventListener(
|
||||
'screenReaderChanged',
|
||||
handleScreenReaderEnabled
|
||||
)
|
||||
reduceMotionSubscription.remove()
|
||||
screenReaderSubscription.remove()
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { ContextsV0 } from './v0'
|
||||
|
||||
const contextsMigration = {
|
||||
1: (state: ContextsV0) => {
|
||||
return (state = {
|
||||
...state,
|
||||
// @ts-ignore
|
||||
mePage: {
|
||||
lists: { shown: false },
|
||||
announcements: { shown: false, unread: 0 }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default contextsMigration
|
|
@ -0,0 +1,13 @@
|
|||
export type ContextsV0 = {
|
||||
storeReview: {
|
||||
context: Readonly<number>
|
||||
current: number
|
||||
shown: boolean
|
||||
}
|
||||
publicRemoteNotice: {
|
||||
context: Readonly<number>
|
||||
current: number
|
||||
hidden: boolean
|
||||
}
|
||||
previousTab: 'Tab-Local' | 'Tab-Public' | 'Tab-Notifications' | 'Tab-Me'
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { InstanceV3 } from './v3'
|
||||
import { InstanceV4 } from './v4'
|
||||
import { InstanceV5 } from './v5'
|
||||
|
||||
const instancesMigration = {
|
||||
4: (state: InstanceV3) => {
|
||||
|
@ -27,7 +28,6 @@ const instancesMigration = {
|
|||
}
|
||||
},
|
||||
5: (state: InstanceV4) => {
|
||||
// Migration is run on each start, don't know why
|
||||
// @ts-ignore
|
||||
if (state.instances.length && !state.instances[0].notifications_filter) {
|
||||
return {
|
||||
|
@ -47,6 +47,13 @@ const instancesMigration = {
|
|||
} else {
|
||||
return state
|
||||
}
|
||||
},
|
||||
6: (state: InstanceV5) => {
|
||||
return {
|
||||
instances: state.instances.map(instance => {
|
||||
return { ...instance, configuration: undefined }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||
|
||||
type Instance = {
|
||||
active: boolean
|
||||
appData: {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
}
|
||||
url: string
|
||||
token: string
|
||||
uri: Mastodon.Instance['uri']
|
||||
urls: Mastodon.Instance['urls']
|
||||
max_toot_chars: number
|
||||
account: {
|
||||
id: Mastodon.Account['id']
|
||||
acct: Mastodon.Account['acct']
|
||||
avatarStatic: Mastodon.Account['avatar_static']
|
||||
preferences: Mastodon.Preferences
|
||||
}
|
||||
filters: Mastodon.Filter[]
|
||||
notifications_filter: {
|
||||
follow: boolean
|
||||
favourite: boolean
|
||||
reblog: boolean
|
||||
mention: boolean
|
||||
poll: boolean
|
||||
follow_request: boolean
|
||||
}
|
||||
push:
|
||||
| {
|
||||
global: { loading: boolean; value: boolean }
|
||||
decode: { loading: boolean; value: true }
|
||||
alerts: {
|
||||
follow: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['follow']
|
||||
}
|
||||
favourite: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||
}
|
||||
reblog: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||
}
|
||||
mention: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['mention']
|
||||
}
|
||||
poll: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['poll']
|
||||
}
|
||||
}
|
||||
keys: {
|
||||
auth: string
|
||||
public: string
|
||||
private: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
global: { loading: boolean; value: boolean }
|
||||
decode: { loading: boolean; value: false }
|
||||
alerts: {
|
||||
follow: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['follow']
|
||||
}
|
||||
favourite: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||
}
|
||||
reblog: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||
}
|
||||
mention: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['mention']
|
||||
}
|
||||
poll: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['poll']
|
||||
}
|
||||
}
|
||||
keys: undefined
|
||||
}
|
||||
drafts: ComposeStateDraft[]
|
||||
}
|
||||
|
||||
export type InstanceV5 = {
|
||||
instances: Instance[]
|
||||
}
|
|
@ -25,12 +25,8 @@ const pushUseConnect = ({ mode, t, instances, dispatch }: Params) => {
|
|||
).data
|
||||
|
||||
apiTooot({
|
||||
method: 'post',
|
||||
service: 'push',
|
||||
url: 'connect',
|
||||
body: {
|
||||
expoToken
|
||||
},
|
||||
method: 'get',
|
||||
url: `push/connect/${expoToken}`,
|
||||
sentry: true
|
||||
}).catch(error => {
|
||||
if (error.status == 410) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKeyAccount = ['Account', { id: Mastodon.Account['id'] }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyAccount }) => {
|
||||
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyAccount>) => {
|
||||
const { id } = queryKey[1]
|
||||
|
||||
return apiInstance<Mastodon.Account>({
|
||||
|
@ -13,11 +13,11 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyAccount }) => {
|
|||
}).then(res => res.body)
|
||||
}
|
||||
|
||||
const useAccountQuery = <TData = Mastodon.Account>({
|
||||
const useAccountQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyAccount[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Account, AxiosError, TData>
|
||||
options?: UseQueryOptions<Mastodon.Account, AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyAccount = ['Account', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
|
@ -9,7 +10,9 @@ import {
|
|||
|
||||
type QueryKeyAnnouncement = ['Announcements', { showAll?: boolean }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
|
||||
const queryFunction = ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyAnnouncement>) => {
|
||||
const { showAll } = queryKey[1]
|
||||
|
||||
return apiInstance<Mastodon.Announcement[]>({
|
||||
|
@ -23,11 +26,11 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
|
|||
}).then(res => res.body)
|
||||
}
|
||||
|
||||
const useAnnouncementQuery = <TData = Mastodon.Announcement[]>({
|
||||
const useAnnouncementQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyAnnouncement[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Announcement[], AxiosError, TData>
|
||||
options?: UseQueryOptions<Mastodon.Announcement[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyAnnouncement = [
|
||||
'Announcements',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import apiGeneral from '@api/general'
|
||||
import { AxiosError } from 'axios'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Apps', { domain?: string }]
|
||||
export type QueryKeyApps = ['Apps', { domain?: string }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
||||
const redirectUri = AuthSession.makeRedirectUri({
|
||||
native: 'tooot://instance-auth',
|
||||
useProxy: false
|
||||
|
@ -27,13 +27,13 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
|||
}).then(res => res.body)
|
||||
}
|
||||
|
||||
const useAppsQuery = <TData = Mastodon.Apps>({
|
||||
const useAppsQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Apps, AxiosError, TData>
|
||||
}: QueryKeyApps[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Apps, AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Apps', { ...queryKeyParams }]
|
||||
const queryKey: QueryKeyApps = ['Apps', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,21 +2,22 @@ import apiInstance from '@api/instance'
|
|||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
type QueryKey = ['Emojis']
|
||||
type QueryKeyEmojis = ['Emojis']
|
||||
|
||||
const queryFunction = () => {
|
||||
return apiInstance<Mastodon.Emoji[]>({
|
||||
const queryFunction = async () => {
|
||||
const res = await apiInstance<Mastodon.Emoji[]>({
|
||||
method: 'get',
|
||||
url: 'custom_emojis'
|
||||
}).then(res => res.body)
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useEmojisQuery = <TData = Mastodon.Emoji[]>({
|
||||
const useEmojisQuery = ({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<Mastodon.Emoji[], AxiosError, TData>
|
||||
options?: UseQueryOptions<Mastodon.Emoji[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Emojis']
|
||||
const queryKey: QueryKeyEmojis = ['Emojis']
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import apiGeneral from '@api/general'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Instance', { domain?: string }]
|
||||
export type QueryKeyInstance = ['Instance', { domain?: string }]
|
||||
|
||||
const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const queryFunction = async ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyInstance>) => {
|
||||
const { domain } = queryKey[1]
|
||||
if (!domain) {
|
||||
return Promise.reject()
|
||||
|
@ -18,19 +20,16 @@ const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
|
|||
return res.body
|
||||
}
|
||||
|
||||
const useInstanceQuery = <
|
||||
TData = Mastodon.Instance & { publicAllow?: boolean }
|
||||
>({
|
||||
const useInstanceQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
}: QueryKeyInstance[1] & {
|
||||
options?: UseQueryOptions<
|
||||
Mastodon.Instance & { publicAllow?: boolean },
|
||||
AxiosError,
|
||||
TData
|
||||
AxiosError
|
||||
>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Instance', { ...queryKeyParams }]
|
||||
const queryKey: QueryKeyInstance = ['Instance', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,17 +4,18 @@ import { useQuery, UseQueryOptions } from 'react-query'
|
|||
|
||||
export type QueryKey = ['Lists']
|
||||
|
||||
const queryFunction = () => {
|
||||
return apiInstance<Mastodon.List[]>({
|
||||
const queryFunction = async () => {
|
||||
const res = await apiInstance<Mastodon.List[]>({
|
||||
method: 'get',
|
||||
url: 'lists'
|
||||
}).then(res => res.body)
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useListsQuery = <TData = Mastodon.List[]>({
|
||||
const useListsQuery = ({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<Mastodon.List[], AxiosError, TData>
|
||||
options?: UseQueryOptions<Mastodon.List[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Lists']
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
|
|
|
@ -14,17 +14,18 @@ type AccountWithSource = Mastodon.Account &
|
|||
type QueryKeyProfile = ['Profile']
|
||||
const queryKey: QueryKeyProfile = ['Profile']
|
||||
|
||||
const queryFunction = () => {
|
||||
return apiInstance<AccountWithSource>({
|
||||
const queryFunction = async () => {
|
||||
const res = await apiInstance<AccountWithSource>({
|
||||
method: 'get',
|
||||
url: `accounts/verify_credentials`
|
||||
}).then(res => res.body)
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useProfileQuery = <TData = AccountWithSource>({
|
||||
const useProfileQuery = ({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<AccountWithSource, AxiosError, TData>
|
||||
options?: UseQueryOptions<AccountWithSource, AxiosError>
|
||||
}) => {
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
|
@ -12,7 +13,9 @@ export type QueryKeyRelationship = [
|
|||
{ id: Mastodon.Account['id'] }
|
||||
]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
|
||||
const queryFunction = ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyRelationship>) => {
|
||||
const { id } = queryKey[1]
|
||||
|
||||
return apiInstance<Mastodon.Relationship[]>({
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = [
|
||||
export type QueryKeySearch = [
|
||||
'Search',
|
||||
{
|
||||
type?: 'accounts' | 'hashtags' | 'statuses'
|
||||
|
@ -17,9 +17,11 @@ export type SearchResult = {
|
|||
statuses: Mastodon.Status[]
|
||||
}
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const queryFunction = async ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeySearch>) => {
|
||||
const { type, term, limit = 20 } = queryKey[1]
|
||||
return apiInstance<SearchResult>({
|
||||
const res = await apiInstance<SearchResult>({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
url: 'search',
|
||||
|
@ -29,16 +31,17 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
|||
limit,
|
||||
resolve: true
|
||||
}
|
||||
}).then(res => res.body)
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useSearchQuery = <TData = SearchResult>({
|
||||
const useSearchQuery = <T = unknown>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
options?: UseQueryOptions<SearchResult, AxiosError, TData>
|
||||
}: QueryKeySearch[1] & {
|
||||
options?: UseQueryOptions<SearchResult, AxiosError, T>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Search', { ...queryKeyParams }]
|
||||
const queryKey: QueryKeySearch = ['Search', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import apiInstance, { InstanceResponse } from '@api/instance'
|
||||
import haptics from '@components/haptics'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import { store } from '@root/store'
|
||||
|
@ -7,6 +7,7 @@ import { AxiosError } from 'axios'
|
|||
import { uniqBy } from 'lodash'
|
||||
import {
|
||||
MutationOptions,
|
||||
QueryFunctionContext,
|
||||
useInfiniteQuery,
|
||||
UseInfiniteQueryOptions,
|
||||
useMutation
|
||||
|
@ -28,10 +29,7 @@ export type QueryKeyTimeline = [
|
|||
const queryFunction = async ({
|
||||
queryKey,
|
||||
pageParam
|
||||
}: {
|
||||
queryKey: QueryKeyTimeline
|
||||
pageParam?: { [key: string]: string }
|
||||
}) => {
|
||||
}: QueryFunctionContext<QueryKeyTimeline>) => {
|
||||
const { page, account, hashtag, list, toot } = queryKey[1]
|
||||
let params: { [key: string]: string } = { ...pageParam }
|
||||
|
||||
|
@ -191,21 +189,15 @@ const queryFunction = async ({
|
|||
|
||||
type Unpromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never
|
||||
export type TimelineData = Unpromise<ReturnType<typeof queryFunction>>
|
||||
const useTimelineQuery = <TData = TimelineData>({
|
||||
const useTimelineQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyTimeline[1] & {
|
||||
options?: UseInfiniteQueryOptions<
|
||||
{
|
||||
body:
|
||||
| Mastodon.Status[]
|
||||
| Mastodon.Notification[]
|
||||
| Mastodon.Conversation[]
|
||||
links?: { prev?: string; next?: string }
|
||||
pinned?: Mastodon.Status['id'][]
|
||||
},
|
||||
AxiosError,
|
||||
TData
|
||||
InstanceResponse<
|
||||
Mastodon.Status[] | Mastodon.Notification[] | Mastodon.Conversation[]
|
||||
>,
|
||||
AxiosError
|
||||
>
|
||||
}) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import apiTooot from '@api/tooot'
|
||||
import haptics from '@components/haptics'
|
||||
import { AxiosError } from 'axios'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
type Translations = {
|
||||
provider: string
|
||||
|
@ -13,32 +12,21 @@ type Translations = {
|
|||
export type QueryKeyTranslate = [
|
||||
'Translate',
|
||||
{
|
||||
uri: string
|
||||
source: string
|
||||
target: string
|
||||
text: string[]
|
||||
}
|
||||
]
|
||||
|
||||
const queryFunction = async ({ queryKey }: { queryKey: QueryKeyTranslate }) => {
|
||||
const { uri, source, target, text } = queryKey[1]
|
||||
|
||||
const uriEncoded = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
uri.replace(/https?:\/\//, ''),
|
||||
{ encoding: Crypto.CryptoEncoding.HEX }
|
||||
)
|
||||
const original = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
JSON.stringify({ source, text }),
|
||||
{ encoding: Crypto.CryptoEncoding.HEX }
|
||||
)
|
||||
const queryFunction = async ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyTranslate>) => {
|
||||
const { source, target, text } = queryKey[1]
|
||||
|
||||
const res = await apiTooot<Translations>({
|
||||
method: 'get',
|
||||
service: 'translate',
|
||||
url: `source/${uriEncoded}/target/${target}`,
|
||||
headers: { original }
|
||||
method: 'post',
|
||||
url: 'translate',
|
||||
body: { source, target, text }
|
||||
})
|
||||
haptics('Light')
|
||||
return res.body
|
||||
|
@ -48,7 +36,7 @@ const useTranslateQuery = ({
|
|||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyTranslate[1] & {
|
||||
options?: UseQueryOptions<Translations, AxiosError, Translations>
|
||||
options?: UseQueryOptions<Translations, AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyTranslate = ['Translate', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, { ...options, retry: false })
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import apiInstance, { InstanceResponse } from '@api/instance'
|
||||
import { TabSharedStackParamList } from '@utils/navigation/navigators'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
useInfiniteQuery,
|
||||
UseInfiniteQueryOptions
|
||||
} from 'react-query'
|
||||
|
||||
export type QueryKeyUsers = [
|
||||
'Users',
|
||||
|
@ -11,10 +15,7 @@ export type QueryKeyUsers = [
|
|||
const queryFunction = ({
|
||||
queryKey,
|
||||
pageParam
|
||||
}: {
|
||||
queryKey: QueryKeyUsers
|
||||
pageParam?: { [key: string]: string }
|
||||
}) => {
|
||||
}: QueryFunctionContext<QueryKeyUsers>) => {
|
||||
const { reference, id, type } = queryKey[1]
|
||||
let params: { [key: string]: string } = { ...pageParam }
|
||||
|
||||
|
@ -30,15 +31,8 @@ const useUsersQuery = ({
|
|||
...queryKeyParams
|
||||
}: QueryKeyUsers[1] & {
|
||||
options?: UseInfiniteQueryOptions<
|
||||
{
|
||||
body: Mastodon.Account[]
|
||||
links?: { prev?: string; next?: string }
|
||||
},
|
||||
AxiosError,
|
||||
{
|
||||
body: Mastodon.Account[]
|
||||
links?: { prev?: string; next?: string }
|
||||
}
|
||||
InstanceResponse<Mastodon.Account[]>,
|
||||
AxiosError
|
||||
>
|
||||
}) => {
|
||||
const queryKey: QueryKeyUsers = ['Users', { ...queryKeyParams }]
|
||||
|
|
|
@ -17,6 +17,10 @@ export type ContextsState = {
|
|||
hidden: boolean
|
||||
}
|
||||
previousTab: 'Tab-Local' | 'Tab-Public' | 'Tab-Notifications' | 'Tab-Me'
|
||||
mePage: {
|
||||
lists: { shown: boolean }
|
||||
announcements: { shown: boolean; unread: number }
|
||||
}
|
||||
}
|
||||
|
||||
export const contextsInitialState = {
|
||||
|
@ -32,7 +36,11 @@ export const contextsInitialState = {
|
|||
current: 0,
|
||||
hidden: false
|
||||
},
|
||||
previousTab: 'Tab-Me'
|
||||
previousTab: 'Tab-Me',
|
||||
mePage: {
|
||||
lists: { shown: false },
|
||||
announcements: { shown: false, unread: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
const contextsSlice = createSlice({
|
||||
|
@ -61,6 +69,12 @@ const contextsSlice = createSlice({
|
|||
action: PayloadAction<ContextsState['previousTab']>
|
||||
) => {
|
||||
state.previousTab = action.payload
|
||||
},
|
||||
updateContextMePage: (
|
||||
state,
|
||||
action: PayloadAction<Partial<ContextsState['mePage']>>
|
||||
) => {
|
||||
state.mePage = { ...state.mePage, ...action.payload }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -68,10 +82,13 @@ const contextsSlice = createSlice({
|
|||
export const getPublicRemoteNotice = (state: RootState) =>
|
||||
state.contexts.publicRemoteNotice
|
||||
export const getPreviousTab = (state: RootState) => state.contexts.previousTab
|
||||
export const getMePage = (state: RootState) => state.contexts.mePage
|
||||
export const getContexts = (state: RootState) => state.contexts
|
||||
|
||||
export const {
|
||||
updateStoreReview,
|
||||
updatePublicRemoteNotice,
|
||||
updatePreviousTab
|
||||
updatePreviousTab,
|
||||
updateContextMePage
|
||||
} = contextsSlice.actions
|
||||
export default contextsSlice.reducer
|
||||
|
|
|
@ -9,13 +9,11 @@ const addInstance = createAsyncThunk(
|
|||
domain,
|
||||
token,
|
||||
instance,
|
||||
max_toot_chars = 500,
|
||||
appData
|
||||
}: {
|
||||
domain: Instance['url']
|
||||
token: Instance['token']
|
||||
instance: Mastodon.Instance
|
||||
max_toot_chars?: number
|
||||
appData: Instance['appData']
|
||||
}): Promise<{ type: 'add' | 'overwrite'; data: Instance }> => {
|
||||
const { store } = require('@root/store')
|
||||
|
@ -70,13 +68,18 @@ const addInstance = createAsyncThunk(
|
|||
token,
|
||||
uri: instance.uri,
|
||||
urls: instance.urls,
|
||||
max_toot_chars,
|
||||
account: {
|
||||
id,
|
||||
acct,
|
||||
avatarStatic: avatar_static,
|
||||
preferences
|
||||
},
|
||||
...(instance.max_toot_chars && {
|
||||
max_toot_chars: instance.max_toot_chars
|
||||
}),
|
||||
...(instance.configuration && {
|
||||
configuration: instance.configuration
|
||||
}),
|
||||
filters,
|
||||
notifications_filter: {
|
||||
follow: true,
|
||||
|
@ -96,7 +99,7 @@ const addInstance = createAsyncThunk(
|
|||
mention: { loading: false, value: true },
|
||||
poll: { loading: false, value: true }
|
||||
},
|
||||
keys: undefined
|
||||
keys: { auth: undefined, public: undefined, private: undefined }
|
||||
},
|
||||
drafts: []
|
||||
}
|
||||
|
|
|
@ -1,53 +1,34 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import apiTooot from '@api/tooot'
|
||||
import apiTooot, { TOOOT_API_DOMAIN } from '@api/tooot'
|
||||
import i18n from '@root/i18n/i18n'
|
||||
import { RootState } from '@root/store'
|
||||
import { getInstance, Instance } from '@utils/slices/instancesSlice'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import * as Random from 'expo-random'
|
||||
import { Platform } from 'react-native'
|
||||
import base64 from 'react-native-base64'
|
||||
import androidDefaults from './androidDefaults'
|
||||
|
||||
const register1 = async ({
|
||||
const subscribe = async ({
|
||||
expoToken,
|
||||
instanceUrl,
|
||||
accountId,
|
||||
accountFull
|
||||
accountFull,
|
||||
serverKey,
|
||||
auth
|
||||
}: {
|
||||
expoToken: string
|
||||
instanceUrl: string
|
||||
accountId: Mastodon.Account['id']
|
||||
accountFull: string
|
||||
}) => {
|
||||
return apiTooot<{
|
||||
endpoint: string
|
||||
keys: { public: string; private: string; auth: string }
|
||||
}>({
|
||||
method: 'post',
|
||||
service: 'push',
|
||||
url: 'register1',
|
||||
body: { expoToken, instanceUrl, accountId, accountFull },
|
||||
sentry: true
|
||||
})
|
||||
}
|
||||
|
||||
const register2 = async ({
|
||||
expoToken,
|
||||
serverKey,
|
||||
instanceUrl,
|
||||
accountId,
|
||||
removeKeys
|
||||
}: {
|
||||
expoToken: string
|
||||
serverKey: Mastodon.PushSubscription['server_key']
|
||||
instanceUrl: string
|
||||
accountId: Mastodon.Account['id']
|
||||
removeKeys: boolean
|
||||
serverKey: string
|
||||
auth: string | null
|
||||
}) => {
|
||||
return apiTooot({
|
||||
method: 'post',
|
||||
service: 'push',
|
||||
url: 'register2',
|
||||
body: { expoToken, instanceUrl, accountId, serverKey, removeKeys },
|
||||
url: `/push/subscribe/${expoToken}/${instanceUrl}/${accountId}`,
|
||||
body: { accountFull, serverKey, auth },
|
||||
sentry: true
|
||||
})
|
||||
}
|
||||
|
@ -55,7 +36,7 @@ const register2 = async ({
|
|||
const pushRegister = async (
|
||||
state: RootState,
|
||||
expoToken: string
|
||||
): Promise<Instance['push']['keys']> => {
|
||||
): Promise<Instance['push']['keys']['auth']> => {
|
||||
const instance = getInstance(state)
|
||||
const instanceUrl = instance?.url
|
||||
const instanceUri = instance?.uri
|
||||
|
@ -68,18 +49,18 @@ const pushRegister = async (
|
|||
|
||||
const accountId = instanceAccount.id
|
||||
const accountFull = `@${instanceAccount.acct}@${instanceUri}`
|
||||
const serverRes = await register1({
|
||||
expoToken,
|
||||
instanceUrl,
|
||||
accountId,
|
||||
accountFull
|
||||
})
|
||||
|
||||
const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${expoToken}/${instanceUrl}/${accountId}`
|
||||
const auth = base64.encodeFromByteArray(Random.getRandomBytes(16))
|
||||
|
||||
const alerts = instancePush.alerts
|
||||
const formData = new FormData()
|
||||
formData.append('subscription[endpoint]', serverRes.body.endpoint)
|
||||
formData.append('subscription[keys][p256dh]', serverRes.body.keys.public)
|
||||
formData.append('subscription[keys][auth]', serverRes.body.keys.auth)
|
||||
formData.append('subscription[endpoint]', endpoint)
|
||||
formData.append(
|
||||
'subscription[keys][p256dh]',
|
||||
Constants.manifest?.extra?.toootPushKeyPublic
|
||||
)
|
||||
formData.append('subscription[keys][auth]', auth)
|
||||
Object.keys(alerts).map(key =>
|
||||
// @ts-ignore
|
||||
formData.append(`data[alerts][${key}]`, alerts[key].value.toString())
|
||||
|
@ -91,12 +72,13 @@ const pushRegister = async (
|
|||
body: formData
|
||||
})
|
||||
|
||||
await register2({
|
||||
await subscribe({
|
||||
expoToken,
|
||||
serverKey: res.body.server_key,
|
||||
instanceUrl,
|
||||
accountId,
|
||||
removeKeys: instancePush.decode.value === false
|
||||
accountFull,
|
||||
serverKey: res.body.server_key,
|
||||
auth: instancePush.decode.value === false ? null : auth
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
|
@ -142,7 +124,7 @@ const pushRegister = async (
|
|||
})
|
||||
}
|
||||
|
||||
return Promise.resolve(serverRes.body.keys)
|
||||
return Promise.resolve(auth)
|
||||
}
|
||||
|
||||
export default pushRegister
|
||||
|
|
|
@ -20,14 +20,8 @@ const pushUnregister = async (state: RootState, expoToken: string) => {
|
|||
})
|
||||
|
||||
await apiTooot<{ endpoint: string; publicKey: string; auth: string }>({
|
||||
method: 'post',
|
||||
service: 'push',
|
||||
url: 'unregister',
|
||||
body: {
|
||||
expoToken,
|
||||
instanceUrl: instance.url,
|
||||
accountId: instance.account.id
|
||||
},
|
||||
method: 'delete',
|
||||
url: `/push/unsubscribe/${expoToken}/${instance.url}/${instance.account.id}`,
|
||||
sentry: true
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
|
||||
export const updateConfiguration = createAsyncThunk(
|
||||
'instances/updateConfiguration',
|
||||
async (): Promise<Mastodon.Instance> => {
|
||||
return apiInstance<Mastodon.Instance>({
|
||||
method: 'get',
|
||||
url: `instance`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
)
|
|
@ -10,7 +10,7 @@ export const updateInstancePush = createAsyncThunk(
|
|||
async (
|
||||
disable: boolean,
|
||||
{ getState }
|
||||
): Promise<Instance['push']['keys'] | undefined> => {
|
||||
): Promise<Instance['push']['keys']['auth'] | undefined> => {
|
||||
const state = getState() as RootState
|
||||
const expoToken = (
|
||||
await Notifications.getExpoPushTokenAsync({
|
||||
|
|
|
@ -26,14 +26,10 @@ export const updateInstancePushDecode = createAsyncThunk(
|
|||
).data
|
||||
|
||||
await apiTooot({
|
||||
method: 'post',
|
||||
service: 'push',
|
||||
url: 'update-decode',
|
||||
method: 'put',
|
||||
url: `/push/update-decode/${expoToken}/${instance.url}/${instance.account.id}`,
|
||||
body: {
|
||||
expoToken,
|
||||
instanceUrl: instance.url,
|
||||
accountId: instance.account.id,
|
||||
...(disable && { keys: instance.push.keys })
|
||||
auth: !disable ? null : instance.push.keys.auth
|
||||
},
|
||||
sentry: true
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { findIndex } from 'lodash'
|
|||
import addInstance from './instances/add'
|
||||
import removeInstance from './instances/remove'
|
||||
import { updateAccountPreferences } from './instances/updateAccountPreferences'
|
||||
import { updateConfiguration } from './instances/updateConfiguration'
|
||||
import { updateFilters } from './instances/updateFilters'
|
||||
import { updateInstancePush } from './instances/updatePush'
|
||||
import { updateInstancePushAlert } from './instances/updatePushAlert'
|
||||
|
@ -21,13 +22,14 @@ export type Instance = {
|
|||
token: string
|
||||
uri: Mastodon.Instance['uri']
|
||||
urls: Mastodon.Instance['urls']
|
||||
max_toot_chars: number
|
||||
account: {
|
||||
id: Mastodon.Account['id']
|
||||
acct: Mastodon.Account['acct']
|
||||
avatarStatic: Mastodon.Account['avatar_static']
|
||||
preferences: Mastodon.Preferences
|
||||
}
|
||||
max_toot_chars?: number // To be deprecated in v4
|
||||
configuration?: Mastodon.Instance['configuration']
|
||||
filters: Mastodon.Filter[]
|
||||
notifications_filter: {
|
||||
follow: boolean
|
||||
|
@ -37,65 +39,37 @@ export type Instance = {
|
|||
poll: boolean
|
||||
follow_request: boolean
|
||||
}
|
||||
push:
|
||||
| {
|
||||
global: { loading: boolean; value: boolean }
|
||||
decode: { loading: boolean; value: true }
|
||||
alerts: {
|
||||
follow: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['follow']
|
||||
}
|
||||
favourite: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||
}
|
||||
reblog: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||
}
|
||||
mention: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['mention']
|
||||
}
|
||||
poll: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['poll']
|
||||
}
|
||||
}
|
||||
keys: {
|
||||
auth: string
|
||||
public: string
|
||||
private: string
|
||||
}
|
||||
push: {
|
||||
global: { loading: boolean; value: boolean }
|
||||
decode: { loading: boolean; value: boolean }
|
||||
alerts: {
|
||||
follow: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['follow']
|
||||
}
|
||||
| {
|
||||
global: { loading: boolean; value: boolean }
|
||||
decode: { loading: boolean; value: false }
|
||||
alerts: {
|
||||
follow: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['follow']
|
||||
}
|
||||
favourite: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||
}
|
||||
reblog: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||
}
|
||||
mention: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['mention']
|
||||
}
|
||||
poll: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['poll']
|
||||
}
|
||||
}
|
||||
keys: undefined
|
||||
favourite: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||
}
|
||||
reblog: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||
}
|
||||
mention: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['mention']
|
||||
}
|
||||
poll: {
|
||||
loading: boolean
|
||||
value: Mastodon.PushSubscription['alerts']['poll']
|
||||
}
|
||||
}
|
||||
keys: {
|
||||
auth?: string
|
||||
public?: string // legacy
|
||||
private?: string // legacy
|
||||
}
|
||||
}
|
||||
drafts: ComposeStateDraft[]
|
||||
}
|
||||
|
||||
|
@ -107,8 +81,8 @@ export const instancesInitialState: InstancesState = {
|
|||
instances: []
|
||||
}
|
||||
|
||||
const findInstanceActive = (state: Instance[]) =>
|
||||
state.findIndex(instance => instance.active)
|
||||
const findInstanceActive = (instances: Instance[]) =>
|
||||
instances.findIndex(instance => instance.active)
|
||||
|
||||
const instancesSlice = createSlice({
|
||||
name: 'instances',
|
||||
|
@ -254,12 +228,24 @@ const instancesSlice = createSlice({
|
|||
console.error(action.error)
|
||||
})
|
||||
|
||||
// Update Instance Configuration
|
||||
.addCase(updateConfiguration.fulfilled, (state, action) => {
|
||||
const activeIndex = findInstanceActive(state.instances)
|
||||
state.instances[activeIndex].max_toot_chars =
|
||||
action.payload.max_toot_chars
|
||||
state.instances[activeIndex].configuration =
|
||||
action.payload.configuration
|
||||
})
|
||||
.addCase(updateConfiguration.rejected, (_, action) => {
|
||||
console.error(action.error)
|
||||
})
|
||||
|
||||
// Update Instance Push Global
|
||||
.addCase(updateInstancePush.fulfilled, (state, action) => {
|
||||
const activeIndex = findInstanceActive(state.instances)
|
||||
state.instances[activeIndex].push.global.loading = false
|
||||
state.instances[activeIndex].push.global.value = action.meta.arg
|
||||
state.instances[activeIndex].push.keys = action.payload
|
||||
state.instances[activeIndex].push.keys = { auth: action.payload }
|
||||
})
|
||||
.addCase(updateInstancePush.rejected, state => {
|
||||
const activeIndex = findInstanceActive(state.instances)
|
||||
|
@ -314,56 +300,62 @@ export const getInstanceActive = ({ instances: { instances } }: RootState) =>
|
|||
export const getInstances = ({ instances: { instances } }: RootState) =>
|
||||
instances
|
||||
|
||||
export const getInstance = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive] : null
|
||||
}
|
||||
export const getInstance = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]
|
||||
|
||||
export const getInstanceUrl = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].url : null
|
||||
}
|
||||
export const getInstanceUrl = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.url
|
||||
|
||||
export const getInstanceUri = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].uri : null
|
||||
}
|
||||
export const getInstanceUri = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.uri
|
||||
|
||||
export const getInstanceUrls = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].urls : null
|
||||
}
|
||||
export const getInstanceUrls = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.urls
|
||||
|
||||
export const getInstanceMaxTootChar = ({
|
||||
/* Get Instance Configuration */
|
||||
export const getInstanceConfigurationStatusMaxChars = ({
|
||||
instances: { instances }
|
||||
}: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].max_toot_chars : 500
|
||||
}
|
||||
}: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.configuration?.statuses
|
||||
.max_characters ||
|
||||
instances[findInstanceActive(instances)]?.max_toot_chars ||
|
||||
500
|
||||
|
||||
export const getInstanceAccount = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].account : null
|
||||
}
|
||||
export const getInstanceConfigurationStatusMaxAttachments = ({
|
||||
instances: { instances }
|
||||
}: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.configuration?.statuses
|
||||
.max_media_attachments || 4
|
||||
|
||||
export const getInstanceConfigurationStatusCharsURL = ({
|
||||
instances: { instances }
|
||||
}: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.configuration?.statuses
|
||||
.characters_reserved_per_url || 23
|
||||
|
||||
export const getInstanceConfigurationPoll = ({
|
||||
instances: { instances }
|
||||
}: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.configuration?.polls || {
|
||||
max_options: 4,
|
||||
max_characters_per_option: 50,
|
||||
min_expiration: 300,
|
||||
max_expiration: 2629746
|
||||
}
|
||||
/* END */
|
||||
|
||||
export const getInstanceAccount = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.account
|
||||
|
||||
export const getInstanceNotificationsFilter = ({
|
||||
instances: { instances }
|
||||
}: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1
|
||||
? instances[instanceActive].notifications_filter
|
||||
: null
|
||||
}
|
||||
}: RootState) => instances[findInstanceActive(instances)].notifications_filter
|
||||
|
||||
export const getInstancePush = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].push : null
|
||||
}
|
||||
export const getInstancePush = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.push
|
||||
|
||||
export const getInstanceDrafts = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].drafts : null
|
||||
}
|
||||
export const getInstanceDrafts = ({ instances: { instances } }: RootState) =>
|
||||
instances[findInstanceActive(instances)]?.drafts
|
||||
|
||||
export const {
|
||||
updateInstanceActive,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import apiGeneral from '@api/general'
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import { Constants } from 'react-native-unimodules'
|
||||
import Constants from 'expo-constants'
|
||||
|
||||
export const retriveVersionLatest = createAsyncThunk(
|
||||
'version/latest',
|
||||
|
|
Loading…
Reference in New Issue